From efa56cefc15a48cd45b3aaba2eddacca79e1be30 Mon Sep 17 00:00:00 2001 From: Patrik Date: Thu, 8 Aug 2024 11:22:47 -0400 Subject: [PATCH] fix: filtering by non-poly `relationships` with `not_equals` operator (#7573) ## Description Fixes #5212 Fixes #6278 - [x] I have read and understand the [CONTRIBUTING.md](https://github.com/payloadcms/payload/blob/main/CONTRIBUTING.md) document in this repository. ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## Checklist: - [x] I have added tests that prove my fix is effective or that my feature works - [x] Existing test suite passes locally with my changes --- .../src/queries/buildSearchParams.ts | 18 ++++-- test/fields/collections/Relationship/index.ts | 12 ++++ test/fields/int.spec.ts | 64 +++++++++++++++++-- test/fields/payload-types.ts | 2 + 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/packages/db-mongodb/src/queries/buildSearchParams.ts b/packages/db-mongodb/src/queries/buildSearchParams.ts index 389e2cea38..336b076527 100644 --- a/packages/db-mongodb/src/queries/buildSearchParams.ts +++ b/packages/db-mongodb/src/queries/buildSearchParams.ts @@ -3,7 +3,7 @@ import type { PathToQuery } from 'payload/database' import type { Field } from 'payload/types' import type { Operator } from 'payload/types' -import objectID from 'bson-objectid' +import ObjectIdImport from 'bson-objectid' import mongoose from 'mongoose' import { getLocalizedPaths } from 'payload/database' import { fieldAffectsData } from 'payload/types' @@ -14,6 +14,8 @@ import type { MongooseAdapter } from '..' import { operatorMap } from './operatorMap' import { sanitizeQueryValue } from './sanitizeQueryValue' +const ObjectId = ObjectIdImport + type SearchParam = { path?: string rawQuery?: unknown @@ -195,16 +197,20 @@ export async function buildSearchParam({ if (field.type === 'relationship' || field.type === 'upload') { let hasNumberIDRelation + let multiIDCondition = '$or' + if (operatorKey === '$ne') multiIDCondition = '$and' const result = { value: { - $or: [{ [path]: { [operatorKey]: formattedValue } }], + [multiIDCondition]: [{ [path]: { [operatorKey]: formattedValue } }], }, } if (typeof formattedValue === 'string') { if (mongoose.Types.ObjectId.isValid(formattedValue)) { - result.value.$or.push({ [path]: { [operatorKey]: objectID(formattedValue) } }) + result.value[multiIDCondition].push({ + [path]: { [operatorKey]: ObjectId(formattedValue) }, + }) } else { ;(Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo]).forEach( (relationTo) => { @@ -225,11 +231,13 @@ export async function buildSearchParam({ ) if (hasNumberIDRelation) - result.value.$or.push({ [path]: { [operatorKey]: parseFloat(formattedValue) } }) + result.value[multiIDCondition].push({ + [path]: { [operatorKey]: parseFloat(formattedValue) }, + }) } } - if (result.value.$or.length > 1) { + if (result.value[multiIDCondition].length > 1) { return result } } diff --git a/test/fields/collections/Relationship/index.ts b/test/fields/collections/Relationship/index.ts index cecca09b75..3d95e19857 100644 --- a/test/fields/collections/Relationship/index.ts +++ b/test/fields/collections/Relationship/index.ts @@ -31,6 +31,18 @@ const RelationshipFields: CollectionConfig = { }, }, }, + { + name: 'relationNoHasManyNonPolymorphic', + relationTo: 'text-fields', + type: 'relationship', + hasMany: false, + }, + { + name: 'relationHasManyNonPolymorphic', + relationTo: 'text-fields', + type: 'relationship', + hasMany: true, + }, { name: 'relationToSelf', relationTo: relationshipFieldsSlug, diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 80cab1293e..ba51b8f04d 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -244,9 +244,6 @@ describe('Fields', () => { }, }) - const anyChildren = await payload.find({ - collection: relationshipFieldsSlug, - }) const allChildren = await payload.find({ collection: relationshipFieldsSlug, where: { @@ -1557,7 +1554,47 @@ describe('Fields', () => { }) }) - describe('relationships', () => { + describe('relationship queries', () => { + let textDoc + let otherTextDoc + let relationshipDocWithNonPolyOne + let relationshipDocWithNonPolyTwo + const textDocText = 'text document' + const otherTextDocText = 'alt text' + + beforeEach(async () => { + textDoc = await payload.create({ + collection: 'text-fields', + data: { + text: textDocText, + }, + }) + otherTextDoc = await payload.create({ + collection: 'text-fields', + data: { + text: otherTextDocText, + }, + }) + const relationship = { relationTo: 'text-fields', value: textDoc.id } + relationshipDocWithNonPolyOne = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationship, + relationNoHasManyNonPolymorphic: textDoc.id, + relationHasManyNonPolymorphic: textDoc.id, + }, + }) + + relationshipDocWithNonPolyTwo = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationship, + relationNoHasManyNonPolymorphic: otherTextDoc.id, + relationHasManyNonPolymorphic: otherTextDoc.id, + }, + }) + }) + it('should not crash if querying with empty in operator', async () => { const query = await payload.find({ collection: 'relationship-fields', @@ -1570,6 +1607,25 @@ describe('Fields', () => { expect(query.docs).toBeDefined() }) + + it('should properly query non-polymorphic relationship with not equals', async () => { + const withoutHasMany = await payload.find({ + collection: relationshipFieldsSlug, + where: { + relationNoHasManyNonPolymorphic: { not_equals: otherTextDoc.id }, + }, + }) + + const withHasMany = await payload.find({ + collection: relationshipFieldsSlug, + where: { + relationHasManyNonPolymorphic: { not_equals: textDoc.id }, + }, + }) + + expect(withoutHasMany.docs).toHaveLength(1) + expect(withHasMany.docs).toHaveLength(1) + }) }) describe('clearable fields - exists', () => { diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index fb4d6351c8..dce88095d1 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -955,6 +955,8 @@ export interface RelationshipField { } )[] | null + relationNoHasManyNonPolymorphic?: (string | null) | TextField + relationHasManyNonPolymorphic?: (string | TextField)[] | null relationToSelf?: (string | null) | RelationshipField relationToSelfSelectOnly?: (string | null) | RelationshipField relationWithDynamicDefault?: (string | null) | User