fix(params): stop accepting null for a comparison parameter - #333
Merged
Conversation
// email: string | null
Params<DB, 'select id from users where email = $1'> // was [string | null]
db.query('select id from users where email = $1', null); // compiled, matched nothing
`= NULL` is never true, so the type layer was signing off on a query that
silently returns no rows - the class of mistake this library exists to catch.
The LEFT JOIN case shows where the null actually comes from: `| null` on a
joined column means a missing match makes it null *in the output*, which says
nothing about what you may compare it to.
Comparison parameters are now NonNullable. Three carve-outs, because null is
meaningful in them:
- `is distinct from`, whose whole purpose is comparing against null
- SET assignments and VALUES tuples, where storing null is ordinary. The scan
keeps only the two previous tokens, so `set a = $1, b = $2` gives the second
placeholder no local hint that it is still a write - the region is carried
along the scan instead.
- an unresolved column, which stays `unknown`: NonNullable<unknown> is `{}`,
which would have rejected the null such a slot is still allowed to take.
Costs 1,506 instantiations (192,486 -> 193,992, 97% of the budget).
Fixes #299
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
tiagolauer
force-pushed
the
fix/299-nullable-param
branch
from
August 2, 2026 13:03
7278a7a to
91e3d92
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #299.
The bug
Join-induced nullability leaks the same way, and shows the mechanism plainly — the
| nullbelongs to the result side (a missing match makes the column null in the output), not to the value you compare against:The fix
ParamTyperesolves throughResolveColumnLoose, which reuses result-column resolution verbatim. Comparison parameters are nowNonNullable, with three carve-outs where null is meaningful:is distinct from— comparing against null is the entire point of it.SETassignments andVALUEStuples — storing null is ordinary. The scan keeps only the two previous tokens, soset a = $1, b = $2gives the second placeholder no local hint that it is still a write; the region is carried along the scan instead.unknown.NonNullable<unknown>is{}, which would have rejected the null such a slot is still allowed to take. This one was caught by an existing test (tests/update-where-subquery.test-d.ts) going red mid-change, not by foresight.Verification
tests/nullable-params.test-d.ts, eleven cases. Four red on master:=,like,in, and the LEFT JOIN column all drop the nullis distinct from,update ... set, every assignment in a multi-column SET, andinsert ... values= any($1)still wraps tonumber[],limit $1is stillnumbertsc --noEmitclean, 192 runtime tests pass. Budget 193,992 (+1,506).