From 88548fcbe635159de52047bb8ff1040fce2ce587 Mon Sep 17 00:00:00 2001 From: Sasha <64744993+r1tsuu@users.noreply.github.com> Date: Tue, 18 Feb 2025 21:51:17 +0200 Subject: [PATCH] fix(db-postgres): querying other collections via relationships inside blocks (#11255) ### What? Previously, in postgres query like: ```ts const result = await payload.find({ collection: 'blocks', where: { 'blocks.director.name': { equals: 'Test Director' } }, }) ``` where `blocks` is a blocks field, `director` is a relationship field and `name` is a text field inside `directors`, failed with: ![image](https://github.com/user-attachments/assets/f4b62b69-bd17-4ef0-9f0c-08057e9f2d57) ### Why? The generated query before was a bit wrong. Before: ```sql select distinct "blocks"."id", "blocks"."created_at", "blocks"."created_at" from "blocks" left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id" left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id" where "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director' order by "blocks"."created_at" desc limit 10 ``` Notice `left join directors` _before_ join of `blocks_blocks_some`. `blocks_blocks_some` doesn't exist yet, this PR changes so now we generate ```sql select distinct "blocks"."id", "blocks"."created_at", "blocks"."created_at" from "blocks" left join "blocks_blocks_some" on "blocks"."id" = "blocks_blocks_some"."_parent_id" left join "directors" "a5ad426a_eda4_4067_af7e_5b294d7f0968" on "a5ad426a_eda4_4067_af7e_5b294d7f0968"."id" = "blocks_blocks_some"."director_id" where "a5ad426a_eda4_4067_af7e_5b294d7f0968"."name" = 'Test Director' order by "blocks"."created_at" desc limit 10 ``` --- .../src/queries/getTableColumnFromPath.ts | 61 ++++++++++++------- test/relationships/config.ts | 21 +++++++ test/relationships/int.spec.ts | 30 +++++++++ test/relationships/payload-types.ts | 42 +++++++++++++ 4 files changed, 131 insertions(+), 23 deletions(-) diff --git a/packages/drizzle/src/queries/getTableColumnFromPath.ts b/packages/drizzle/src/queries/getTableColumnFromPath.ts index 3e9dc92f7..66de4ca7f 100644 --- a/packages/drizzle/src/queries/getTableColumnFromPath.ts +++ b/packages/drizzle/src/queries/getTableColumnFromPath.ts @@ -220,6 +220,37 @@ export const getTableColumnFromPath = ({ let result: TableColumn const blockConstraints = [] const blockSelectFields = {} + + let blockJoin: BuildQueryJoinAliases[0] + if (isFieldLocalized && adapter.payload.config.localization) { + const conditions = [ + eq( + (aliasTable || adapter.tables[tableName]).id, + adapter.tables[newTableName]._parentID, + ), + ] + + if (locale !== 'all') { + conditions.push(eq(adapter.tables[newTableName]._locale, locale)) + } + + blockJoin = { + condition: and(...conditions), + table: adapter.tables[newTableName], + } + } else { + blockJoin = { + condition: eq( + (aliasTable || adapter.tables[tableName]).id, + adapter.tables[newTableName]._parentID, + ), + table: adapter.tables[newTableName], + } + } + + // Create a new reference for nested joins + const newJoins = [...joins] + try { result = getTableColumnFromPath({ adapter, @@ -227,7 +258,7 @@ export const getTableColumnFromPath = ({ constraintPath, constraints: blockConstraints, fields: block.flattenedFields, - joins, + joins: newJoins, locale, parentIsLocalized: parentIsLocalized || field.localized, pathSegments: pathSegments.slice(1), @@ -246,30 +277,14 @@ export const getTableColumnFromPath = ({ blockTableColumn = result constraints = constraints.concat(blockConstraints) selectFields = { ...selectFields, ...blockSelectFields } - if (isFieldLocalized && adapter.payload.config.localization) { - const conditions = [ - eq( - (aliasTable || adapter.tables[tableName]).id, - adapter.tables[newTableName]._parentID, - ), - ] - if (locale !== 'all') { - conditions.push(eq(adapter.tables[newTableName]._locale, locale)) + const previousLength = joins.length + joins.push(blockJoin) + // Append new joins AFTER the block join to prevent errors with missing FROM clause. + if (newJoins.length > previousLength) { + for (let i = previousLength; i < newJoins.length; i++) { + joins.push(newJoins[i]) } - - joins.push({ - condition: and(...conditions), - table: adapter.tables[newTableName], - }) - } else { - joins.push({ - condition: eq( - (aliasTable || adapter.tables[tableName]).id, - adapter.tables[newTableName]._parentID, - ), - table: adapter.tables[newTableName], - }) } return true }) diff --git a/test/relationships/config.ts b/test/relationships/config.ts index 19423afe4..d6eda7fc4 100644 --- a/test/relationships/config.ts +++ b/test/relationships/config.ts @@ -460,6 +460,27 @@ export default buildConfigWithDefaults({ }, ], }, + { + slug: 'blocks', + fields: [ + { + type: 'blocks', + name: 'blocks', + blocks: [ + { + slug: 'some', + fields: [ + { + type: 'relationship', + relationTo: 'directors', + name: 'director', + }, + ], + }, + ], + }, + ], + }, ], onInit: async (payload) => { await payload.create({ diff --git a/test/relationships/int.spec.ts b/test/relationships/int.spec.ts index 7cdccca84..6a5623703 100644 --- a/test/relationships/int.spec.ts +++ b/test/relationships/int.spec.ts @@ -1130,6 +1130,36 @@ describe('Relationships', () => { }) }) + it('should allow querying within block nesting', async () => { + const director = await payload.create({ + collection: 'directors', + data: { name: 'Test Director' }, + }) + + const director_false = await payload.create({ + collection: 'directors', + data: { name: 'False Director' }, + }) + + const doc = await payload.create({ + collection: 'blocks', + data: { blocks: [{ blockType: 'some', director: director.id }] }, + }) + + await payload.create({ + collection: 'blocks', + data: { blocks: [{ blockType: 'some', director: director_false.id }] }, + }) + + const result = await payload.find({ + collection: 'blocks', + where: { 'blocks.director.name': { equals: 'Test Director' } }, + }) + + expect(result.totalDocs).toBe(1) + expect(result.docs[0]!.id).toBe(doc.id) + }) + describe('Nested Querying Separate Collections', () => { let director: Director diff --git a/test/relationships/payload-types.ts b/test/relationships/payload-types.ts index 7d2aabc96..9b4d9698c 100644 --- a/test/relationships/payload-types.ts +++ b/test/relationships/payload-types.ts @@ -86,6 +86,7 @@ export interface Config { 'deep-nested': DeepNested; relations: Relation1; items: Item; + blocks: Block; users: User; 'payload-locked-documents': PayloadLockedDocument; 'payload-preferences': PayloadPreference; @@ -117,6 +118,7 @@ export interface Config { 'deep-nested': DeepNestedSelect | DeepNestedSelect; relations: RelationsSelect | RelationsSelect; items: ItemsSelect | ItemsSelect; + blocks: BlocksSelect | BlocksSelect; users: UsersSelect | UsersSelect; 'payload-locked-documents': PayloadLockedDocumentsSelect | PayloadLockedDocumentsSelect; 'payload-preferences': PayloadPreferencesSelect | PayloadPreferencesSelect; @@ -464,6 +466,23 @@ export interface Item { updatedAt: string; createdAt: string; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "blocks". + */ +export interface Block { + id: string; + blocks?: + | { + director?: (string | null) | Director; + id?: string | null; + blockName?: string | null; + blockType: 'some'; + }[] + | null; + updatedAt: string; + createdAt: string; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "payload-locked-documents". @@ -551,6 +570,10 @@ export interface PayloadLockedDocument { relationTo: 'items'; value: string | Item; } | null) + | ({ + relationTo: 'blocks'; + value: string | Block; + } | null) | ({ relationTo: 'users'; value: string | User; @@ -840,6 +863,25 @@ export interface ItemsSelect { updatedAt?: T; createdAt?: T; } +/** + * This interface was referenced by `Config`'s JSON-Schema + * via the `definition` "blocks_select". + */ +export interface BlocksSelect { + blocks?: + | T + | { + some?: + | T + | { + director?: T; + id?: T; + blockName?: T; + }; + }; + updatedAt?: T; + createdAt?: T; +} /** * This interface was referenced by `Config`'s JSON-Schema * via the `definition` "users_select".