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:
@@ -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,
|
||||
|
||||
@@ -115,6 +115,16 @@ const DateFields: CollectionConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'array',
|
||||
name: 'array',
|
||||
fields: [
|
||||
{
|
||||
name: 'date',
|
||||
type: 'date',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user