fix(db-postgres): in and not_in query operator (#3608)
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import type { SQL } from 'drizzle-orm'
|
||||
import type { Field, Operator, Where } from 'payload/types'
|
||||
|
||||
import { and, ilike, isNotNull, isNull, ne, or, sql } from 'drizzle-orm'
|
||||
import { and, ilike, isNotNull, isNull, ne, notInArray, or, sql } from 'drizzle-orm'
|
||||
import { QueryError } from 'payload/errors'
|
||||
import { validOperators } from 'payload/types'
|
||||
|
||||
@@ -147,6 +147,7 @@ export async function parseParams({
|
||||
const { operator: queryOperator, value: queryValue } = sanitizeQueryValue({
|
||||
field,
|
||||
operator,
|
||||
relationOrPath,
|
||||
val,
|
||||
})
|
||||
|
||||
@@ -158,6 +159,17 @@ export async function parseParams({
|
||||
ne<any>(rawColumn || table[columnName], queryValue),
|
||||
),
|
||||
)
|
||||
} else if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
Array.isArray(queryValue) &&
|
||||
operator === 'not_in'
|
||||
) {
|
||||
constraints.push(
|
||||
sql`${notInArray(table[columnName], queryValue)} OR
|
||||
${table[columnName]}
|
||||
IS
|
||||
NULL`,
|
||||
)
|
||||
} else {
|
||||
constraints.push(
|
||||
operatorMap[queryOperator](rawColumn || table[columnName], queryValue),
|
||||
|
||||
@@ -5,12 +5,14 @@ import { createArrayFromCommaDelineated } from 'payload/utilities'
|
||||
type SanitizeQueryValueArgs = {
|
||||
field: Field | TabAsField
|
||||
operator: string
|
||||
relationOrPath: string
|
||||
val: any
|
||||
}
|
||||
|
||||
export const sanitizeQueryValue = ({
|
||||
field,
|
||||
operator: operatorArg,
|
||||
relationOrPath,
|
||||
val,
|
||||
}: SanitizeQueryValueArgs): { operator: string; value: unknown } => {
|
||||
let operator = operatorArg
|
||||
@@ -18,6 +20,22 @@ export const sanitizeQueryValue = ({
|
||||
|
||||
if (!fieldAffectsData(field)) return { operator, value: formattedValue }
|
||||
|
||||
if (
|
||||
(field.type === 'relationship' || field.type === 'upload') &&
|
||||
!relationOrPath.endsWith('relationTo') &&
|
||||
Array.isArray(formattedValue)
|
||||
) {
|
||||
const allPossibleIDTypes: (number | string)[] = []
|
||||
formattedValue.forEach((val) => {
|
||||
if (typeof val === 'string') {
|
||||
allPossibleIDTypes.push(val, parseInt(val))
|
||||
} else {
|
||||
allPossibleIDTypes.push(val, String(val))
|
||||
}
|
||||
})
|
||||
formattedValue = allPossibleIDTypes
|
||||
}
|
||||
|
||||
// Cast incoming values as proper searchable types
|
||||
if (field.type === 'checkbox' && typeof val === 'string') {
|
||||
if (val.toLowerCase() === 'true') formattedValue = true
|
||||
|
||||
@@ -7,7 +7,14 @@ import payload from '../../packages/payload/src'
|
||||
import { mapAsync } from '../../packages/payload/src/utilities/mapAsync'
|
||||
import { initPayloadTest } from '../helpers/configHelpers'
|
||||
import { RESTClient } from '../helpers/rest'
|
||||
import config, { customIdNumberSlug, customIdSlug, errorOnHookSlug, pointSlug, relationSlug, slug, } from './config'
|
||||
import config, {
|
||||
customIdNumberSlug,
|
||||
customIdSlug,
|
||||
errorOnHookSlug,
|
||||
pointSlug,
|
||||
relationSlug,
|
||||
slug,
|
||||
} from './config'
|
||||
|
||||
let client: RESTClient
|
||||
|
||||
@@ -676,6 +683,72 @@ describe('collections-rest', () => {
|
||||
expect(result.totalDocs).toEqual(1)
|
||||
})
|
||||
|
||||
it('not_in (relationships)', async () => {
|
||||
const relationship = await payload.create({
|
||||
collection: relationSlug,
|
||||
data: {},
|
||||
})
|
||||
|
||||
await createPost({ relationField: relationship.id, title: 'not-me' })
|
||||
// await createPost({ relationMultiRelationTo: relationship.id, title: 'not-me' })
|
||||
const post2 = await createPost({ title: 'me' })
|
||||
const { result, status } = await client.find<Post>({
|
||||
query: {
|
||||
relationField: {
|
||||
not_in: [relationship.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// do not want to error for empty arrays
|
||||
const { status: emptyNotInStatus } = await client.find<Post>({
|
||||
query: {
|
||||
relationField: {
|
||||
not_in: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(emptyNotInStatus).toEqual(200)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(result.docs).toEqual([post2])
|
||||
expect(result.totalDocs).toEqual(1)
|
||||
})
|
||||
|
||||
it('in (relationships)', async () => {
|
||||
const relationship = await payload.create({
|
||||
collection: relationSlug,
|
||||
data: {},
|
||||
})
|
||||
|
||||
const post1 = await createPost({ relationField: relationship.id, title: 'me' })
|
||||
// await createPost({ relationMultiRelationTo: relationship.id, title: 'not-me' })
|
||||
await createPost({ title: 'not-me' })
|
||||
const { result, status } = await client.find<Post>({
|
||||
query: {
|
||||
relationField: {
|
||||
in: [relationship.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// do not want to error for empty arrays
|
||||
const { status: emptyNotInStatus } = await client.find<Post>({
|
||||
query: {
|
||||
relationField: {
|
||||
in: [],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
expect(emptyNotInStatus).toEqual(200)
|
||||
|
||||
expect(status).toEqual(200)
|
||||
expect(result.docs).toEqual([post1])
|
||||
expect(result.totalDocs).toEqual(1)
|
||||
})
|
||||
|
||||
it('like', async () => {
|
||||
const post1 = await createPost({ title: 'prefix-value' })
|
||||
|
||||
@@ -1175,18 +1248,18 @@ describe('collections-rest', () => {
|
||||
})
|
||||
})
|
||||
|
||||
async function createPost (overrides?: Partial<Post>) {
|
||||
async function createPost(overrides?: Partial<Post>) {
|
||||
const { doc } = await client.create<Post>({ data: { title: 'title', ...overrides } })
|
||||
return doc
|
||||
}
|
||||
|
||||
async function createPosts (count: number) {
|
||||
async function createPosts(count: number) {
|
||||
await mapAsync([...Array(count)], async () => {
|
||||
await createPost()
|
||||
})
|
||||
}
|
||||
|
||||
async function clearDocs (): Promise<void> {
|
||||
async function clearDocs(): Promise<void> {
|
||||
const allDocs = await payload.find({ collection: slug, limit: 100 })
|
||||
const ids = allDocs.docs.map((doc) => doc.id)
|
||||
await mapAsync(ids, async (id) => {
|
||||
|
||||
Reference in New Issue
Block a user