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
```
This commit is contained in:
Sasha
2025-02-18 21:51:17 +02:00
committed by GitHub
parent 06debf5e14
commit 88548fcbe6
4 changed files with 131 additions and 23 deletions

View File

@@ -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
})

View File

@@ -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({

View File

@@ -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

View File

@@ -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<false> | DeepNestedSelect<true>;
relations: RelationsSelect<false> | RelationsSelect<true>;
items: ItemsSelect<false> | ItemsSelect<true>;
blocks: BlocksSelect<false> | BlocksSelect<true>;
users: UsersSelect<false> | UsersSelect<true>;
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
@@ -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<T extends boolean = true> {
updatedAt?: T;
createdAt?: T;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "blocks_select".
*/
export interface BlocksSelect<T extends boolean = true> {
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".