fix(db-postgres): querying multiple hasMany text or number fields (#14028)

Fixes https://github.com/payloadcms/payload/issues/14023
This commit is contained in:
Sasha
2025-10-02 05:30:04 +03:00
committed by GitHub
parent 1e654c0a95
commit 95bdffd11f
4 changed files with 62 additions and 8 deletions

View File

@@ -1,4 +1,4 @@
import type { SQL, Table } from 'drizzle-orm' import type { SQL } from 'drizzle-orm'
import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core' import type { SQLiteTableWithColumns } from 'drizzle-orm/sqlite-core'
import type { import type {
FlattenedBlock, FlattenedBlock,
@@ -8,7 +8,7 @@ import type {
TextField, TextField,
} from 'payload' } from 'payload'
import { and, eq, getTableName, like, sql } from 'drizzle-orm' import { and, eq, getTableName, like, or, sql, Table } from 'drizzle-orm'
import { type PgTableWithColumns } from 'drizzle-orm/pg-core' import { type PgTableWithColumns } from 'drizzle-orm/pg-core'
import { APIError, getFieldByPath } from 'payload' import { APIError, getFieldByPath } from 'payload'
import { fieldShouldBeLocalized, tabHasName } from 'payload/shared' import { fieldShouldBeLocalized, tabHasName } from 'payload/shared'
@@ -499,27 +499,37 @@ export const getTableColumnFromPath = ({
columnName = 'number' columnName = 'number'
} }
newTableName = `${rootTableName}_${tableType}` newTableName = `${rootTableName}_${tableType}`
const existingTable = joins.find((e) => e.queryPath === `${constraintPath}${field.name}`)
const table = (existingTable?.table ??
getTableAlias({ adapter, tableName: newTableName })
.newAliasTable) as PgTableWithColumns<any>
const joinConstraints = [ const joinConstraints = [
eq(adapter.tables[rootTableName].id, adapter.tables[newTableName].parent), eq(adapter.tables[rootTableName].id, table.parent),
like(adapter.tables[newTableName].path, `${constraintPath}${field.name}`), like(table.path, `${constraintPath}${field.name}`),
] ]
if (locale && isFieldLocalized && adapter.payload.config.localization) { if (locale && isFieldLocalized && adapter.payload.config.localization) {
const conditions = [...joinConstraints] const conditions = [...joinConstraints]
if (locale !== 'all') { if (locale !== 'all') {
conditions.push(eq(adapter.tables[newTableName]._locale, locale)) conditions.push(eq(table._locale, locale))
} }
addJoinTable({ addJoinTable({
condition: and(...conditions), condition: and(...conditions),
joins, joins,
table: adapter.tables[newTableName], queryPath: `${constraintPath}${field.name}`,
table,
}) })
} else { } else {
addJoinTable({ addJoinTable({
condition: and(...joinConstraints), condition: and(...joinConstraints),
joins, joins,
table: adapter.tables[newTableName], queryPath: `${constraintPath}${field.name}`,
table,
}) })
} }
@@ -527,7 +537,7 @@ export const getTableColumnFromPath = ({
columnName, columnName,
constraints, constraints,
field, field,
table: adapter.tables[newTableName], table,
} }
} }
break break

View File

@@ -127,6 +127,11 @@ const TextFields: CollectionConfig = {
type: 'text', type: 'text',
hasMany: true, hasMany: true,
}, },
{
name: 'hasManySecond',
type: 'text',
hasMany: true,
},
{ {
name: 'readOnlyHasMany', name: 'readOnlyHasMany',
type: 'text', type: 'text',

View File

@@ -165,6 +165,43 @@ describe('Fields', () => {
expect(missResult).toBeFalsy() expect(missResult).toBeFalsy()
}) })
it('should query multiple hasMany fields', async () => {
await payload.delete({ collection: 'text-fields', where: {} })
const hit = await payload.create({
collection: 'text-fields',
data: {
hasMany: ['1', '2', '3'],
hasManySecond: ['4'],
text: 'required',
},
})
const miss = await payload.create({
collection: 'text-fields',
data: {
hasMany: ['6'],
hasManySecond: ['4'],
text: 'required',
},
})
const { docs } = await payload.find({
collection: 'text-fields',
where: {
hasMany: { equals: '3' },
hasManySecond: {
equals: '4',
},
},
})
const hitResult = docs.find(({ id: findID }) => hit.id === findID)
const missResult = docs.find(({ id: findID }) => miss.id === findID)
expect(hitResult).toBeDefined()
expect(missResult).toBeFalsy()
})
it('should query like on value', async () => { it('should query like on value', async () => {
const miss = await payload.create({ const miss = await payload.create({
collection: 'text-fields', collection: 'text-fields',

View File

@@ -769,6 +769,7 @@ export interface TextField {
fieldWithDefaultValue?: string | null; fieldWithDefaultValue?: string | null;
dependentOnFieldWithDefaultValue?: string | null; dependentOnFieldWithDefaultValue?: string | null;
hasMany?: string[] | null; hasMany?: string[] | null;
hasManySecond?: string[] | null;
readOnlyHasMany?: string[] | null; readOnlyHasMany?: string[] | null;
validatesHasMany?: string[] | null; validatesHasMany?: string[] | null;
localizedHasMany?: string[] | null; localizedHasMany?: string[] | null;
@@ -3256,6 +3257,7 @@ export interface TextFieldsSelect<T extends boolean = true> {
fieldWithDefaultValue?: T; fieldWithDefaultValue?: T;
dependentOnFieldWithDefaultValue?: T; dependentOnFieldWithDefaultValue?: T;
hasMany?: T; hasMany?: T;
hasManySecond?: T;
readOnlyHasMany?: T; readOnlyHasMany?: T;
validatesHasMany?: T; validatesHasMany?: T;
localizedHasMany?: T; localizedHasMany?: T;