Summary
The DELETE query builder's .where() method hardcodes WHERE on every call. Chaining two .where()s produces invalid SQL with two WHERE clauses instead of WHERE ... AND .... SQLite rejects the prepared statement with near "WHERE": syntax error.
The UPDATE builder gets this right via a getWhereKeyword() helper that flips to AND after the first clause. DELETE just needs the same helper.
Third bug in the where/where-related family alongside #1012 and #1013.
Reproducer
import { db } from '@stacksjs/database' // proxies bun-query-builder
// Crashes
await db.deleteFrom('judge_reviews_likes')
.where('judge_review_id', '=', 12)
.where('user_id', '=', 5)
.execute()
// Works (object form takes a single where call)
await db.deleteFrom('judge_reviews_likes')
.where({ judge_review_id: 12, user_id: 5 })
.execute()
// Works (UPDATE has the same surface, but its where() correctly switches to AND)
await db.updateTable('judge_reviews_likes')
.set({ foo: 'bar' })
.where('judge_review_id', '=', 12)
.where('user_id', '=', 5)
.execute()Error
SQLiteError: near "WHERE": syntax error
at prepare (bun:sqlite:345:37)
at run (bun-query-builder/dist/src/index.js:11772:33)
at execute (bun-query-builder/dist/src/index.js:11921:39)
at execute (bun-query-builder/dist/src/index.js:15205:44)Compiled SQL the crashing query emits:
DELETE FROM "judge_reviews_likes" WHERE "judge_review_id" = ? WHERE "user_id" = ?Root cause
client.ts:5577-5611 — the DELETE builder's .where():
where(expr: any, op?: string, value?: any) {
whereCondition = expr
if (typeof expr === 'string' && op !== undefined) {
const paramIndex = delParams.length + 1
sqlText += ` WHERE ${quoteId(expr)} ${op} ${getPlaceholder(paramIndex)}` // ← always ` WHERE `
delParams.push(value)
built = null
return this
}
// ...same hardcoded ` WHERE ` in the array-form and object-form branches
}Compare to the UPDATE builder at client.ts:5454, which defines:
const getWhereKeyword = () => sqlText.toUpperCase().includes(' WHERE ') ? 'AND' : 'WHERE'…and uses it for every clause. DELETE is missing this helper.
Suggested fix
Add the same helper at the top of the DELETE builder closure and use it across all three where-shape branches:
const getWhereKeyword = () => sqlText.toUpperCase().includes(' WHERE ') ? 'AND' : 'WHERE'
// in each branch:
sqlText += ` ${getWhereKeyword()} ${quoteId(expr)} ${op} ${getPlaceholder(paramIndex)}`That mirrors the UPDATE implementation byte-for-byte; DELETE inherits the same semantics for free.
Workaround in the meantime
Use the object form (.where({ a: 1, b: 2 })) which collapses into a single SQL WHERE clause with AND-joined conditions. Caught while implementing a like/unlike toggle — JudgeReview._likeable.unlike() (the framework's own likeable trait, storage/framework/core/orm/src/traits/likeable.ts) uses .where('judge_review_id', '=', id).where('user_id', '=', userId) and crashed on the second click.
Environment
- bun-query-builder vendored via stacks pantry
- Bun 1.3.x, SQLite driver