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:
Sasha
2024-09-17 19:57:00 +03:00
committed by GitHub
parent 9035467998
commit 31ffc57366
3 changed files with 93 additions and 10 deletions

View File

@@ -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),
)

View File

@@ -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 | string
if (idType === 'number') {
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)
}

View File

@@ -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()
})
})