$action is a MERGE pseudo-column, not a parameter — the row type already handles it correctly ('INSERT' | 'UPDATE' | 'DELETE', README Limitations). But IsPlaceholder matches any token starting with $, so the parameter scanner and the placeholder-style check both treat $action as a $ placeholder. The documented MERGE example therefore does not compile against the mssql adapter.
Reproduction
const db = createTypedDb<DB, { placeholders: 'at' }>(createMssqlExecutor(pool), { placeholders: 'at' });
await db.query(
'merge into users using orders on users.id = orders.user_id when matched then update set name = @name output $action',
'x',
);
Two independent errors:
-
Extra argument.
type P = Params<DB, '…output $action'>;
// ^? [unknown, string] (expected: [string] — only @name is a parameter)
With the right arity the call reports Expected 3 arguments, but got 2.
-
Bogus dialect error. With the arity satisfied:
Argument of type '"merge into … output $action"' is not assignable to parameter of type
'"merge into … output $action" & QueryTypeError<"the query placeholder style does not match the executor dialect">'
because UsedPlaceholderStyles<Q> sees the $ in $action and reports 'dollar' for a query whose only real placeholder is @name. MERGE is SQL-Server-only, so the executor is always 'at' — the check fires on every MERGE that outputs $action.
Root cause
src/params.ts:
-
IsPlaceholder — excludes $$…, '$', @@…, but not $action.
-
UsedPlaceholderStyles — Text extends \${string}$${string}` ? 'dollar' : never`, no exclusion at all.
ResolveColumnType already special-cases the pseudo-column via IsMergeActionPseudoColumn (src/parse.ts); the same exclusion is missing on the parameter side. Excluding a case-insensitive $action in IsPlaceholder and stripping it before the 'dollar' test in UsedPlaceholderStyles (the way StripDoubledAt already handles @@) covers both symptoms.
Found in an audit of master @ dc1afdb (v0.1.8).
$actionis a MERGE pseudo-column, not a parameter — the row type already handles it correctly ('INSERT' | 'UPDATE' | 'DELETE', README Limitations). ButIsPlaceholdermatches any token starting with$, so the parameter scanner and the placeholder-style check both treat$actionas a$placeholder. The documented MERGE example therefore does not compile against the mssql adapter.Reproduction
Two independent errors:
Extra argument.
With the right arity the call reports
Expected 3 arguments, but got 2.Bogus dialect error. With the arity satisfied:
because
UsedPlaceholderStyles<Q>sees the$in$actionand reports'dollar'for a query whose only real placeholder is@name. MERGE is SQL-Server-only, so the executor is always'at'— the check fires on every MERGE that outputs$action.Root cause
src/params.ts:IsPlaceholder— excludes$$…,'$',@@…, but not$action.UsedPlaceholderStyles—Text extends \ResolveColumnTypealready special-cases the pseudo-column viaIsMergeActionPseudoColumn(src/parse.ts); the same exclusion is missing on the parameter side. Excluding a case-insensitive$actioninIsPlaceholderand stripping it before the'dollar'test inUsedPlaceholderStyles(the wayStripDoubledAtalready handles@@) covers both symptoms.Found in an audit of
master@ dc1afdb (v0.1.8).