fix(db-postgres): joins with hasMany: true relationships nested to an array (#12980)
Fixes https://github.com/payloadcms/payload/issues/12679
This commit is contained in:
@@ -53,6 +53,7 @@ type Args = {
|
||||
fields: FlattenedField[]
|
||||
joins: BuildQueryJoinAliases
|
||||
locale?: string
|
||||
parentAliasTable?: PgTableWithColumns<any> | SQLiteTableWithColumns<any>
|
||||
parentIsLocalized: boolean
|
||||
pathSegments: string[]
|
||||
rootTableName?: string
|
||||
@@ -83,6 +84,7 @@ export const getTableColumnFromPath = ({
|
||||
fields,
|
||||
joins,
|
||||
locale: incomingLocale,
|
||||
parentAliasTable,
|
||||
parentIsLocalized,
|
||||
pathSegments: incomingSegments,
|
||||
rootTableName: incomingRootTableName,
|
||||
@@ -162,6 +164,7 @@ export const getTableColumnFromPath = ({
|
||||
table: adapter.tables[newTableName],
|
||||
})
|
||||
}
|
||||
|
||||
return getTableColumnFromPath({
|
||||
adapter,
|
||||
collectionPath,
|
||||
@@ -170,6 +173,7 @@ export const getTableColumnFromPath = ({
|
||||
fields: field.flattenedFields,
|
||||
joins,
|
||||
locale,
|
||||
parentAliasTable: aliasTable,
|
||||
parentIsLocalized: parentIsLocalized || field.localized,
|
||||
pathSegments: pathSegments.slice(1),
|
||||
rootTableName,
|
||||
@@ -548,7 +552,10 @@ export const getTableColumnFromPath = ({
|
||||
// Join in the relationships table
|
||||
if (locale && isFieldLocalized && adapter.payload.config.localization) {
|
||||
const conditions = [
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(
|
||||
(parentAliasTable || aliasTable || adapter.tables[rootTableName]).id,
|
||||
aliasRelationshipTable.parent,
|
||||
),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
]
|
||||
|
||||
@@ -566,7 +573,10 @@ export const getTableColumnFromPath = ({
|
||||
// Join in the relationships table
|
||||
addJoinTable({
|
||||
condition: and(
|
||||
eq((aliasTable || adapter.tables[rootTableName]).id, aliasRelationshipTable.parent),
|
||||
eq(
|
||||
(parentAliasTable || aliasTable || adapter.tables[rootTableName]).id,
|
||||
aliasRelationshipTable.parent,
|
||||
),
|
||||
like(aliasRelationshipTable.path, `${constraintPath}${field.name}`),
|
||||
),
|
||||
joins,
|
||||
|
||||
@@ -111,6 +111,12 @@ export const Categories: CollectionConfig = {
|
||||
collection: 'posts',
|
||||
on: 'array.category',
|
||||
},
|
||||
{
|
||||
name: 'arrayHasManyPosts',
|
||||
type: 'join',
|
||||
collection: 'posts',
|
||||
on: 'arrayHasMany.category',
|
||||
},
|
||||
{
|
||||
name: 'localizedArrayPosts',
|
||||
type: 'join',
|
||||
|
||||
@@ -114,6 +114,18 @@ export const Posts: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'arrayHasMany',
|
||||
type: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'category',
|
||||
type: 'relationship',
|
||||
hasMany: true,
|
||||
relationTo: categoriesSlug,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'localizedArray',
|
||||
type: 'array',
|
||||
|
||||
@@ -115,6 +115,7 @@ describe('Joins Field', () => {
|
||||
camelCaseCategory: category.id,
|
||||
},
|
||||
array: [{ category: category.id }],
|
||||
arrayHasMany: [{ category: [category.id] }],
|
||||
localizedArray: [{ category: category.id }],
|
||||
blocks: [{ blockType: 'block', category: category.id }],
|
||||
})
|
||||
@@ -247,6 +248,16 @@ describe('Joins Field', () => {
|
||||
expect(categoryWithPosts.arrayPosts.docs).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should populate joins with array hasMany relationships', async () => {
|
||||
const categoryWithPosts = await payload.findByID({
|
||||
id: category.id,
|
||||
collection: categoriesSlug,
|
||||
})
|
||||
|
||||
expect(categoryWithPosts.arrayHasManyPosts.docs).toBeDefined()
|
||||
expect(categoryWithPosts.arrayHasManyPosts.docs).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should populate joins with localized array relationships', async () => {
|
||||
const categoryWithPosts = await payload.findByID({
|
||||
id: category.id,
|
||||
|
||||
@@ -109,6 +109,7 @@ export interface Config {
|
||||
'group.relatedPosts': 'posts';
|
||||
'group.camelCasePosts': 'posts';
|
||||
arrayPosts: 'posts';
|
||||
arrayHasManyPosts: 'posts';
|
||||
localizedArrayPosts: 'posts';
|
||||
blocksPosts: 'posts';
|
||||
polymorphic: 'posts';
|
||||
@@ -240,6 +241,13 @@ export interface User {
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
sessions?:
|
||||
| {
|
||||
id: string;
|
||||
createdAt?: string | null;
|
||||
expiresAt: string;
|
||||
}[]
|
||||
| null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
@@ -312,6 +320,12 @@ export interface Post {
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
arrayHasMany?:
|
||||
| {
|
||||
category?: (string | Category)[] | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
localizedArray?:
|
||||
| {
|
||||
category?: (string | null) | Category;
|
||||
@@ -405,6 +419,11 @@ export interface Category {
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
arrayHasManyPosts?: {
|
||||
docs?: (string | Post)[];
|
||||
hasNextPage?: boolean;
|
||||
totalDocs?: number;
|
||||
};
|
||||
localizedArrayPosts?: {
|
||||
docs?: (string | Post)[];
|
||||
hasNextPage?: boolean;
|
||||
@@ -971,6 +990,13 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
sessions?:
|
||||
| T
|
||||
| {
|
||||
id?: T;
|
||||
createdAt?: T;
|
||||
expiresAt?: T;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
@@ -1002,6 +1028,12 @@ export interface PostsSelect<T extends boolean = true> {
|
||||
category?: T;
|
||||
id?: T;
|
||||
};
|
||||
arrayHasMany?:
|
||||
| T
|
||||
| {
|
||||
category?: T;
|
||||
id?: T;
|
||||
};
|
||||
localizedArray?:
|
||||
| T
|
||||
| {
|
||||
@@ -1049,6 +1081,7 @@ export interface CategoriesSelect<T extends boolean = true> {
|
||||
camelCasePosts?: T;
|
||||
};
|
||||
arrayPosts?: T;
|
||||
arrayHasManyPosts?: T;
|
||||
localizedArrayPosts?: T;
|
||||
blocksPosts?: T;
|
||||
polymorphic?: T;
|
||||
|
||||
@@ -240,6 +240,18 @@ export default buildConfigWithDefaults({
|
||||
type: 'relationship',
|
||||
relationTo: 'directors',
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'director',
|
||||
type: 'relationship',
|
||||
relationTo: 'directors',
|
||||
hasMany: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1151,6 +1151,29 @@ describe('Relationships', () => {
|
||||
expect(result.docs[0]!.id).toBe(doc.id)
|
||||
})
|
||||
|
||||
it('should allow querying hasMany in array', async () => {
|
||||
const director = await payload.create({
|
||||
collection: 'directors',
|
||||
data: { name: 'Test Director1337' },
|
||||
})
|
||||
const movie = await payload.create({
|
||||
collection: 'movies',
|
||||
data: { array: [{ director: [director.id] }] },
|
||||
})
|
||||
const res = await payload.find({
|
||||
collection: 'movies',
|
||||
where: { 'array.director': { equals: director.id } },
|
||||
})
|
||||
expect(res.docs).toHaveLength(1)
|
||||
expect(res.docs[0].id).toBe(movie.id)
|
||||
const res2 = await payload.find({
|
||||
collection: 'movies',
|
||||
where: { 'array.director.name': { equals: 'Test Director1337' } },
|
||||
})
|
||||
expect(res2.docs).toHaveLength(1)
|
||||
expect(res2.docs[0].id).toBe(movie.id)
|
||||
})
|
||||
|
||||
describe('Nested Querying Separate Collections', () => {
|
||||
let director: Director
|
||||
|
||||
|
||||
@@ -268,6 +268,12 @@ export interface Movie {
|
||||
name?: string | null;
|
||||
select?: ('a' | 'b' | 'c')[] | null;
|
||||
director?: (string | null) | Director;
|
||||
array?:
|
||||
| {
|
||||
director?: (string | Director)[] | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
_status?: ('draft' | 'published') | null;
|
||||
@@ -313,6 +319,13 @@ export interface User {
|
||||
hash?: string | null;
|
||||
loginAttempts?: number | null;
|
||||
lockUntil?: string | null;
|
||||
sessions?:
|
||||
| {
|
||||
id: string;
|
||||
createdAt?: string | null;
|
||||
expiresAt: string;
|
||||
}[]
|
||||
| null;
|
||||
password?: string | null;
|
||||
}
|
||||
/**
|
||||
@@ -733,6 +746,12 @@ export interface MoviesSelect<T extends boolean = true> {
|
||||
name?: T;
|
||||
select?: T;
|
||||
director?: T;
|
||||
array?:
|
||||
| T
|
||||
| {
|
||||
director?: T;
|
||||
id?: T;
|
||||
};
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
_status?: T;
|
||||
@@ -906,6 +925,13 @@ export interface UsersSelect<T extends boolean = true> {
|
||||
hash?: T;
|
||||
loginAttempts?: T;
|
||||
lockUntil?: T;
|
||||
sessions?:
|
||||
| T
|
||||
| {
|
||||
id?: T;
|
||||
createdAt?: T;
|
||||
expiresAt?: T;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
|
||||
Reference in New Issue
Block a user