Skip to content

fix(params): give a placeholder inside an INSERT VALUES call its slot - #335

Merged
tiagolauer merged 1 commit into
masterfrom
fix/269-insert-values-call-params
Aug 2, 2026
Merged

fix(params): give a placeholder inside an INSERT VALUES call its slot#335
tiagolauer merged 1 commit into
masterfrom
fix/269-insert-values-call-params

Conversation

@tiagolauer

Copy link
Copy Markdown
Owner

Fixes #269.

The bug

Params<DB, 'insert into users (id, name) values (coalesce(?, 0), ?)'>   // [string]
Params<DB, 'insert into users (id, name) values (coalesce(@a, 0), @b)'> // [string]
Params<DB, 'insert into users (id, name) values (coalesce($1, 0), $2)'> // [unknown, string]

The README promises the opposite — "A placeholder inside a call is typed (lower($1), coalesce($1, 0))" — and it does work in WHERE.

The @ variant is the one that bites hardest, and it does it silently: the caller can only pass one value, resolveMixedParameters scans @a first and gives it that value, binds @b = null, and the INSERT succeeds having written into the wrong column.

The fix

MatchInsertValues runs IsPlaceholder on each VALUES entry as one token, and StripCallWrapper bails on any inner comma, so the placeholders inside the call never registered. (WHERE only works by accident of space-splitting, which isolates coalesce($1, as its own token.)

An entry that is not itself a placeholder now has its call's argument list split, and each argument is checked again. That is enough: a single-argument call around a placeholder (lower(?)) is something CleanScanToken already unwraps, so nesting keeps working. The type still comes from the matching entry in the INSERT column list.

Verification

tests/insert-values-call-params.test-d.ts, eight cases. Four red on master:

tests/insert-values-call-params.test-d.ts(18,3): error TS2344: Type 'false' does not satisfy the constraint 'true'.
tests/insert-values-call-params.test-d.ts(25,3): ...
tests/insert-values-call-params.test-d.ts(32,3): ...
tests/insert-values-call-params.test-d.ts(39,3): ...
  • ?, @name and $n inside coalesce(x, 0) all reach [number, string]
  • a nested coalesce(lower(@b), $$x$$) too, which also pins that a dollar-quoted body is not read as a placeholder

Controls: bare placeholders, a single-argument call, literals taking no slot, and the WHERE-clause form that already worked.

tsc --noEmit clean, 192 runtime tests pass, budget 192,807 (+321).

    Params<DB, 'insert into users (id, name) values (coalesce(?, 0), ?)'>
    // was [string] - one slot for two placeholders

MatchInsertValues runs IsPlaceholder on each VALUES entry as a single token,
and StripCallWrapper gives up on a call carrying an inner comma, so the
placeholder inside it registered nothing. WHERE only works by accident of
space-splitting, which isolates `coalesce($1,` as its own token.

The `@name` spelling is the one that bites hardest. The caller can only pass
one value, resolveMixedParameters scans `@a` first and gives it that value,
binds `@b` to null, and the INSERT succeeds having written into the wrong
column. No error anywhere.

Splitting the call's own argument list is enough to fix it: each argument is
a token again, and a single-argument call around a placeholder (`lower(?)`)
is something CleanScanToken already unwraps, so nesting keeps working. The
column type still comes from the matching entry in the INSERT column list.

Fixes #269

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@tiagolauer
tiagolauer force-pushed the fix/269-insert-values-call-params branch from 6f75af3 to 2800d87 Compare August 2, 2026 13:03
@tiagolauer
tiagolauer merged commit 2790949 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.

Placeholder inside a multi-argument call in INSERT VALUES loses its tuple slot

1 participant