fix: findDistinct with polymorphic relationships (#13875)

Fixes `findDistinct` with polymorphic relationships and also fixes a bug
from https://github.com/payloadcms/payload/pull/13840 when
`findDistinct` didn't work properly for `hasMany` relationships in
mongodb if `sort` is the same as `field`

---------

Co-authored-by: Patrik Kozak <35232443+PatrikKozak@users.noreply.github.com>
This commit is contained in:
Sasha
2025-09-22 22:23:17 +03:00
committed by GitHub
parent 82d98ab375
commit e99e054d7c
6 changed files with 254 additions and 7 deletions

View File

@@ -132,6 +132,17 @@ export const getConfig: () => Partial<Config> = () => ({
hasMany: true,
name: 'categories',
},
{
type: 'relationship',
relationTo: ['categories'],
name: 'categoryPoly',
},
{
type: 'relationship',
relationTo: ['categories'],
hasMany: true,
name: 'categoryPolyMany',
},
{
type: 'relationship',
relationTo: 'categories-custom-id',

View File

@@ -923,6 +923,183 @@ describe('database', () => {
expect(fromRes.categories.title).toBe(title)
expect(fromRes.categories.id).toBe(id)
}
// Non-consistent sorting by ID
// eslint-disable-next-line jest/no-conditional-in-test
if (process.env.PAYLOAD_DATABASE?.includes('uuid')) {
return
}
const resultDepth1NoSort = await payload.findDistinct({
depth: 1,
collection: 'posts',
field: 'categories',
})
for (let i = 0; i < resultDepth1NoSort.values.length; i++) {
const fromRes = resultDepth1NoSort.values[i] as any
const id = categoriesIDS[i].categories as any
const title = categories[i]?.title
expect(fromRes.categories.title).toBe(title)
expect(fromRes.categories.id).toBe(id)
}
})
it('should populate distinct relationships of polymorphic when depth>0', async () => {
await payload.delete({ collection: 'posts', where: {} })
await payload.delete({ collection: 'categories', where: {} })
const category_1 = await payload.create({
collection: 'categories',
data: { title: 'category_1' },
})
const category_2 = await payload.create({
collection: 'categories',
data: { title: 'category_2' },
})
const category_3 = await payload.create({
collection: 'categories',
data: { title: 'category_3' },
})
const post_1 = await payload.create({
collection: 'posts',
data: { title: 'post_1', categoryPoly: { relationTo: 'categories', value: category_1.id } },
})
const post_2 = await payload.create({
collection: 'posts',
data: { title: 'post_2', categoryPoly: { relationTo: 'categories', value: category_1.id } },
})
const post_3 = await payload.create({
collection: 'posts',
data: { title: 'post_3', categoryPoly: { relationTo: 'categories', value: category_2.id } },
})
const post_4 = await payload.create({
collection: 'posts',
data: { title: 'post_4', categoryPoly: { relationTo: 'categories', value: category_3.id } },
})
const post_5 = await payload.create({
collection: 'posts',
data: { title: 'post_5', categoryPoly: { relationTo: 'categories', value: category_3.id } },
})
const result = await payload.findDistinct({
depth: 0,
collection: 'posts',
field: 'categoryPoly',
})
expect(result.values).toHaveLength(3)
expect(
result.values.some(
(v) =>
v.categoryPoly?.relationTo === 'categories' && v.categoryPoly.value === category_1.id,
),
).toBe(true)
expect(
result.values.some(
(v) =>
v.categoryPoly?.relationTo === 'categories' && v.categoryPoly.value === category_2.id,
),
).toBe(true)
expect(
result.values.some(
(v) =>
v.categoryPoly?.relationTo === 'categories' && v.categoryPoly.value === category_3.id,
),
).toBe(true)
})
it('should populate distinct relationships of hasMany polymorphic when depth>0', async () => {
await payload.delete({ collection: 'posts', where: {} })
await payload.delete({ collection: 'categories', where: {} })
const category_1 = await payload.create({
collection: 'categories',
data: { title: 'category_1' },
})
const category_2 = await payload.create({
collection: 'categories',
data: { title: 'category_2' },
})
const category_3 = await payload.create({
collection: 'categories',
data: { title: 'category_3' },
})
const post_1 = await payload.create({
collection: 'posts',
data: {
title: 'post_1',
categoryPolyMany: [{ relationTo: 'categories', value: category_1.id }],
},
})
const post_2 = await payload.create({
collection: 'posts',
data: {
title: 'post_2',
categoryPolyMany: [{ relationTo: 'categories', value: category_1.id }],
},
})
const post_3 = await payload.create({
collection: 'posts',
data: {
title: 'post_3',
categoryPolyMany: [{ relationTo: 'categories', value: category_2.id }],
},
})
const post_4 = await payload.create({
collection: 'posts',
data: {
title: 'post_4',
categoryPolyMany: [{ relationTo: 'categories', value: category_3.id }],
},
})
const post_5 = await payload.create({
collection: 'posts',
data: {
title: 'post_5',
categoryPolyMany: [{ relationTo: 'categories', value: category_3.id }],
},
})
const post_6 = await payload.create({
collection: 'posts',
data: {
title: 'post_6',
categoryPolyMany: null,
},
})
const result = await payload.findDistinct({
depth: 0,
collection: 'posts',
field: 'categoryPolyMany',
})
expect(result.values).toHaveLength(4)
expect(
result.values.some(
(v) =>
v.categoryPolyMany?.relationTo === 'categories' &&
v.categoryPolyMany.value === category_1.id,
),
).toBe(true)
expect(
result.values.some(
(v) =>
v.categoryPolyMany?.relationTo === 'categories' &&
v.categoryPolyMany.value === category_2.id,
),
).toBe(true)
expect(
result.values.some(
(v) =>
v.categoryPolyMany?.relationTo === 'categories' &&
v.categoryPolyMany.value === category_3.id,
),
).toBe(true)
expect(result.values.some((v) => v.categoryPolyMany === null)).toBe(true)
})
describe('Compound Indexes', () => {

View File

@@ -198,6 +198,16 @@ export interface Post {
title: string;
category?: (string | null) | Category;
categories?: (string | Category)[] | null;
categoryPoly?: {
relationTo: 'categories';
value: string | Category;
} | null;
categoryPolyMany?:
| {
relationTo: 'categories';
value: string | Category;
}[]
| null;
categoryCustomID?: (number | null) | CategoriesCustomId;
localized?: string | null;
text?: string | null;
@@ -827,6 +837,8 @@ export interface PostsSelect<T extends boolean = true> {
title?: T;
category?: T;
categories?: T;
categoryPoly?: T;
categoryPolyMany?: T;
categoryCustomID?: T;
localized?: T;
text?: T;