fix(where): validate the trailing operand of a WHERE clause in strict mode - #147
Merged
tiagolauer merged 1 commit intoJul 23, 2026
Merged
Conversation
… mode
WhereScan recurses on `${Head} ${Tail}`, so it only ever fired
ValidateWhereOperand on a token that had a following operator (or another
space) after it. The final token of a clause has no trailing space and no
subsequent operator, so it fell through to the `never` base case and was
never validated — a false negative that silently accepted typo'd columns as
the trailing operand (e.g. `where age = naem`), the mirror of the LHS case
that already errored.
Add a terminal branch: when no space remains, `S` is the last operand;
validate `CleanColumnToken<S>` the same way the operator branch validates
`Prev`. `not` stays transparent, and empty/placeholder operands are already
short-circuited inside ValidateWhereOperand, so literals, placeholders, and
`is [not] null` tails keep resolving. Preserves today's IsTransparentToken
(NOT) and CleanColumnToken behavior.
Regression tests: trailing-RHS typo now errors, LHS case still errors, and
valid trailing column/literal operands still resolve.
Fixes tiagolauer#128
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tiagolauer
pushed a commit
that referenced
this pull request
Jul 23, 2026
In `WhereScan`, `and`/`or` were neither trigger operators nor transparent tokens, so the RHS operand of the comparison immediately before them (held in `Prev`) was overwritten by the next token without ever reaching `ValidateWhereOperand`. Only operands sitting directly before a validating operator (or, since #147, at the very end of the clause) were checked, so a typo'd column mid-clause slipped through: `where age = naem and id = 1` passed strict mode while the trailing `where age = naem` was caught (#148). Treat `and`/`or` as validation boundaries: when the scanner consumes one, validate the accumulated `Prev` exactly as the operator branch does, then reset scanning state so the next comparison's LHS operand starts fresh and is still validated by the existing flow. `between`'s syntactic `and` is covered too, but its bounds are literals or real columns, so the check is harmless. NOT transparency, `CleanColumnToken` cleaning, the #147 terminal branch, and the parenthesized/IN-list forms are all preserved. Adds strict-mode locks for the mid-clause RHS repro pair (bad column now errors, valid column still resolves), an `or` variant, the LHS-after-boundary case, and a NOT-form interaction. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
tiagolauer
added a commit
that referenced
this pull request
Jul 25, 2026
fix(where): validate the trailing operand of a WHERE clause in strict mode
tiagolauer
pushed a commit
that referenced
this pull request
Jul 25, 2026
In `WhereScan`, `and`/`or` were neither trigger operators nor transparent tokens, so the RHS operand of the comparison immediately before them (held in `Prev`) was overwritten by the next token without ever reaching `ValidateWhereOperand`. Only operands sitting directly before a validating operator (or, since #147, at the very end of the clause) were checked, so a typo'd column mid-clause slipped through: `where age = naem and id = 1` passed strict mode while the trailing `where age = naem` was caught (#148). Treat `and`/`or` as validation boundaries: when the scanner consumes one, validate the accumulated `Prev` exactly as the operator branch does, then reset scanning state so the next comparison's LHS operand starts fresh and is still validated by the existing flow. `between`'s syntactic `and` is covered too, but its bounds are literals or real columns, so the check is harmless. NOT transparency, `CleanColumnToken` cleaning, the #147 terminal branch, and the parenthesized/IN-list forms are all preserved. Adds strict-mode locks for the mid-clause RHS repro pair (bad column now errors, valid column still resolves), an `or` variant, the LHS-after-boundary case, and a NOT-form interaction.
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 #128
WhereScanrecurses on${infer Head} ${infer Tail}, which only fires while a trailing space remains — so the final token of a clause fell through to the unconditionalneverbase case and was never fed toValidateWhereOperand(the false negative from the issue:where age = naempassed strict mode). The base case is now a terminal branch: when no space remains, the remaining string IS the last operand — validated viaCleanColumnToken<S>, mirroring how the operator branch validatesPrev, withnotkept transparent viaIsTransparentToken<S>. (The issue's line refs predate today's #144/#145 merges; reconciled against current master, and both of those behaviors keep their locks green.)Tests:
UnknownColumnOnComparisonRhs(the repro — fails on current master withType 'false' does not satisfy 'true', passes with the fix) plus two over-rejection guards: a valid trailing column and a trailing literal both still resolve. All 23 pre-existingWhereStrictLockentries green.Gates:
npx tsc --noEmit -p tsconfig.jsonexit 0 (exit 2 before the fix, single new assert failing); runtime vitest (non-ts-plugin) 108 tests green.One adjacent gap surfaced while working on this, deliberately NOT touched here — filing it as a separate issue: an RHS operand followed by
and/oris also unvalidated mid-clause (and/oraren't transparent, so the operand before them never reaches validation).Generated by Claude Fable 5 (brief, review), Claude Opus 4.8 (implementation)