Found on main @ a258137, verified by execution against live Postgres 17.
The update builder decides between WHERE and AND by scanning the whole statement text (client.ts ~:6902):
const getWhereKeyword = () => SQL_PATTERNS.WHERE.test(sqlText) ? 'AND' : 'WHERE'A subquery in set() that contains its own WHERE satisfies that test, so the caller's first predicate is emitted as AND and becomes part of the SET expression.
Repro
await db.updateTable('t')
.set({ flag: raw('(SELECT count(*) FROM t x WHERE x.id > 3)') })
.where({ id: 1 })
.execute()Emitted:
UPDATE "t" SET "flag" = (SELECT count(*) FROM t x WHERE x.id > 3) AND "id" = $1
-- ^^^ should be WHEREOn Postgres with an integer column this is loud:
argument of AND must be type boolean, not type bigintWhy it is worth fixing rather than leaving to the error
It is loud only because the types happen not to line up. SET "flag" = (<boolean expr>) AND "id" = $1 parses fine when the SET target is boolean — the predicate then silently becomes part of the value being written, and the UPDATE still has no WHERE, so it hits every row.
The same scan is used by whereNull/whereNotNull on this builder, and the delete builder has its own copy.
Suggested fix
Track whether this builder has appended a predicate, rather than inferring it from the statement text. A boolean on the builder cannot be fooled by a subquery, a string literal containing the word, or a column named where.
Found by an automated sweep; the emitted SQL above is from my own reproduction.