Found on main @ 68fd61c. Reproduces on every dialect — this is not the SQLite-wrapper class of bug.
cursorPaginate appends its cursor predicate with an unconditional WHERE keyword rather than continuing an existing one with AND. So the moment the builder already has a filter, the SQL has two top-level WHEREs and fails to parse.
chunkById and eachById both delegate to cursorPaginate, so they inherit it.
Repro
const q = () => db.selectFrom('t').selectAll()
// works — no preceding where, so no cursor predicate collision
await q().cursorPaginate(3, 5, 'id') // { data: [6,7,8] }
await q().chunkById(3, 'id', rows => { /* ... */ }) // all 20 rows
// throws — the moment a filter exists
await q().where('b', '=', 1).cursorPaginate(3, 5, 'id')
await q().where('b', '=', 1).chunkById(3, 'id', rows => {})
await q().where('b', '=', 1).eachById(3, 'id', row => {})All three raise:
SQLiteError: near "WHERE": syntax errorNote the cursor has to be non-null to see it: with cursor === undefined the predicate is never emitted, which is why chunkById's first page succeeds and the failure only lands on the second iteration.
Impact
.where(...).chunkById(...) is the natural way to walk a filtered table in batches, and it has never worked. Because the first page succeeds, a short test fixture that fits in one chunk passes and the failure only shows up against real data.
Source
cursorPaginate in packages/bun-query-builder/src/client.ts builds the predicate as sql`${q} WHERE ${...} > ${cursor}` . It needs the same AND-vs-WHERE decision every other where-method makes.
Verified by execution, not by reading.