fix(db-d1-sqlite): avoid bound parameter limit when querying relationships and inserting rows (#14099)

Fixes
https://discord.com/channels/967097582721572934/1422639568808841329/1425037080051978261

This PR avoids bound parameters usage for `IN` and `NOT_IN` querying on
`id` to respect the D1 limit
https://developers.cloudflare.com/d1/platform/limits/
And also batches inserts when inserting arrays/blocks/hasMany
relationships etc. This is needed because we can't avoid using bound
parameters there, but still want to respect the 100 variables limit per
query.
This commit is contained in:
Sasha
2025-10-07 16:55:44 +03:00
committed by GitHub
parent e8140ed544
commit 444ca0f439
7 changed files with 190 additions and 2 deletions

View File

@@ -0,0 +1,99 @@
import type { Payload } from 'payload'
/* eslint-disable jest/require-top-level-describe */
import path from 'path'
import { fileURLToPath } from 'url'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const describeSqlite = process.env.PAYLOAD_DATABASE?.startsWith('sqlite') ? describe : describe.skip
let payload: Payload
describeSqlite('database - sqlite bound parameters limit', () => {
beforeAll(async () => {
;({ payload } = await initPayloadInt(dirname))
})
afterAll(async () => {
await payload.destroy()
})
it('should not use bound parameters for where querying on ID with IN if limitedBoundParameters: true', async () => {
const defaultExecute = payload.db.drizzle.$client.execute.bind(payload.db.drizzle.$client)
// Limit bounds parameters length
payload.db.drizzle.$client.execute = async function execute(...args) {
const res = await defaultExecute(...args)
const [{ args: boundParameters }] = args as [{ args: any[] }]
// eslint-disable-next-line jest/no-conditional-in-test
if (boundParameters.length > 100) {
throw new Error('Exceeded limit of bound parameters!')
}
return res
}
payload.db.limitedBoundParameters = false
const IN = Array.from({ length: 300 }, (_, i) => i)
// Should fail here because too the length exceeds the limit
await expect(
payload.find({
collection: 'simple',
pagination: false,
where: { id: { in: IN } },
}),
).rejects.toBeTruthy()
// Should fail here because too the length exceeds the limit
await expect(
payload.find({
collection: 'simple',
pagination: false,
where: { id: { not_in: IN } },
}),
).rejects.toBeTruthy()
payload.db.limitedBoundParameters = true
// Should not fail because limitedBoundParameters: true
await expect(
payload.find({
collection: 'simple',
pagination: false,
where: { id: { in: IN } },
}),
).resolves.toBeTruthy()
// Should not fail because limitedBoundParameters: true
await expect(
payload.find({
collection: 'simple',
pagination: false,
where: { id: { not_in: IN } },
}),
).resolves.toBeTruthy()
// Verify that "in" still works properly
const docs = await Promise.all(
Array.from({ length: 300 }, () => payload.create({ collection: 'simple', data: {} })),
)
const res = await payload.find({
collection: 'simple',
pagination: false,
where: { id: { in: docs.map((e) => e.id) } },
})
expect(res.totalDocs).toBe(300)
for (const docInRes of res.docs) {
expect(docs.some((doc) => doc.id === docInRes.id)).toBeTruthy()
}
})
})