fix(drizzle): in query on polymorphic relations across ID types (#8240)
Fixes querying using `in` operator by polymorphic relationship value. The previous PR https://github.com/payloadcms/payload/pull/8191 didn't handle the case when the incoming query value is an array and therefore each item of the array can have a different type. Ensures test coverage
This commit is contained in:
@@ -23,7 +23,7 @@ type Constraint = {
|
||||
type TableColumn = {
|
||||
columnName?: string
|
||||
columns?: {
|
||||
idType: 'number' | 'text'
|
||||
idType: 'number' | 'text' | 'uuid'
|
||||
rawColumn: SQL<unknown>
|
||||
}[]
|
||||
constraints: Constraint[]
|
||||
@@ -521,7 +521,8 @@ export const getTableColumnFromPath = ({
|
||||
|
||||
const columns: TableColumn['columns'] = field.relationTo
|
||||
.map((relationTo) => {
|
||||
let idType: 'number' | 'text' = adapter.idType === 'uuid' ? 'text' : 'number'
|
||||
let idType: 'number' | 'text' | 'uuid' =
|
||||
adapter.idType === 'uuid' ? 'uuid' : 'number'
|
||||
|
||||
const { customIDType } = adapter.payload.collections[relationTo]
|
||||
|
||||
@@ -529,9 +530,19 @@ export const getTableColumnFromPath = ({
|
||||
idType = customIDType
|
||||
}
|
||||
|
||||
const idTypeTextOrUuid = idType === 'text' || idType === 'uuid'
|
||||
|
||||
// Do not add the column to OR if we know that it can't match by the type
|
||||
// We can't do the same with idType: 'number' because `value` can be from the REST search query params
|
||||
if (typeof value === 'number' && idType === 'text') {
|
||||
if (typeof value === 'number' && idTypeTextOrUuid) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
value.every((val) => typeof val === 'number') &&
|
||||
idTypeTextOrUuid
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -540,8 +551,8 @@ export const getTableColumnFromPath = ({
|
||||
// We need this because Postgres throws an error if querying by UUID column with a value that isn't a valid UUID.
|
||||
if (
|
||||
value &&
|
||||
!customIDType &&
|
||||
adapter.idType === 'uuid' &&
|
||||
!Array.isArray(value) &&
|
||||
idType === 'uuid' &&
|
||||
hasCustomCollectionWithCustomID
|
||||
) {
|
||||
if (!uuidValidate(value)) {
|
||||
@@ -549,6 +560,15 @@ export const getTableColumnFromPath = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
Array.isArray(value) &&
|
||||
idType === 'uuid' &&
|
||||
hasCustomCollectionWithCustomID &&
|
||||
!value.some((val) => uuidValidate(val))
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
const relationTableName = adapter.tableNameMap.get(
|
||||
toSnakeCase(adapter.payload.collections[relationTo].config.slug),
|
||||
)
|
||||
|
||||
@@ -2,13 +2,14 @@ import type { SQL } from 'drizzle-orm'
|
||||
|
||||
import { APIError, createArrayFromCommaDelineated, type Field, type TabAsField } from 'payload'
|
||||
import { fieldAffectsData } from 'payload/shared'
|
||||
import { validate as uuidValidate } from 'uuid'
|
||||
|
||||
import type { DrizzleAdapter } from '../types.js'
|
||||
|
||||
type SanitizeQueryValueArgs = {
|
||||
adapter: DrizzleAdapter
|
||||
columns?: {
|
||||
idType: 'number' | 'text'
|
||||
idType: 'number' | 'text' | 'uuid'
|
||||
rawColumn: SQL<unknown>
|
||||
}[]
|
||||
field: Field | TabAsField
|
||||
@@ -119,15 +120,37 @@ export const sanitizeQueryValue = ({
|
||||
} else {
|
||||
formattedColumns = columns
|
||||
.map(({ idType, rawColumn }) => {
|
||||
let formattedValue: number | number[] | string | string[]
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
formattedValue = val
|
||||
.map((eachVal) => {
|
||||
let formattedValue: number | string
|
||||
|
||||
if (idType === 'number') {
|
||||
formattedValue = Number(eachVal)
|
||||
|
||||
if (Number.isNaN(formattedValue)) {
|
||||
return null
|
||||
}
|
||||
} else {
|
||||
if (idType === 'uuid' && !uuidValidate(eachVal)) {
|
||||
return null
|
||||
}
|
||||
|
||||
formattedValue = String(eachVal)
|
||||
}
|
||||
|
||||
return formattedValue
|
||||
})
|
||||
.filter(Boolean) as number[] | string[]
|
||||
} else if (idType === 'number') {
|
||||
formattedValue = Number(val)
|
||||
|
||||
if (Number.isNaN(formattedValue)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
if (idType === 'text') {
|
||||
} else {
|
||||
formattedValue = String(val)
|
||||
}
|
||||
|
||||
|
||||
@@ -587,6 +587,46 @@ describe('Relationships', () => {
|
||||
|
||||
expect(customIDNumberResult.totalDocs).toBe(1)
|
||||
expect(customIDNumberResult.docs[0].id).toBe(relToCustomIdNumber.id)
|
||||
|
||||
const inResult_1 = await payload.find({
|
||||
collection: 'rels-to-pages-and-custom-text-ids',
|
||||
where: {
|
||||
'rel.value': {
|
||||
in: [page.id, customIDNumber.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(inResult_1.totalDocs).toBe(2)
|
||||
expect(inResult_1.docs.some((each) => each.id === relToPage.id)).toBeTruthy()
|
||||
expect(inResult_1.docs.some((each) => each.id === relToCustomIdNumber.id)).toBeTruthy()
|
||||
|
||||
const inResult_2 = await payload.find({
|
||||
collection: 'rels-to-pages-and-custom-text-ids',
|
||||
where: {
|
||||
'rel.value': {
|
||||
in: [customIDNumber.id, customIDText.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(inResult_2.totalDocs).toBe(2)
|
||||
expect(inResult_2.docs.some((each) => each.id === relToCustomIdText.id)).toBeTruthy()
|
||||
expect(inResult_2.docs.some((each) => each.id === relToCustomIdNumber.id)).toBeTruthy()
|
||||
|
||||
const inResult_3 = await payload.find({
|
||||
collection: 'rels-to-pages-and-custom-text-ids',
|
||||
where: {
|
||||
'rel.value': {
|
||||
in: [customIDNumber.id, customIDText.id, page.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(inResult_3.totalDocs).toBe(3)
|
||||
expect(inResult_3.docs.some((each) => each.id === relToCustomIdText.id)).toBeTruthy()
|
||||
expect(inResult_3.docs.some((each) => each.id === relToCustomIdNumber.id)).toBeTruthy()
|
||||
expect(inResult_3.docs.some((each) => each.id === relToPage.id)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user