ReviewOS

stacks/bun-query-builder

where({}) on UPDATE/DELETE emits a bare WHERE, and poisons every predicate chained after it

#1095
Closed glennmichael123 opened this 22 days ago · 0 comments
22 days ago

Found on main @ 68fd61c, verified by execution.

An empty condition object on a write builder produces malformed SQL rather than being rejected.

Repro

String(db.updateTable('t').set({ b: 1 }).where({}).toSQL())
// UPDATE "t" SET "b" = $1 WHERE

String(db.deleteFrom('t').where({}).toSQL())
// DELETE FROM "t" WHERE

String(db.updateTable('t').set({ b: 1 }).where({}).where('id', '=', 1).toSQL())
// UPDATE "t" SET "b" = $1 WHERE  AND "id" = $2

The first two fail closed — the statement is malformed, so the driver rejects it and nothing is written. That is survivable.

The third is the problem: the empty object opens a WHERE it never fills, and the next predicate attaches to it with AND. The statement stays malformed here, but the failure mode is a dangling connector rather than a missing filter, and it means where({}) is not inert — it corrupts whatever follows.

Why not "just omit the WHERE"

Because on a write, omitting the clause turns "no filter" into "every row". UPDATE t SET b = 1 with no WHERE rewrites the table; DELETE FROM t empties it. The empty-object case must throw, not degrade to an unfiltered statement.

That is the opposite of the SELECT builder, where where({}) is correctly a no-op: a conjunction of zero conditions is TRUE, and a SELECT matching everything has no destructive consequence. The asymmetry is deliberate and worth a comment wherever this is fixed.

Suggested

Throw at the call site in the write builders' object branch:

[query-builder] where({}) on an UPDATE/DELETE: an empty condition object has no filter,
and emitting none would match every row. Pass at least one condition, or call the
statement unconditionally if that is what you mean.

Sign in to comment on this issue.