Skip to content

fix(where): validate the trailing operand of a WHERE clause in strict mode - #147

Merged
tiagolauer merged 1 commit into
tiagolauer:masterfrom
Nitjsefnie-OSC:fix/where-trailing-operand
Jul 23, 2026
Merged

fix(where): validate the trailing operand of a WHERE clause in strict mode#147
tiagolauer merged 1 commit into
tiagolauer:masterfrom
Nitjsefnie-OSC:fix/where-trailing-operand

Conversation

@Nitjsefnie

Copy link
Copy Markdown
Contributor

Fixes #128

WhereScan recurses on ${infer Head} ${infer Tail}, which only fires while a trailing space remains — so the final token of a clause fell through to the unconditional never base case and was never fed to ValidateWhereOperand (the false negative from the issue: where age = naem passed strict mode). The base case is now a terminal branch: when no space remains, the remaining string IS the last operand — validated via CleanColumnToken<S>, mirroring how the operator branch validates Prev, with not kept transparent via IsTransparentToken<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 with Type '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-existing WhereStrictLock entries green.

Gates: npx tsc --noEmit -p tsconfig.json exit 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/or is also unvalidated mid-clause (and/or aren't transparent, so the operand before them never reaches validation).

Generated by Claude Fable 5 (brief, review), Claude Opus 4.8 (implementation)

… 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
tiagolauer merged commit 557ba3f into tiagolauer:master Jul 23, 2026
6 checks passed
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.
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.

WHERE clause: trailing operand in a clause is never validated in strict mode (false negative)

2 participants