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:

### 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:
@@ -220,6 +220,37 @@ export const getTableColumnFromPath = ({
|
|||||||
let result: TableColumn
|
let result: TableColumn
|
||||||
const blockConstraints = []
|
const blockConstraints = []
|
||||||
const blockSelectFields = {}
|
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 {
|
try {
|
||||||
result = getTableColumnFromPath({
|
result = getTableColumnFromPath({
|
||||||
adapter,
|
adapter,
|
||||||
@@ -227,7 +258,7 @@ export const getTableColumnFromPath = ({
|
|||||||
constraintPath,
|
constraintPath,
|
||||||
constraints: blockConstraints,
|
constraints: blockConstraints,
|
||||||
fields: block.flattenedFields,
|
fields: block.flattenedFields,
|
||||||
joins,
|
joins: newJoins,
|
||||||
locale,
|
locale,
|
||||||
parentIsLocalized: parentIsLocalized || field.localized,
|
parentIsLocalized: parentIsLocalized || field.localized,
|
||||||
pathSegments: pathSegments.slice(1),
|
pathSegments: pathSegments.slice(1),
|
||||||
@@ -246,30 +277,14 @@ export const getTableColumnFromPath = ({
|
|||||||
blockTableColumn = result
|
blockTableColumn = result
|
||||||
constraints = constraints.concat(blockConstraints)
|
constraints = constraints.concat(blockConstraints)
|
||||||
selectFields = { ...selectFields, ...blockSelectFields }
|
selectFields = { ...selectFields, ...blockSelectFields }
|
||||||
if (isFieldLocalized && adapter.payload.config.localization) {
|
|
||||||
const conditions = [
|
|
||||||
eq(
|
|
||||||
(aliasTable || adapter.tables[tableName]).id,
|
|
||||||
adapter.tables[newTableName]._parentID,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
if (locale !== 'all') {
|
const previousLength = joins.length
|
||||||
conditions.push(eq(adapter.tables[newTableName]._locale, locale))
|
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
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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) => {
|
onInit: async (payload) => {
|
||||||
await payload.create({
|
await payload.create({
|
||||||
|
|||||||
@@ -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', () => {
|
describe('Nested Querying Separate Collections', () => {
|
||||||
let director: Director
|
let director: Director
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ export interface Config {
|
|||||||
'deep-nested': DeepNested;
|
'deep-nested': DeepNested;
|
||||||
relations: Relation1;
|
relations: Relation1;
|
||||||
items: Item;
|
items: Item;
|
||||||
|
blocks: Block;
|
||||||
users: User;
|
users: User;
|
||||||
'payload-locked-documents': PayloadLockedDocument;
|
'payload-locked-documents': PayloadLockedDocument;
|
||||||
'payload-preferences': PayloadPreference;
|
'payload-preferences': PayloadPreference;
|
||||||
@@ -117,6 +118,7 @@ export interface Config {
|
|||||||
'deep-nested': DeepNestedSelect<false> | DeepNestedSelect<true>;
|
'deep-nested': DeepNestedSelect<false> | DeepNestedSelect<true>;
|
||||||
relations: RelationsSelect<false> | RelationsSelect<true>;
|
relations: RelationsSelect<false> | RelationsSelect<true>;
|
||||||
items: ItemsSelect<false> | ItemsSelect<true>;
|
items: ItemsSelect<false> | ItemsSelect<true>;
|
||||||
|
blocks: BlocksSelect<false> | BlocksSelect<true>;
|
||||||
users: UsersSelect<false> | UsersSelect<true>;
|
users: UsersSelect<false> | UsersSelect<true>;
|
||||||
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
'payload-locked-documents': PayloadLockedDocumentsSelect<false> | PayloadLockedDocumentsSelect<true>;
|
||||||
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
'payload-preferences': PayloadPreferencesSelect<false> | PayloadPreferencesSelect<true>;
|
||||||
@@ -464,6 +466,23 @@ export interface Item {
|
|||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "payload-locked-documents".
|
* via the `definition` "payload-locked-documents".
|
||||||
@@ -551,6 +570,10 @@ export interface PayloadLockedDocument {
|
|||||||
relationTo: 'items';
|
relationTo: 'items';
|
||||||
value: string | Item;
|
value: string | Item;
|
||||||
} | null)
|
} | null)
|
||||||
|
| ({
|
||||||
|
relationTo: 'blocks';
|
||||||
|
value: string | Block;
|
||||||
|
} | null)
|
||||||
| ({
|
| ({
|
||||||
relationTo: 'users';
|
relationTo: 'users';
|
||||||
value: string | User;
|
value: string | User;
|
||||||
@@ -840,6 +863,25 @@ export interface ItemsSelect<T extends boolean = true> {
|
|||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: 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
|
* This interface was referenced by `Config`'s JSON-Schema
|
||||||
* via the `definition` "users_select".
|
* via the `definition` "users_select".
|
||||||
|
|||||||
Reference in New Issue
Block a user