fix(db-mongodb): querying by localized polymorphic relationships using objects (#10037)

Previously, queries like this didn't work:
```ts
const res = await payload.find({
  collection: 'polymorphic-relationships',
  where: {
    polymorphicLocalized: {
      equals: {
        relationTo: 'movies',
        value: movie.id,
      },
    },
  },
})
```

This was due to the incorrectly passed path to MongoDB without
`.{locale}` suffix.
Additionally, to MongoDB now we send:
```

{
  $or: [
    {
      polymorphic: {
        $eq: {
          relationTo: formattedValue.relationTo,
          value: formattedValue.value,
        },
      },
    },
    {
      polymorphic: {
        $eq: {
          relationTo: 'movies',
          value: 'some-id',
        },
      },
    },
  ],
},
```

Instead of:
```
{
  $and: [
    {
      'polymorphic.relationTo': {
        $eq: 'movies ',
      },
    },
    {
      'polymorphic.value': {
        $eq: 'some-id ',
      },
    },
  ],
}
```

To match the _exact_ value. This is essential when we do querying by
relationships with `hasMany: true` and custom IDs that can be repeated.
`$or` is needed if for some reason keys are stored in the DB in a
different order
This commit is contained in:
Sasha
2024-12-19 17:42:15 +02:00
committed by GitHub
parent 12dad35cf9
commit 5753efb0a4
5 changed files with 173 additions and 3 deletions

View File

@@ -87,6 +87,7 @@ export async function buildSearchParam({
const sanitizedQueryValue = sanitizeQueryValue({
field,
hasCustomID,
locale,
operator,
path,
payload,

View File

@@ -6,6 +6,7 @@ import { createArrayFromCommaDelineated } from 'payload'
type SanitizeQueryValueArgs = {
field: FlattenedField
hasCustomID: boolean
locale?: string
operator: string
path: string
payload: Payload
@@ -74,6 +75,7 @@ const getFieldFromSegments = ({
export const sanitizeQueryValue = ({
field,
hasCustomID,
locale,
operator,
path,
payload,
@@ -205,11 +207,34 @@ export const sanitizeQueryValue = ({
formattedValue.value = new Types.ObjectId(value)
}
let localizedPath = path
if (field.localized && payload.config.localization && locale) {
localizedPath = `${path}.${locale}`
}
return {
rawQuery: {
$and: [
{ [`${path}.value`]: { $eq: formattedValue.value } },
{ [`${path}.relationTo`]: { $eq: formattedValue.relationTo } },
$or: [
{
[localizedPath]: {
$eq: {
// disable auto sort
/* eslint-disable */
value: formattedValue.value,
relationTo: formattedValue.relationTo,
/* eslint-enable */
},
},
},
{
[localizedPath]: {
$eq: {
relationTo: formattedValue.relationTo,
value: formattedValue.value,
},
},
},
],
},
}

View File

@@ -290,6 +290,25 @@ export default buildConfigWithDefaults({
name: 'polymorphic',
relationTo: ['movies'],
},
{
type: 'relationship',
name: 'polymorphicLocalized',
relationTo: ['movies'],
localized: true,
},
{
type: 'relationship',
name: 'polymorphicMany',
hasMany: true,
relationTo: ['movies'],
},
{
type: 'relationship',
hasMany: true,
name: 'polymorphicManyLocalized',
localized: true,
relationTo: ['movies'],
},
],
},
{

View File

@@ -1364,6 +1364,112 @@ describe('Relationships', () => {
})
expect(res_2.docs).toHaveLength(0)
})
it('should allow querying on hasMany polymorphic relationships with an object syntax', async () => {
const movie = await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction 2',
},
})
const { id } = await payload.create({
collection: polymorphicRelationshipsSlug,
data: {
polymorphicMany: [
{
relationTo: 'movies',
value: movie.id,
},
],
},
})
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphicMany: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
expect(res.docs).toHaveLength(1)
expect(res.docs[0].id).toBe(id)
})
it('should allow querying on localized polymorphic relationships with an object syntax', async () => {
const movie = await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction 2',
},
})
const { id } = await payload.create({
collection: polymorphicRelationshipsSlug,
data: {
polymorphicLocalized: {
relationTo: 'movies',
value: movie.id,
},
},
})
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphicLocalized: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
expect(res.docs).toHaveLength(1)
expect(res.docs[0].id).toBe(id)
})
it('should allow querying on hasMany localized polymorphic relationships with an object syntax', async () => {
const movie = await payload.create({
collection: 'movies',
data: {
name: 'Pulp Fiction 2',
},
})
const { id } = await payload.create({
collection: polymorphicRelationshipsSlug,
data: {
polymorphicManyLocalized: [
{
relationTo: 'movies',
value: movie.id,
},
],
},
})
const res = await payload.find({
collection: 'polymorphic-relationships',
where: {
polymorphicManyLocalized: {
equals: {
relationTo: 'movies',
value: movie.id,
},
},
},
})
expect(res.docs).toHaveLength(1)
expect(res.docs[0].id).toBe(id)
})
})
})

View File

@@ -252,6 +252,22 @@ export interface PolymorphicRelationship {
relationTo: 'movies';
value: string | Movie;
} | null;
polymorphicLocalized?: {
relationTo: 'movies';
value: string | Movie;
} | null;
polymorphicMany?:
| {
relationTo: 'movies';
value: string | Movie;
}[]
| null;
polymorphicManyLocalized?:
| {
relationTo: 'movies';
value: string | Movie;
}[]
| null;
updatedAt: string;
createdAt: string;
}
@@ -591,6 +607,9 @@ export interface MovieReviewsSelect<T extends boolean = true> {
*/
export interface PolymorphicRelationshipsSelect<T extends boolean = true> {
polymorphic?: T;
polymorphicLocalized?: T;
polymorphicMany?: T;
polymorphicManyLocalized?: T;
updatedAt?: T;
createdAt?: T;
}