fix(db-postgres): in and not_in query operator (#3608)

This commit is contained in:
Dan Ribbens
2023-10-12 17:11:12 -04:00
committed by GitHub
parent 06a51b3c9b
commit 46a24a9822
3 changed files with 108 additions and 5 deletions

View File

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

View File

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

View File

@@ -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) => {