feat: allow findDistinct on fields nested to relationships and on virtual fields (#14026)

This adds support for using `findDistinct`
https://github.com/payloadcms/payload/pull/13102 on fields:
* Nested to a relationship, for example `category.title`
* Virtual fields that are linked to relationships, for example
`categoryTitle`


```tsx
const Category: CollectionConfig = {
  slug: 'categories',
  fields: [
    {
      name: 'title',
      type: 'text',
    },
  ],
}

const Posts: CollectionConfig = {
  slug: 'posts',
  fields: [
    {
      name: 'category',
      type: 'relationship',
      relationTo: 'categories',
    },
    {
      name: 'categoryTitle',
      type: 'text',
      virtual: 'category.title',
    },
  ],
}

// Supported now
const relationResult = await payload.findDistinct({ collection: 'posts', field: 'category.title' })
// Supported now
const virtualResult = await payload.findDistinct({ collection: 'posts', field: 'categoryTitle' })
```
This commit is contained in:
Sasha
2025-10-02 05:36:30 +03:00
committed by GitHub
parent 95bdffd11f
commit 9d6cae0445
6 changed files with 271 additions and 36 deletions

View File

@@ -126,6 +126,11 @@ export const getConfig: () => Partial<Config> = () => ({
relationTo: 'categories',
name: 'category',
},
{
type: 'text',
name: 'categoryTitle',
virtual: 'category.title',
},
{
type: 'relationship',
relationTo: 'categories',

View File

@@ -1102,6 +1102,94 @@ describe('database', () => {
expect(result.values.some((v) => v.categoryPolyMany === null)).toBe(true)
})
it('should find distinct values with field nested to a relationship', 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' },
})
await payload.create({ collection: 'posts', data: { title: 'post', category: category_1 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
const res = await payload.findDistinct({
collection: 'posts',
field: 'category.title',
})
expect(res.values).toEqual([
{
'category.title': 'category_1',
},
{
'category.title': 'category_2',
},
{
'category.title': 'category_3',
},
])
})
it('should find distinct values with virtual field linked to a relationship', 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' },
})
await payload.create({ collection: 'posts', data: { title: 'post', category: category_1 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_2 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
await payload.create({ collection: 'posts', data: { title: 'post', category: category_3 } })
const res = await payload.findDistinct({
collection: 'posts',
field: 'categoryTitle',
})
expect(res.values).toEqual([
{
categoryTitle: 'category_1',
},
{
categoryTitle: 'category_2',
},
{
categoryTitle: 'category_3',
},
])
})
describe('Compound Indexes', () => {
beforeEach(async () => {
await payload.delete({ collection: 'compound-indexes', where: {} })

View File

@@ -197,6 +197,7 @@ export interface Post {
id: string;
title: string;
category?: (string | null) | Category;
categoryTitle?: string | null;
categories?: (string | Category)[] | null;
categoryPoly?: {
relationTo: 'categories';
@@ -210,22 +211,28 @@ export interface Post {
| null;
categoryCustomID?: (number | null) | CategoriesCustomId;
polymorphicRelations?:
| ({
relationTo: 'categories';
value: string | Category;
} | {
relationTo: 'simple';
value: string | Simple;
})[]
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'simple';
value: string | Simple;
}
)[]
| null;
localizedPolymorphicRelations?:
| ({
relationTo: 'categories';
value: string | Category;
} | {
relationTo: 'simple';
value: string | Simple;
})[]
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'simple';
value: string | Simple;
}
)[]
| null;
localized?: string | null;
text?: string | null;
@@ -245,6 +252,23 @@ export interface Post {
blockType: 'block-third';
}[]
| null;
testNestedGroup?: {
nestedLocalizedPolymorphicRelation?:
| (
| {
relationTo: 'categories';
value: string | Category;
}
| {
relationTo: 'simple';
value: string | Simple;
}
)[]
| null;
nestedLocalizedText?: string | null;
nestedText1?: string | null;
nestedText2?: string | null;
};
D1?: {
D2?: {
D3?: {
@@ -252,20 +276,6 @@ export interface Post {
};
};
};
testNestedGroup?: {
nestedLocalizedPolymorphicRelation?:
| ({
relationTo: 'categories';
value: string | Category;
} | {
relationTo: 'simple';
value: string | Simple;
})[]
| null;
nestedLocalizedText?: string | null;
nestedText1?: string | null;
nestedText2?: string | null;
};
hasTransaction?: boolean | null;
throwAfterChange?: boolean | null;
arrayWithIDs?:
@@ -868,6 +878,7 @@ export interface CategoriesCustomIdSelect<T extends boolean = true> {
export interface PostsSelect<T extends boolean = true> {
title?: T;
category?: T;
categoryTitle?: T;
categories?: T;
categoryPoly?: T;
categoryPolyMany?: T;
@@ -898,6 +909,14 @@ export interface PostsSelect<T extends boolean = true> {
blockName?: T;
};
};
testNestedGroup?:
| T
| {
nestedLocalizedPolymorphicRelation?: T;
nestedLocalizedText?: T;
nestedText1?: T;
nestedText2?: T;
};
D1?:
| T
| {
@@ -1455,6 +1474,6 @@ export interface Auth {
declare module 'payload' {
// @ts-ignore
// @ts-ignore
export interface GeneratedTypes extends Config {}
}
}