fix(db-postgres): selectDistinct might remove expected rows when querying with nested fields or relations (#12365)

Fixes https://github.com/payloadcms/payload/issues/12263
This was caused by passing not needed columns to the `SELECT DISTINCT`
query, which we execute in case if we have a filter / sort by a nested
field / relationship. Since the only columns that we need to pass to the
`SELECT DISTINCT` query are: ID and field(s) specified in `sort`, we now
filter the `selectFields` variable.
This commit is contained in:
Sasha
2025-05-12 22:34:15 +03:00
committed by GitHub
parent 021932cc8b
commit 8219c046de
4 changed files with 103 additions and 4 deletions

View File

@@ -7,6 +7,7 @@ import type { DrizzleAdapter } from '../types.js'
import buildQuery from '../queries/buildQuery.js'
import { selectDistinct } from '../queries/selectDistinct.js'
import { transform } from '../transform/read/index.js'
import { getNameFromDrizzleTable } from '../utilities/getNameFromDrizzleTable.js'
import { getTransaction } from '../utilities/getTransaction.js'
import { buildFindManyArgs } from './buildFindManyArgs.js'
@@ -75,6 +76,26 @@ export const findMany = async function find({
tableName,
versions,
})
if (orderBy) {
for (const key in selectFields) {
const column = selectFields[key]
if (column.primary) {
continue
}
if (
!orderBy.some(
(col) =>
col.column.name === column.name &&
getNameFromDrizzleTable(col.column.table) === getNameFromDrizzleTable(column.table),
)
) {
delete selectFields[key]
}
}
}
const selectDistinctResult = await selectDistinct({
adapter,
db,

View File

@@ -115,6 +115,16 @@ const DateFields: CollectionConfig = {
},
],
},
{
type: 'array',
name: 'array',
fields: [
{
name: 'date',
type: 'date',
},
],
},
],
}

View File

@@ -600,6 +600,56 @@ describe('Fields', () => {
expect(result.docs[0].id).toEqual(doc.id)
})
// Function to generate random date between start and end dates
function getRandomDate(start: Date, end: Date): string {
const date = new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime()))
return date.toISOString()
}
// Generate sample data
const dataSample = Array.from({ length: 100 }, (_, index) => {
const startDate = new Date('2024-01-01')
const endDate = new Date('2025-12-31')
return {
array: Array.from({ length: 5 }, (_, listIndex) => {
return {
date: getRandomDate(startDate, endDate),
}
}),
...dateDoc,
}
})
it('should query a date field inside an array field', async () => {
await payload.delete({ collection: 'date-fields', where: {} })
for (const doc of dataSample) {
await payload.create({
collection: 'date-fields',
data: doc,
})
}
const res = await payload.find({
collection: 'date-fields',
where: { 'array.date': { greater_than: new Date('2025-06-01').toISOString() } },
})
const filter = (doc: any) =>
doc.array.some((item) => new Date(item.date).getTime() > new Date('2025-06-01').getTime())
expect(res.docs.every(filter)).toBe(true)
expect(dataSample.filter(filter)).toHaveLength(res.totalDocs)
// eslint-disable-next-line jest/no-conditional-in-test
if (res.totalDocs > 10) {
// This is where postgres might fail! selectDistinct actually removed some rows here, because it distincts by:
// not only ID, but also created_at, updated_at, items_date
expect(res.docs).toHaveLength(10)
} else {
expect(res.docs.length).toBeLessThanOrEqual(res.totalDocs)
}
})
})
describe('select', () => {

View File

@@ -929,6 +929,12 @@ export interface DateField {
id?: string | null;
}[]
| null;
array?:
| {
date?: string | null;
id?: string | null;
}[]
| null;
updatedAt: string;
createdAt: string;
}
@@ -1326,10 +1332,16 @@ export interface RelationshipField {
} | null);
relationshipDrawerHasMany?: (string | TextField)[] | null;
relationshipDrawerHasManyPolymorphic?:
| (
| {
relationTo: 'text-fields';
value: string | TextField;
}[]
}
| {
relationTo: 'array-fields';
value: string | ArrayField;
}
)[]
| null;
relationshipDrawerWithAllowCreateFalse?: (string | null) | TextField;
relationshipDrawerWithFilterOptions?: {
@@ -2492,6 +2504,12 @@ export interface DateFieldsSelect<T extends boolean = true> {
dayAndTime_tz?: T;
id?: T;
};
array?:
| T
| {
date?: T;
id?: T;
};
updatedAt?: T;
createdAt?: T;
}