Skip to content

fix(parse): check INSERT columns and UPDATE SET targets in strict mode - #349

Merged
tiagolauer merged 1 commit into
masterfrom
fix/282-write-column-check
Aug 2, 2026
Merged

fix(parse): check INSERT columns and UPDATE SET targets in strict mode#349
tiagolauer merged 1 commit into
masterfrom
fix/282-write-column-check

Conversation

@tiagolauer

Copy link
Copy Markdown
Owner

Fixes #282.

The bug

StrictRow<DB, 'insert into users (naem) values ($1)'>       // Record<string, never> - no error
StrictRow<DB, 'update users set naem = $1 where id = 1'>    // same

Both are guaranteed runtime errors on every engine. The documented list of unchecked clauses covers GROUP BY, HAVING and ORDER BY; column names in write statements are not on it, and no test pinned this either way.

The fix

Nothing scanned the SET assignment targets or the INSERT column list against the schema — the DML branches used those lists only to position placeholders for params inference.

The parsed statement now carries the write column text (INSERT's list, or UPDATE's SET targets), and the row check resolves each name through the same ResolveColumnType that RETURNING columns go through, so the message is identical to the one every other path produces.

Shapes it cannot read are left alone rather than guessed at:

  • a row assignment (set (a, b) = (1, 2))
  • an INSERT with no column list
  • anything whose target is not a plain name

Budget

MAX_INSTANTIATIONS moves 200,000 → 210,000, raised in this commit as the script asks.

instantiations
master 192,486
naive (structural row test first) 204,196
gated on the column text first 201,322

A SELECT carries an empty string here and pays only for that; the ~4.5% is the two clause scans a write now runs (SplitAtTopLevelKeyword for VALUES/SELECT, and for SET) plus the extra field on the parsed statement.

Verification

tests/write-column-check.test-d.ts, twelve cases. Five red on master:

tests/write-column-check.test-d.ts(18,3): error TS2344: Type 'false' does not satisfy the constraint 'true'.
tests/write-column-check.test-d.ts(25,3): ... (32,3), (39,3), (46,3)
  • unknown INSERT column, unknown SET target, each also when it is not the first entry, and an unknown INSERT column with a RETURNING that would otherwise have typed fine

Controls: valid writes (with and without RETURNING), an INSERT with no column list, a subquery on the value side of a SET, a qualified SET target, and loose mode.

tsc --noEmit clean, 192 runtime tests pass.

    StrictRow<DB, 'insert into users (naem) values ($1)'>     // was Record<string, never>
    StrictRow<DB, 'update users set naem = $1 where id = 1'>  // same

Both are guaranteed runtime errors on every engine. Nothing scanned either
list against the schema: the DML branches used them only to position
placeholders for params inference. The documented list of unchecked clauses
covers GROUP BY, HAVING and ORDER BY - column names in write statements were
never on it, and no test pinned the behaviour either way.

The statement record carries the write column text (the INSERT list, or the
SET assignment targets) and the row check resolves each name the same way
RETURNING columns are resolved, so the message reads identically. Shapes it
cannot read are left alone rather than guessed at: a row assignment
(`set (a, b) = (1, 2)`), an INSERT with no column list, and anything whose
target is not a plain name.

MAX_INSTANTIATIONS moves 200,000 -> 210,000. The fixture goes 192,486 ->
201,322: the check is gated on a non-empty column text, so a SELECT pays
only for carrying the empty string, and the cost is the two clause scans a
write statement now runs. Ordering that gate before the structural
`Row extends QueryTypeError` test was worth 2,500 on its own.

Fixes #282

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tiagolauer
tiagolauer force-pushed the fix/282-write-column-check branch from 54704b1 to 4592c97 Compare August 2, 2026 13:04
@tiagolauer
tiagolauer merged commit 81790b5 into master Aug 2, 2026
12 checks passed
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.

Strict mode never checks INSERT column lists or UPDATE SET targets

1 participant