diff --git a/packages/drizzle/src/queries/parseParams.ts b/packages/drizzle/src/queries/parseParams.ts index 01ecb51630e..20824a579c2 100644 --- a/packages/drizzle/src/queries/parseParams.ts +++ b/packages/drizzle/src/queries/parseParams.ts @@ -185,7 +185,11 @@ export function parseParams({ } let formattedValue = val - if (adapter.name === 'sqlite' && operator === 'equals' && !isNaN(val)) { + if ( + adapter.name === 'sqlite' && + operator === 'equals' && + (typeof val === 'number' || typeof val === 'boolean') + ) { formattedValue = val } else if (['in', 'not_in'].includes(operator) && Array.isArray(val)) { formattedValue = `(${val.map((v) => `${escapeSQLValue(v)}`).join(',')})` diff --git a/packages/drizzle/src/sqlite/createJSONQuery/convertPathToJSONTraversal.ts b/packages/drizzle/src/sqlite/createJSONQuery/convertPathToJSONTraversal.ts index d7ec458a314..7045cda4241 100644 --- a/packages/drizzle/src/sqlite/createJSONQuery/convertPathToJSONTraversal.ts +++ b/packages/drizzle/src/sqlite/createJSONQuery/convertPathToJSONTraversal.ts @@ -2,8 +2,9 @@ export const convertPathToJSONTraversal = (incomingSegments: string[]): string = const segments = [...incomingSegments] segments.shift() - return segments.reduce((res, segment) => { + return segments.reduce((res, segment, i) => { const formattedSegment = Number.isNaN(parseInt(segment)) ? `'${segment}'` : segment - return `${res}->>${formattedSegment}` + const isLast = i === segments.length - 1 + return `${res}${isLast ? '->>' : '->'}${formattedSegment}` }, '') } diff --git a/test/fields/int.spec.ts b/test/fields/int.spec.ts index 846aa7fb122..7b01acad1e0 100644 --- a/test/fields/int.spec.ts +++ b/test/fields/int.spec.ts @@ -3779,6 +3779,68 @@ describe('Fields', () => { expect(docs[0].id).toBe(json_1.id) }) + it('should query 2-level nested object properties', async () => { + const docId = '42' + const collectionSlug = 'posts' + + const matchingDoc = await payload.create({ + collection: 'json-fields', + data: { + json: { doc: { value: docId, relationTo: collectionSlug } }, + }, + }) + + // different ID, same relationTo — should NOT match the and query + await payload.create({ + collection: 'json-fields', + data: { + json: { doc: { value: '99', relationTo: collectionSlug } }, + }, + }) + + // same ID, different relationTo — should NOT match the and query + await payload.create({ + collection: 'json-fields', + data: { + json: { doc: { value: docId, relationTo: 'other' } }, + }, + }) + + const { docs: equalsDocs } = await payload.find({ + collection: 'json-fields', + where: { + 'json.doc.value': { equals: docId }, + }, + }) + + expect(equalsDocs.map(({ id }) => id)).toContain(matchingDoc.id) + + const { docs: existsDocs } = await payload.find({ + collection: 'json-fields', + where: { + 'json.doc.value': { exists: true }, + }, + }) + + expect(existsDocs.map(({ id }) => id)).toContain(matchingDoc.id) + + // mirrors the pattern from scheduled jobs frontend query: + // where[and][2][input.doc.value][equals] = id + // where[and][3][input.doc.relationTo][equals] = collectionSlug + const { docs: andDocs } = await payload.find({ + collection: 'json-fields', + where: { + and: [ + { 'json.doc.value': { equals: docId } }, + { 'json.doc.relationTo': { equals: collectionSlug } }, + ], + }, + }) + + expect(andDocs).toHaveLength(1) + expect(andDocs[0]?.id).toBe(matchingDoc.id) + }) + it('should disallow unsafe query paths', async () => { await expect( payload.find({