From 8ce15c8b07800397a50dcf790c263ed5b3cfad53 Mon Sep 17 00:00:00 2001 From: Jesse Sivonen Date: Fri, 19 Jan 2024 20:35:58 +0200 Subject: [PATCH] fix(db-postgres): query unset relation (#4862) --- .../src/queries/sanitizeQueryValue.ts | 1 + .../src/queries/getTableColumnFromPath.ts | 16 ++--- .../db-postgres/src/queries/parseParams.ts | 10 ++++ test/relationships/config.ts | 30 ++++++++++ test/relationships/int.spec.ts | 59 +++++++++++++++++++ 5 files changed, 105 insertions(+), 11 deletions(-) diff --git a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts index 604df04351..9da1827afd 100644 --- a/packages/db-mongodb/src/queries/sanitizeQueryValue.ts +++ b/packages/db-mongodb/src/queries/sanitizeQueryValue.ts @@ -77,6 +77,7 @@ export const sanitizeQueryValue = ({ // Object equality requires the value to be the first key in the object that is being queried. if ( operator === 'equals' && + formattedValue && typeof formattedValue === 'object' && formattedValue.value && formattedValue.relationTo diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index ba34857bd9..6ff91c5ad4 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -2,7 +2,7 @@ import type { SQL } from 'drizzle-orm' import type { Field, FieldAffectingData, TabAsField } from 'payload/types' -import { and, eq, sql } from 'drizzle-orm' +import { and, eq, like, sql } from 'drizzle-orm' import { alias } from 'drizzle-orm/pg-core' import { APIError } from 'payload/errors' import { fieldAffectsData, tabHasName } from 'payload/types' @@ -317,21 +317,15 @@ export const getTableColumnFromPath = ({ // Join in the relationships table joinAliases.push({ - condition: eq( - (aliasTable || adapter.tables[rootTableName]).id, - aliasRelationshipTable.parent, + condition: and( + eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent), + like(aliasRelationshipTable.path, `${constraintPath}${field.name}`), ), table: aliasRelationshipTable, }) selectFields[`${relationTableName}.path`] = aliasRelationshipTable.path - constraints.push({ - columnName: 'path', - table: aliasRelationshipTable, - value: `${constraintPath}${field.name}`, - }) - let newAliasTable if (typeof field.relationTo === 'string') { @@ -428,7 +422,7 @@ export const getTableColumnFromPath = ({ columnName: `${columnPrefix}${field.name}`, constraints, field, - pathSegments: pathSegments, + pathSegments, table: targetTable, } } diff --git a/packages/db-postgres/src/queries/parseParams.ts b/packages/db-postgres/src/queries/parseParams.ts index 700b9b0871..ef19156496 100644 --- a/packages/db-postgres/src/queries/parseParams.ts +++ b/packages/db-postgres/src/queries/parseParams.ts @@ -207,6 +207,16 @@ export async function parseParams({ break } + if (operator === 'equals' && queryValue === null) { + constraints.push(isNull(rawColumn || table[columnName])) + break + } + + if (operator === 'not_equals' && queryValue === null) { + constraints.push(isNotNull(rawColumn || table[columnName])) + break + } + constraints.push( operatorMap[queryOperator](rawColumn || table[columnName], queryValue), ) diff --git a/test/relationships/config.ts b/test/relationships/config.ts index 46b48f1456..c22f15ef4e 100644 --- a/test/relationships/config.ts +++ b/test/relationships/config.ts @@ -43,6 +43,7 @@ export const chainedRelSlug = 'chained' export const customIdSlug = 'custom-id' export const customIdNumberSlug = 'custom-id-number' export const polymorphicRelationshipsSlug = 'polymorphic-relationships' +export const treeSlug = 'tree' export default buildConfigWithDefaults({ collections: [ @@ -244,6 +245,20 @@ export default buildConfigWithDefaults({ }, ], }, + { + slug: treeSlug, + fields: [ + { + name: 'text', + type: 'text', + }, + { + name: 'parent', + type: 'relationship', + relationTo: 'tree', + }, + ], + }, ], onInit: async (payload) => { await payload.create({ @@ -337,5 +352,20 @@ export default buildConfigWithDefaults({ filteredRelation: filteredRelation.id, }, }) + + const root = await payload.create({ + collection: 'tree', + data: { + text: 'root', + }, + }) + + await payload.create({ + collection: 'tree', + data: { + text: 'sub', + parent: root.id, + }, + }) }, }) diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index 81536269b3..da19f9bf7f 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -22,6 +22,7 @@ import config, { defaultAccessRelSlug, relationSlug, slug, + treeSlug, } from './config' let apiUrl @@ -554,6 +555,64 @@ describe('Relationships', () => { expect(stanleyNeverMadeMovies.movies).toHaveLength(0) }) }) + + describe('Hierarchy', () => { + it('finds 1 root item with equals', async () => { + const { + docs: [item], + totalDocs: count, + } = await payload.find({ + collection: treeSlug, + where: { + parent: { equals: null }, + }, + }) + expect(count).toBe(1) + expect(item.text).toBe('root') + }) + + it('finds 1 root item with exists', async () => { + const { + docs: [item], + totalDocs: count, + } = await payload.find({ + collection: treeSlug, + where: { + parent: { exists: false }, + }, + }) + expect(count).toBe(1) + expect(item.text).toBe('root') + }) + + it('finds 1 sub item with equals', async () => { + const { + docs: [item], + totalDocs: count, + } = await payload.find({ + collection: treeSlug, + where: { + parent: { not_equals: null }, + }, + }) + expect(count).toBe(1) + expect(item.text).toBe('sub') + }) + + it('finds 1 sub item with exists', async () => { + const { + docs: [item], + totalDocs: count, + } = await payload.find({ + collection: treeSlug, + where: { + parent: { exists: true }, + }, + }) + expect(count).toBe(1) + expect(item.text).toBe('sub') + }) + }) }) describe('Creating', () => {