From d601300034c6ae73ea2f3eb0ab82898c073cce5b Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:23:50 +0200 Subject: [PATCH] fix(db-mongodb): querying polymorphic relationships with the `all` operator (#10704) Fixes https://github.com/payloadcms/payload/issues/10678 --- docs/queries/overview.mdx | 34 ++++----- .../src/queries/sanitizeQueryValue.ts | 13 ++++ packages/drizzle/src/queries/operatorMap.ts | 1 + test/relationships/int.spec.ts | 75 +++++++++++++++++++ 4 files changed, 106 insertions(+), 17 deletions(-) diff --git a/docs/queries/overview.mdx b/docs/queries/overview.mdx index fe12faff46..30cbd7631d 100644 --- a/docs/queries/overview.mdx +++ b/docs/queries/overview.mdx @@ -39,23 +39,23 @@ _The exact query syntax will depend on the API you are using, but the concepts a The following operators are available for use in queries: -| Operator | Description | -| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `equals` | The value must be exactly equal. | -| `not_equals` | The query will return all documents where the value is not equal. | -| `greater_than` | For numeric or date-based fields. | -| `greater_than_equal` | For numeric or date-based fields. | -| `less_than` | For numeric or date-based fields. | -| `less_than_equal` | For numeric or date-based fields. | -| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. | -| `contains` | Must contain the value entered, case-insensitive. | -| `in` | The value must be found within the provided comma-delimited list of values. | -| `not_in` | The value must NOT be within the provided comma-delimited list of values. | -| `all` | The value must contain all values provided in the comma-delimited list. | -| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). | -| `near` | For distance related to a [Point Field](../fields/point) comma separated as `, , , `. | -| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) | -| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) | +| Operator | Description | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `equals` | The value must be exactly equal. | +| `not_equals` | The query will return all documents where the value is not equal. | +| `greater_than` | For numeric or date-based fields. | +| `greater_than_equal` | For numeric or date-based fields. | +| `less_than` | For numeric or date-based fields. | +| `less_than_equal` | For numeric or date-based fields. | +| `like` | Case-insensitive string must be present. If string of words, all words must be present, in any order. | +| `contains` | Must contain the value entered, case-insensitive. | +| `in` | The value must be found within the provided comma-delimited list of values. | +| `not_in` | The value must NOT be within the provided comma-delimited list of values. | +| `all` | The value must contain all values provided in the comma-delimited list. Note: currently this operator is supported only with the MongoDB adapter. | +| `exists` | Only return documents where the value either exists (`true`) or does not exist (`false`). | +| `near` | For distance related to a [Point Field](../fields/point) comma separated as `, , , `. | +| `within` | For [Point Fields](../fields/point) to filter documents based on whether points are inside of the given area defined in GeoJSON. [Example](../fields/point#querying-within) | +| `intersects` | For [Point Fields](../fields/point) to filter documents based on whether points intersect with the given area defined in GeoJSON. [Example](../fields/point#querying-intersects) | **Tip:** diff --git a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts index 872d35b5f8..219cbd2a44 100644 --- a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts +++ b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts @@ -324,6 +324,19 @@ export const sanitizeQueryValue = ({ } } } + + if ( + operator === 'all' && + Array.isArray(relationTo) && + path.endsWith('.value') && + Array.isArray(formattedValue) + ) { + formattedValue.forEach((v, i) => { + if (Types.ObjectId.isValid(v)) { + formattedValue[i] = new Types.ObjectId(v) + } + }) + } } // Set up specific formatting necessary by operators diff --git a/packages/drizzle/src/queries/operatorMap.ts b/packages/drizzle/src/queries/operatorMap.ts index 0fe4a804f3..18a3ee7923 100644 --- a/packages/drizzle/src/queries/operatorMap.ts +++ b/packages/drizzle/src/queries/operatorMap.ts @@ -48,6 +48,7 @@ export const operatorMap: Operators = { less_than_equal: lte, like: ilike, not_equals: ne, + // TODO: support this // all: all, not_in: notInArray, or, diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index 58834286ad..73dabb9b00 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -39,6 +39,8 @@ const dirname = path.dirname(filename) type EasierChained = { id: string; relation: EasierChained } +const mongoIt = process.env.PAYLOAD_DATABASE === 'mongodb' ? it : it.skip + describe('Relationships', () => { beforeAll(async () => { ;({ payload, restClient } = await initPayloadInt(dirname)) @@ -459,6 +461,46 @@ describe('Relationships', () => { expect(query2.totalDocs).toStrictEqual(2) }) + // all operator is not supported in Postgres yet for any fields + mongoIt('should query using "all" by hasMany relationship field', async () => { + const movie1 = await payload.create({ + collection: 'movies', + data: {}, + }) + const movie2 = await payload.create({ + collection: 'movies', + data: {}, + }) + + await payload.create({ + collection: 'directors', + data: { + name: 'Quentin Tarantino', + movies: [movie2.id, movie1.id], + }, + }) + + await payload.create({ + collection: 'directors', + data: { + name: 'Quentin Tarantino', + movies: [movie2.id], + }, + }) + + const query1 = await payload.find({ + collection: 'directors', + depth: 0, + where: { + movies: { + all: [movie1.id], + }, + }, + }) + + expect(query1.totalDocs).toStrictEqual(1) + }) + it('should sort by a property of a hasMany relationship', async () => { // no support for sort by relation in mongodb if (isMongoose(payload)) { @@ -1352,6 +1394,39 @@ describe('Relationships', () => { expect(queryTwo.docs).toHaveLength(1) }) + // all operator is not supported in Postgres yet for any fields + mongoIt('should allow REST all querying on polymorphic relationships', async () => { + const movie = await payload.create({ + collection: 'movies', + data: { + name: 'Pulp Fiction 2', + }, + }) + await payload.create({ + collection: polymorphicRelationshipsSlug, + data: { + polymorphic: { + relationTo: 'movies', + value: movie.id, + }, + }, + }) + + const queryOne = await restClient + .GET(`/${polymorphicRelationshipsSlug}`, { + query: { + where: { + 'polymorphic.value': { + all: [movie.id], + }, + }, + }, + }) + .then((res) => res.json()) + + expect(queryOne.docs).toHaveLength(1) + }) + it('should allow querying on polymorphic relationships with an object syntax', async () => { const movie = await payload.create({ collection: 'movies',