From ec3730722b0ccd5470b7d4aff37eceece76204d3 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 11 Sep 2024 11:11:13 -0600 Subject: [PATCH] feat(drizzle): add support for in and not_in operators on json field (#8148) Closes https://github.com/payloadcms/payload/issues/7952 Adds support for `in` and `not_in` operator against JSON field filters. The following queries are now valid in postgres as well, previously it only worked in mongo ```ts await payload.find({ collection: 'posts', where: { 'data.value': { in: ['12', '13', '14'], }, }, context: { disable: true, }, }) await payload.find({ collection: 'posts', where: { 'data.value': { not_in: ['12', '13', '14'], }, }, context: { disable: true, }, }) ``` --- package.json | 1 + packages/drizzle/src/queries/parseParams.ts | 12 +++++-- test/fields/int.spec.ts | 40 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index bdc83b3a0b..44a803f803 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "test:e2e:prod:ci": "rm -rf test/node_modules && rm -f test/pnpm-lock.yaml && pnpm run script:pack --all --no-build --dest test/packed && pnpm runts test/setupProd.ts && cd test && pnpm i --ignore-workspace && cd .. && pnpm runts ./test/runE2E.ts --prod", "test:int": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand", "test:int:postgres": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=postgres DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand", + "test:int:sqlite": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 PAYLOAD_DATABASE=sqlite DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=test/jest.config.js --runInBand", "test:unit": "cross-env NODE_OPTIONS=\"--no-deprecation\" NODE_NO_WARNINGS=1 DISABLE_LOGGING=true jest --forceExit --detectOpenHandles --config=jest.config.js --runInBand", "translateNewKeys": "pnpm --filter payload run translateNewKeys" }, diff --git a/packages/drizzle/src/queries/parseParams.ts b/packages/drizzle/src/queries/parseParams.ts index 8a0084d595..fb5e3dd308 100644 --- a/packages/drizzle/src/queries/parseParams.ts +++ b/packages/drizzle/src/queries/parseParams.ts @@ -125,17 +125,25 @@ export async function parseParams({ } const jsonQuery = adapter.convertPathToJSONTraversal(pathSegments) - const operatorKeys = { + const operatorKeys: Record = { contains: { operator: 'like', wildcard: '%' }, equals: { operator: '=', wildcard: '' }, - exists: { operator: val === true ? 'is not null' : 'is null' }, + exists: { operator: val === true ? 'is not null' : 'is null', wildcard: '' }, + in: { operator: 'in', wildcard: '' }, like: { operator: 'like', wildcard: '%' }, not_equals: { operator: '<>', wildcard: '' }, + not_in: { operator: 'not in', wildcard: '' }, } let formattedValue = val if (adapter.name === 'sqlite' && operator === 'equals' && !isNaN(val)) { formattedValue = val + } else if (['in', 'not_in'].includes(operator) && Array.isArray(val)) { + if (adapter.name === 'sqlite') { + formattedValue = `(${val.map((v) => `${v}`).join(',')})` + } else { + formattedValue = `(${val.map((v) => `'${v}'`).join(', ')})` + } } else { formattedValue = `'${operatorKeys[operator].wildcard}${val}${operatorKeys[operator].wildcard}'` } diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index a892eaed44..40802ebc9d 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -1744,6 +1744,16 @@ describe('Fields', () => { json: { baz: 'bar', number: 10 }, }, }) + + // Create content for array 'in' and 'not_in' queries + for (let i = 1; i < 6; i++) { + await payload.create({ + collection: 'json-fields', + data: { + json: { value: i }, + }, + }) + } }) it('should query nested properties - like', async () => { @@ -1901,6 +1911,36 @@ describe('Fields', () => { expect(result.docs).toHaveLength(1) }) + + it('should query nested numbers - in', async () => { + const { docs } = await payload.find({ + collection: 'json-fields', + where: { + 'json.value': { in: [1, 3] }, + }, + }) + + const docIDs = docs.map(({ json }) => json.value) + + expect(docIDs).toContain(1) + expect(docIDs).toContain(3) + expect(docIDs).not.toContain(2) + }) + + it('should query nested numbers - not_in', async () => { + const { docs } = await payload.find({ + collection: 'json-fields', + where: { + 'json.value': { not_in: [1, 3] }, + }, + }) + + const docIDs = docs.map(({ json }) => json.value) + + expect(docIDs).not.toContain(1) + expect(docIDs).not.toContain(3) + expect(docIDs).toContain(2) + }) }) })