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 buildQuery from '../queries/buildQuery.js'
import { selectDistinct } from '../queries/selectDistinct.js' import { selectDistinct } from '../queries/selectDistinct.js'
import { transform } from '../transform/read/index.js' import { transform } from '../transform/read/index.js'
import { getNameFromDrizzleTable } from '../utilities/getNameFromDrizzleTable.js'
import { getTransaction } from '../utilities/getTransaction.js' import { getTransaction } from '../utilities/getTransaction.js'
import { buildFindManyArgs } from './buildFindManyArgs.js' import { buildFindManyArgs } from './buildFindManyArgs.js'
@@ -75,6 +76,26 @@ export const findMany = async function find({
tableName, tableName,
versions, 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({ const selectDistinctResult = await selectDistinct({
adapter, adapter,
db, 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) 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', () => { describe('select', () => {

View File

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