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,
  },
})
```
This commit is contained in:
Paul
2024-09-11 11:11:13 -06:00
committed by GitHub
parent 465e47a219
commit ec3730722b
3 changed files with 51 additions and 2 deletions

View File

@@ -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: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": "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: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", "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" "translateNewKeys": "pnpm --filter payload run translateNewKeys"
}, },

View File

@@ -125,17 +125,25 @@ export async function parseParams({
} }
const jsonQuery = adapter.convertPathToJSONTraversal(pathSegments) const jsonQuery = adapter.convertPathToJSONTraversal(pathSegments)
const operatorKeys = { const operatorKeys: Record<string, { operator: string; wildcard: string }> = {
contains: { operator: 'like', wildcard: '%' }, contains: { operator: 'like', wildcard: '%' },
equals: { operator: '=', 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: '%' }, like: { operator: 'like', wildcard: '%' },
not_equals: { operator: '<>', wildcard: '' }, not_equals: { operator: '<>', wildcard: '' },
not_in: { operator: 'not in', wildcard: '' },
} }
let formattedValue = val let formattedValue = val
if (adapter.name === 'sqlite' && operator === 'equals' && !isNaN(val)) { if (adapter.name === 'sqlite' && operator === 'equals' && !isNaN(val)) {
formattedValue = 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 { } else {
formattedValue = `'${operatorKeys[operator].wildcard}${val}${operatorKeys[operator].wildcard}'` formattedValue = `'${operatorKeys[operator].wildcard}${val}${operatorKeys[operator].wildcard}'`
} }

View File

@@ -1744,6 +1744,16 @@ describe('Fields', () => {
json: { baz: 'bar', number: 10 }, 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 () => { it('should query nested properties - like', async () => {
@@ -1901,6 +1911,36 @@ describe('Fields', () => {
expect(result.docs).toHaveLength(1) 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)
})
}) })
}) })