Skip to content

fix(query-builder): keep every where condition in deletes and updates - #56

Merged
3m1n3nc3 merged 1 commit into
mainfrom
fix/write-where-conditions
Oct 19, 2026
Merged

3m1n3nc3 merged 1 commit into
mainfrom
fix/write-where-conditions

Conversation

@3m1n3nc3

Copy link
Copy Markdown
Member

Problem

delete(), update() and updateFrom() built their where clause through buildWhere(), which converts the structured condition back to the Prisma-like schema shape via toQuerySchemaWhere. That shape only represents comparisons, groups and negations; every other condition type converts to undefined and is filtered out of its group.

So raw, JSON, relation (whereHas), exists, column-comparison, date/time, full-text and expression conditions were silently dropped from writes, and the write hit every row the remaining constraints matched:

// Deletes ALL of the user's tokens, not just the run's
await PersonalAccessToken.query()
  .where({ userId })
  .whereRaw('jsonb_exists(abilities::jsonb, ?)', [`run:${runId}`])
  .delete()

Reads were unaffected because they compile from the structured condition directly.

Two query-building paths could also lose constraints (affecting reads too): combining an object where that cannot be converted to a structured condition with raw/JSON/expression constraints dropped one side, depending on order.

Fix

  • delete(), update() and updateFrom() build from buildQueryWhereCondition(), the same condition reads use, through a new buildWriteCondition(). The "requires a where clause" and "could not be compiled" errors are unchanged.
  • update() takes the direct primary-key path only when the whole condition is a primary-key equality. Previously where({ id }).whereRaw(...) looked unique and the raw clause was ignored.
  • appendQueryCondition flushes a legacy where into the structured condition before appending, and throws UnsupportedAdapterFeatureException when it cannot be converted. addLogicalWhere throws instead of converting constraints the schema shape cannot represent.
  • isUniqueWhere is replaced by uniqueWhereFor(condition).

Behaviour change

A query that silently lost a condition now either applies it or throws. Writes that were too broad now affect fewer rows.

Tests

New PostgreSQL tests in tests/postgres/kysely-adapter.spec.ts cover delete, updateFrom, update (first match and primary key) with raw clauses, and the combination guard. All fail on main and pass here.

  • pnpm test: 413 passed, 2 skipped
  • pnpm test:postgres: 159 passed
  • tsc --noEmit and pnpm lint clean

delete(), update() and updateFrom() rebuilt their where clause through the
Prisma-like schema shape, which only represents comparisons, groups and
negations. Raw, JSON, relation, exists, column-comparison, date/time,
full-text and expression conditions were silently dropped, widening the
write to every row the remaining constraints matched.

Writes now build from the same structured condition reads use. update()
only takes the primary-key path when the whole condition is a primary-key
equality, and combining an unconvertible object where with structured
constraints throws instead of losing one of them.
@3m1n3nc3
3m1n3nc3 merged commit 584ec2c into main Oct 19, 2026
5 checks passed
@3m1n3nc3
3m1n3nc3 deleted the fix/write-where-conditions branch October 19, 2026 05:29
@codecov

codecov Bot commented Oct 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.69767% with 4 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/QueryBuilder.ts 90.69% 4 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant