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 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,
|
||||||
|
|||||||
@@ -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)
|
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', () => {
|
||||||
|
|||||||
@@ -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';
|
| {
|
||||||
value: string | TextField;
|
relationTo: 'text-fields';
|
||||||
}[]
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user