From 2c67eff059ee6a1c9e878f04b12419ecd4c603b5 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Fri, 13 Oct 2023 13:32:44 -0400 Subject: [PATCH 1/5] fix(db-postgres): query relationship in array alias --- .../src/queries/getTableColumnFromPath.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index 9c0a0143ee..dd1109e03e 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -40,6 +40,7 @@ type Args = { joins: BuildQueryJoins locale?: string pathSegments: string[] + rootTableName?: string selectFields: Record tableName: string } @@ -59,11 +60,14 @@ export const getTableColumnFromPath = ({ joins, locale: incomingLocale, pathSegments: incomingSegments, + rootTableName: incomingRootTableName, selectFields, tableName, }: Args): TableColumn => { const fieldPath = incomingSegments[0] let locale = incomingLocale + const rootTableName = incomingRootTableName || tableName + const field = flattenTopLevelFields(fields as Field[]).find( (fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath, ) as Field | TabAsField @@ -114,6 +118,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields, tableName: newTableName, }) @@ -131,6 +136,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields, tableName: newTableName, }) @@ -146,6 +152,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields, tableName: newTableName, }) @@ -178,6 +185,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields, tableName: newTableName, }) @@ -212,6 +220,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields, tableName: newTableName, }) @@ -235,6 +244,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName, selectFields: blockSelectFields, tableName: newTableName, }) @@ -283,7 +293,7 @@ export const getTableColumnFromPath = ({ case 'relationship': case 'upload': { let relationshipFields - const relationTableName = `${tableName}_rels` + const relationTableName = `${rootTableName}_rels` const newCollectionPath = pathSegments.slice(1).join('.') const aliasRelationshipTableName = uuid() @@ -368,6 +378,7 @@ export const getTableColumnFromPath = ({ joins, locale, pathSegments: pathSegments.slice(1), + rootTableName: newTableName, selectFields, tableName: newTableName, }) From 21649537a6e09f623ad23f0612161245be0384de Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Sat, 14 Oct 2023 15:17:42 -0400 Subject: [PATCH 2/5] fix(db-postgres): query relationship path inside arrays --- .../src/queries/getTableColumnFromPath.ts | 16 ++- .../db-postgres/src/queries/parseParams.ts | 6 +- test/fields/collections/Relationship/index.ts | 54 +++++--- test/fields/config.ts | 8 +- test/fields/int.spec.ts | 116 ++++++++++++++++++ 5 files changed, 174 insertions(+), 26 deletions(-) diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index dd1109e03e..abdc43247a 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -34,6 +34,7 @@ type Args = { aliasTable?: GenericTable collectionPath: string columnPrefix?: string + constraintPath?: string constraints?: Constraint[] fields: (Field | TabAsField)[] joinAliases: BuildQueryJoinAliases @@ -54,6 +55,7 @@ export const getTableColumnFromPath = ({ aliasTable, collectionPath, columnPrefix = '', + constraintPath: incomingConstraintPath, constraints = [], fields, joinAliases, @@ -67,6 +69,7 @@ export const getTableColumnFromPath = ({ const fieldPath = incomingSegments[0] let locale = incomingLocale const rootTableName = incomingRootTableName || tableName + let constraintPath = incomingConstraintPath || '' const field = flattenTopLevelFields(fields as Field[]).find( (fieldToFind) => fieldAffectsData(fieldToFind) && fieldToFind.name === fieldPath, @@ -109,6 +112,7 @@ export const getTableColumnFromPath = ({ aliasTable, collectionPath, columnPrefix, + constraintPath, constraints, fields: field.tabs.map((tab) => ({ ...tab, @@ -130,6 +134,7 @@ export const getTableColumnFromPath = ({ aliasTable, collectionPath, columnPrefix: `${columnPrefix}${field.name}_`, + constraintPath, constraints, fields: field.fields, joinAliases, @@ -146,6 +151,7 @@ export const getTableColumnFromPath = ({ aliasTable, collectionPath, columnPrefix, + constraintPath, constraints, fields: field.fields, joinAliases, @@ -179,6 +185,7 @@ export const getTableColumnFromPath = ({ aliasTable, collectionPath, columnPrefix: `${columnPrefix}${field.name}_`, + constraintPath, constraints, fields: field.fields, joinAliases, @@ -193,6 +200,7 @@ export const getTableColumnFromPath = ({ case 'array': { newTableName = `${tableName}_${toSnakeCase(field.name)}` + constraintPath = `${constraintPath}${field.name}.%.` if (locale && field.localized && adapter.payload.config.localization) { joins[newTableName] = and( eq(adapter.tables[tableName].id, adapter.tables[newTableName]._parentID), @@ -214,6 +222,7 @@ export const getTableColumnFromPath = ({ return getTableColumnFromPath({ adapter, collectionPath, + constraintPath, constraints, fields: field.fields, joinAliases, @@ -238,6 +247,7 @@ export const getTableColumnFromPath = ({ result = getTableColumnFromPath({ adapter, collectionPath, + constraintPath: '', constraints: blockConstraints, fields: block.fields, joinAliases, @@ -295,7 +305,7 @@ export const getTableColumnFromPath = ({ let relationshipFields const relationTableName = `${rootTableName}_rels` const newCollectionPath = pathSegments.slice(1).join('.') - + // missing FROM-clause entry for table "relationship_fields_array" const aliasRelationshipTableName = uuid() const aliasRelationshipTable = alias( adapter.tables[relationTableName], @@ -305,7 +315,7 @@ export const getTableColumnFromPath = ({ // Join in the relationships table joinAliases.push({ condition: eq( - (aliasTable || adapter.tables[tableName]).id, + (aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent, ), table: aliasRelationshipTable, @@ -316,7 +326,7 @@ export const getTableColumnFromPath = ({ constraints.push({ columnName: 'path', table: aliasRelationshipTable, - value: field.name, + value: `${constraintPath}${field.name}`, }) let newAliasTable diff --git a/packages/db-postgres/src/queries/parseParams.ts b/packages/db-postgres/src/queries/parseParams.ts index ba95dafdec..beadd0e178 100644 --- a/packages/db-postgres/src/queries/parseParams.ts +++ b/packages/db-postgres/src/queries/parseParams.ts @@ -100,7 +100,11 @@ export async function parseParams({ const val = where[relationOrPath][operator] queryConstraints.forEach(({ columnName: col, table: constraintTable, value }) => { - constraints.push(operatorMap.equals(constraintTable[col], value)) + if (typeof value === 'string' && value.indexOf('%') > -1) { + constraints.push(operatorMap.like(constraintTable[col], value)) + } else { + constraints.push(operatorMap.equals(constraintTable[col], value)) + } }) if (['json', 'richText'].includes(field.type) && Array.isArray(pathSegments)) { diff --git a/test/fields/collections/Relationship/index.ts b/test/fields/collections/Relationship/index.ts index a9587176d8..157cab3f9c 100644 --- a/test/fields/collections/Relationship/index.ts +++ b/test/fields/collections/Relationship/index.ts @@ -3,63 +3,81 @@ import type { CollectionConfig } from '../../../../packages/payload/src/collecti export const relationshipFieldsSlug = 'relationship-fields' const RelationshipFields: CollectionConfig = { - slug: relationshipFieldsSlug, fields: [ + { + name: 'text', + type: 'text', + }, { name: 'relationship', - type: 'relationship', relationTo: ['text-fields', 'array-fields'], required: true, + type: 'relationship', }, { name: 'relationToSelf', - type: 'relationship', relationTo: relationshipFieldsSlug, + type: 'relationship', }, { name: 'relationToSelfSelectOnly', - type: 'relationship', - relationTo: relationshipFieldsSlug, admin: { allowCreate: false, }, + relationTo: relationshipFieldsSlug, + type: 'relationship', }, { name: 'relationWithDynamicDefault', - type: 'relationship', + defaultValue: ({ user }) => user?.id, relationTo: 'users', - defaultValue: ({ user }) => user.id, + type: 'relationship', }, { name: 'relationHasManyWithDynamicDefault', - type: 'relationship', + defaultValue: ({ user }) => + user + ? { + relationTo: 'users', + value: user.id, + } + : undefined, relationTo: ['users'], - defaultValue: ({ user }) => ({ - relationTo: 'users', - value: user.id, - }), + type: 'relationship', }, { name: 'relationshipWithMin', - type: 'relationship', - relationTo: 'text-fields', hasMany: true, minRows: 2, + relationTo: 'text-fields', + type: 'relationship', }, { name: 'relationshipWithMax', - type: 'relationship', - relationTo: 'text-fields', hasMany: true, maxRows: 2, + relationTo: 'text-fields', + type: 'relationship', }, { name: 'relationshipHasMany', - type: 'relationship', - relationTo: 'text-fields', hasMany: true, + relationTo: 'text-fields', + type: 'relationship', + }, + { + name: 'array', + fields: [ + { + name: 'relationship', + relationTo: 'text-fields', + type: 'relationship', + }, + ], + type: 'array', }, ], + slug: relationshipFieldsSlug, } export default RelationshipFields diff --git a/test/fields/config.ts b/test/fields/config.ts index 7c58f78cb2..08283323b2 100644 --- a/test/fields/config.ts +++ b/test/fields/config.ts @@ -44,18 +44,18 @@ export default buildConfigWithDefaults({ collections: [ LexicalFields, { - slug: 'users', - auth: true, admin: { useAsTitle: 'email', }, + auth: true, fields: [ { name: 'canViewConditionalField', - type: 'checkbox', defaultValue: true, + type: 'checkbox', }, ], + slug: 'users', }, ArrayFields, BlockFields, @@ -81,8 +81,8 @@ export default buildConfigWithDefaults({ ], localization: { defaultLocale: 'en', - locales: ['en', 'es'], fallback: true, + locales: ['en', 'es'], }, onInit: async (payload) => { await payload.create({ diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 4fbb6817f2..b6441956bb 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -21,6 +21,7 @@ import { } from './collections/Group' import { defaultNumber, numberDoc } from './collections/Number' import { pointDoc } from './collections/Point' +import { relationshipFieldsSlug } from './collections/Relationship' import { tabsDoc } from './collections/Tabs' import { localizedTextValue, @@ -73,6 +74,121 @@ describe('Fields', () => { }) }) + describe('relationship', () => { + let textDoc + let otherTextDoc + let parent + let child + let grandChild + let relationshipInArray + const textDocText = 'text document' + const otherTextDocText = 'alt text' + const relationshipText = 'relationship text' + + beforeAll(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 } + parent = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationship, + text: relationshipText, + }, + }) + + child = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationToSelf: parent.id, + relationship, + text: relationshipText, + }, + }) + + grandChild = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationToSelf: child.id, + relationship, + text: relationshipText, + }, + }) + + selfReferencing = await payload.create({ + collection: relationshipFieldsSlug, + data: { + relationship, + text: relationshipText, + }, + }) + + relationshipInArray = await payload.create({ + collection: relationshipFieldsSlug, + data: { + array: [ + { + relationship: otherTextDoc.id, + }, + ], + relationship, + }, + }) + }) + + it('should query parent self-reference', async () => { + const childResult = await payload.find({ + collection: relationshipFieldsSlug, + where: { + relationToSelf: { equals: parent.id }, + }, + }) + + const grandChildResult = await payload.find({ + collection: relationshipFieldsSlug, + where: { + relationToSelf: { equals: child.id }, + }, + }) + + const anyChildren = await payload.find({ + collection: relationshipFieldsSlug, + }) + const allChildren = await payload.find({ + collection: relationshipFieldsSlug, + where: { + 'relationToSelf.text': { equals: relationshipText }, + }, + }) + + expect(childResult.docs[0].id).toStrictEqual(child.id) + expect(grandChildResult.docs[0].id).toStrictEqual(grandChild.id) + expect(allChildren.docs).toHaveLength(2) + }) + + it('should query relationship inside array', async () => { + const result = await payload.find({ + collection: relationshipFieldsSlug, + where: { + 'array.relationship.text': { equals: otherTextDocText }, + }, + }) + + expect(result.docs).toHaveLength(1) + expect(result.docs[0]).toMatchObject(relationshipInArray) + }) + }) + describe('timestamps', () => { const tenMinutesAgo = new Date(Date.now() - 1000 * 60 * 10) let doc From b0083b7c07e09b0c51a32fb4265b92d5b44dcda2 Mon Sep 17 00:00:00 2001 From: Dan Ribbens Date: Sat, 14 Oct 2023 15:32:06 -0400 Subject: [PATCH 3/5] test: fix missing variable --- test/fields/int.spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index b6441956bb..6e6823f0dc 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -77,6 +77,7 @@ describe('Fields', () => { describe('relationship', () => { let textDoc let otherTextDoc + let selfReferencing let parent let child let grandChild From 2c8fbf1be3598a34b1ecb3ff49acffe14d04b8a4 Mon Sep 17 00:00:00 2001 From: James Date: Sun, 15 Oct 2023 11:08:16 -0400 Subject: [PATCH 4/5] chore: adds specificity to tests --- packages/db-postgres/src/queries/getTableColumnFromPath.ts | 1 - test/fields/e2e.spec.ts | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/db-postgres/src/queries/getTableColumnFromPath.ts b/packages/db-postgres/src/queries/getTableColumnFromPath.ts index abdc43247a..13f37c8a48 100644 --- a/packages/db-postgres/src/queries/getTableColumnFromPath.ts +++ b/packages/db-postgres/src/queries/getTableColumnFromPath.ts @@ -305,7 +305,6 @@ export const getTableColumnFromPath = ({ let relationshipFields const relationTableName = `${rootTableName}_rels` const newCollectionPath = pathSegments.slice(1).join('.') - // missing FROM-clause entry for table "relationship_fields_array" const aliasRelationshipTableName = uuid() const aliasRelationshipTable = alias( adapter.tables[relationTableName], diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 81c75aea84..0e35a9e0de 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -1093,7 +1093,7 @@ describe('fields', () => { .locator('#field-relationship .relationship-add-new__relation-button--text-fields') .click() - const textField = page.locator('#field-text') + const textField = page.locator('.drawer__content #field-text') const textValue = 'hello' await textField.fill(textValue) @@ -1217,7 +1217,7 @@ describe('fields', () => { .locator('#field-relationship .relationship-add-new__relation-button--text-fields') .click() - await page.locator('#field-text').fill('something') + await page.locator('.drawer__content #field-text').fill('something') await page.locator('[id^=doc-drawer_text-fields_1_] #action-save').click() await expect(page.locator('.Toastify')).toContainText('successfully') @@ -1290,7 +1290,7 @@ describe('fields', () => { await page.getByRole('button', { name: 'Edit Seeded text document' }).click() // Fill 'text' field of 'Seeded text document' - await page.locator('#field-text').fill('some updated text value') + await page.locator('.drawer__content #field-text').fill('some updated text value') // Save drawer (not parent page) with hotkey await saveDocHotkeyAndAssert(page) From 27589482dd20377916ab7ad67af0b4a46df0b92a Mon Sep 17 00:00:00 2001 From: Elliot DeNolf Date: Sun, 15 Oct 2023 14:12:02 -0400 Subject: [PATCH 5/5] chore(release): @payloadcms/db-postgres/0.1.7 --- packages/db-postgres/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/db-postgres/package.json b/packages/db-postgres/package.json index b10c56e261..5d60748fb2 100644 --- a/packages/db-postgres/package.json +++ b/packages/db-postgres/package.json @@ -1,6 +1,6 @@ { "name": "@payloadcms/db-postgres", - "version": "0.1.6", + "version": "0.1.7", "description": "The officially supported Postgres database adapter for Payload", "repository": "https://github.com/payloadcms/payload", "license": "MIT",