This PR fixes queries like this:
```ts
const findFolder = await payload.find({
collection: 'payload-folders',
where: {
id: {
equals: folderDoc.id,
},
},
joins: {
documentsAndFolders: {
limit: 100_000,
sort: 'name',
where: {
and: [
{
relationTo: {
equals: 'payload-folders',
},
},
{
folderType: {
in: ['folderPoly1'], // previously this didn't work
},
},
],
},
},
},
})
```
Additionally, this PR potentially fixes querying JSON fields by the top
level path, for example if your JSON field has a value like: `[1, 2]`,
previously `where: { json: { equals: 1 } }` didn't work, however with a
value like `{ nested: [1, 2] }` and a query `where: { 'json.nested': {
equals: 1 } }`it did.
---------
Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
110 lines
2.7 KiB
TypeScript
110 lines
2.7 KiB
TypeScript
import type { CreateJSONQueryArgs } from '@payloadcms/drizzle/types'
|
|
|
|
type FromArrayArgs = {
|
|
isRoot?: true
|
|
operator: string
|
|
pathSegments: string[]
|
|
table: string
|
|
treatAsArray?: string[]
|
|
value: boolean | number | string
|
|
}
|
|
|
|
const fromArray = ({
|
|
isRoot,
|
|
operator,
|
|
pathSegments,
|
|
table,
|
|
treatAsArray,
|
|
value,
|
|
}: FromArrayArgs) => {
|
|
const newPathSegments = pathSegments.slice(1)
|
|
const alias = `${pathSegments[isRoot ? 0 : 1]}_alias_${newPathSegments.length}`
|
|
|
|
return `EXISTS (
|
|
SELECT 1
|
|
FROM json_each(${table}.${pathSegments[0]}) AS ${alias}
|
|
WHERE ${createJSONQuery({
|
|
operator,
|
|
pathSegments: newPathSegments,
|
|
table: alias,
|
|
treatAsArray,
|
|
value,
|
|
})}
|
|
)`
|
|
}
|
|
|
|
type CreateConstraintArgs = {
|
|
alias?: string
|
|
operator: string
|
|
pathSegments: string[]
|
|
treatAsArray?: string[]
|
|
value: boolean | number | string
|
|
}
|
|
|
|
const createConstraint = ({
|
|
alias,
|
|
operator,
|
|
pathSegments,
|
|
value,
|
|
}: CreateConstraintArgs): string => {
|
|
const newAlias = `${pathSegments[0]}_alias_${pathSegments.length - 1}`
|
|
let formattedValue = value
|
|
let formattedOperator = operator
|
|
if (['contains', 'like'].includes(operator)) {
|
|
formattedOperator = 'like'
|
|
formattedValue = `%${value}%`
|
|
} else if (['not_like', 'notlike'].includes(operator)) {
|
|
formattedOperator = 'not like'
|
|
formattedValue = `%${value}%`
|
|
} else if (operator === 'equals') {
|
|
formattedOperator = '='
|
|
}
|
|
|
|
if (pathSegments.length === 1) {
|
|
return `EXISTS (SELECT 1 FROM json_each("${pathSegments[0]}") AS ${newAlias} WHERE ${newAlias}.value ${formattedOperator} '${formattedValue}')`
|
|
}
|
|
|
|
return `EXISTS (
|
|
SELECT 1
|
|
FROM json_each(${alias}.value -> '${pathSegments[0]}') AS ${newAlias}
|
|
WHERE COALESCE(${newAlias}.value ->> '${pathSegments[1]}', '') ${formattedOperator} '${formattedValue}'
|
|
)`
|
|
}
|
|
|
|
export const createJSONQuery = ({
|
|
column,
|
|
operator,
|
|
pathSegments,
|
|
rawColumn,
|
|
table,
|
|
treatAsArray,
|
|
treatRootAsArray,
|
|
value,
|
|
}: CreateJSONQueryArgs): string => {
|
|
if ((operator === 'in' || operator === 'not_in') && Array.isArray(value)) {
|
|
let sql = ''
|
|
for (const [i, v] of value.entries()) {
|
|
sql = `${sql}${createJSONQuery({ column, operator: operator === 'in' ? 'equals' : 'not_equals', pathSegments, rawColumn, table, treatAsArray, treatRootAsArray, value: v })} ${i === value.length - 1 ? '' : ` ${operator === 'in' ? 'OR' : 'AND'} `}`
|
|
}
|
|
return sql
|
|
}
|
|
|
|
if (treatAsArray?.includes(pathSegments[1]!) && table) {
|
|
return fromArray({
|
|
operator,
|
|
pathSegments,
|
|
table,
|
|
treatAsArray,
|
|
value: value as CreateConstraintArgs['value'],
|
|
})
|
|
}
|
|
|
|
return createConstraint({
|
|
alias: table,
|
|
operator,
|
|
pathSegments,
|
|
treatAsArray,
|
|
value: value as CreateConstraintArgs['value'],
|
|
})
|
|
}
|