Fix #284 and #285 — quoted operand may head arithmetic; a dangling dot no longer eats the next word - #286
Merged
Conversation
…g dot no longer eats the next word Closes #284 Closes #285 Both defects were found implementing story 21.1 (#252 part 1) and were pinned as accepted limitations rather than fixed. They should not have shipped that way: a PR does not bring new issues with it. #284 — a quoted lexeme at the HEAD of an arithmetic expression was consumed alone and the operator left unconsumed, so `SELECT `amount` + 1` failed with `end of input expected` while `SELECT (`amount` + 1)` and `SELECT MAX(`amount` + 1)` worked. The four clause-level productions list `quotedIdentifier` ahead of `identifierWithArithmeticExpression` and `|` commits to the first SUCCEEDING alternative. That ordering cannot be reversed — it is what makes `SELECT "category"` a column rather than a string literal (21.1 AD-3) — so the fix is a one-token lookahead: `quotedIdentifierUnlessArithmetic` declines to match only when an operator is genuinely next. `not(...)` consumes nothing, so every other input reaches `quotedIdentifier` unchanged. Deliberately NOT applied to `quotedIdentifier` itself: in operand position, inside `identifierWithIntervalFunction`, matching the bare name is exactly what lets `arithmeticExpressionLevel1`'s `rep` pick up the rest of the expression. Guarding it there would push the operand down to `identifierWithValue` and turn it back into a string — the AD-13 corruption in reverse. #285 — `nameTail` was `rep("." ~> part)`, and RegexParsers skips whitespace before every terminal, so a name ending in a dot swallowed the next word: `ORDER BY b. DESC` parsed as `ORDER BY "b.DESC" ASC` and the sort direction vanished SILENTLY. The separator now belongs to `nameTailPartRegex`, which matches the dot together with its part (bare or quoted), so the two must be adjacent. `ORDER BY b. DESC` is now a loud rejection instead of a wrong answer. This reverts the `SELECT a . b` widening story 21.1 had pinned — the adjacency rule is what closes the silent defect, and the spacing is not a spelling anything emits. Residual, accepted and pinned: whitespace BEFORE the dot is still skipped, so `SELECT a .b` reads as `a.b`; nothing is lost and no clause is mis-parsed. Verified: sql 655 and core 868 green on BOTH 2.13.16 and 2.12.20, bridge template 129, macrosTests 19, scalafmtCheckAll and headerCheck clean. Two test rows corrected while writing this — `SELECT `category`, `amount` + 1 ... GROUP BY `category`` is rejected by validate() for the bare spelling too (not a quoting limit, now pinned as a pair), and the render of an arithmetic expression carries the canonical quoting (`"amount" + 1`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fupelaqu
marked this pull request as ready for review
September 6, 2026 00:11
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.
Closes #284
Closes #285
Both defects were found while implementing story 21.1 (#252 part 1) and were pinned as accepted limitations rather than fixed. They should not have shipped that way — a PR does not bring new issues with it. This closes them.
#284 — arithmetic headed by a quoted operand
SELECT `amount` + 1failed withend of input expected, in both spellings, whileSELECT (`amount` + 1)andSELECT MAX(`amount` + 1)worked.The four clause-level productions (
SelectParser.field,GroupByParser.bucketWithFunction,OrderByParser.fieldWithFunction,WhereParser.any_identifier) listquotedIdentifierahead ofidentifierWithArithmeticExpression, and|commits to the first succeeding alternative — so the quoted lexeme was consumed alone and the operator was left unconsumed.That ordering cannot be reversed: it is what makes
SELECT "category"a column rather than a string literal (story 21.1 AD-3). The fix is a one-token lookahead —quotedIdentifierUnlessArithmeticdeclines to match only when an arithmetic operator is genuinely next.not(...)consumes nothing, so every other input reachesquotedIdentifierexactly as before.Deliberately NOT applied to
quotedIdentifieritself. In operand position — insideidentifierWithIntervalFunction, reached fromfactor— matching the bare name is precisely what letsarithmeticExpressionLevel1sreppick up the rest of the expression. Guarding it there would push the operand down toidentifierWithValueand turn it back into a string: the AD-13 corruption in reverse. A dedicated test pins that a quoted identifier with no operator after it is untouched in all four productions.#285 — a dangling dot swallowing the next word
nameTailwasrep("." ~> part), andRegexParsersskips whitespace before every terminal, so a name ending in a dot absorbed whatever followed:The separator now belongs to
nameTailPartRegex, which matches the dot together with its part (bare or quoted), so the two must be adjacent. The statement is now a loud rejection instead of a wrong answer.This reverts the
SELECT a . bwidening story 21.1 had pinned. The adjacency rule is what closes the silent defect, and spaced-out dots are not a spelling anything emits. Both directions are pinned so the revert is a decision.Residual, accepted and pinned: whitespace before the dot is still skipped by the enclosing
rep, soSELECT a .breads asa.b. That is a tolerance of an odd spelling — nothing is lost, no clause is mis-parsed.Verification
sql/test+ sql/test)core/testsoftclient4es-sql-bridge/test[success]with noTests:line on a stale generated tree)macrosTests/testscalafmtCheckAll,headerCheck+ core/compilegreen on both versions. Noes{6,7,8,9}source is touched;build.sbtis untouched (0.23.0-SNAPSHOTstays).Two test rows I had to correct while writing this
Recorded because each looked like a code failure and was not:
category, `amount` + 1 AS a FROM t GROUP BY `category``` is rejected by `validate()` — "Non-aggregated fields ... cannot be selected when GROUP BY is present" — identically for the bare spelling. Not a quoting limit; now pinned as a pair so the two reasons stay distinguishable."amount" + 1, notamount + 1.Notes
known_limitations.md(it is fixed) and the dot-adjacency rule is documented in its place, with the reason.🤖 Generated with Claude Code