Skip to content

Fix #284 and #285 — quoted operand may head arithmetic; a dangling dot no longer eats the next word - #286

Merged
fupelaqu merged 1 commit into
mainfrom
fix/284-285-quoted-name-productions
Sep 6, 2026
Merged

Fix #284 and #285 — quoted operand may head arithmetic; a dangling dot no longer eats the next word#286
fupelaqu merged 1 commit into
mainfrom
fix/284-285-quoted-name-productions

Conversation

@fupelaqu

@fupelaqu fupelaqu commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

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` + 1 failed with end of input expected, in both spellings, while SELECT (`amount` + 1) and SELECT MAX(`amount` + 1) worked.

The four clause-level productions (SelectParser.field, GroupByParser.bucketWithFunction, OrderByParser.fieldWithFunction, WhereParser.any_identifier) list quotedIdentifier ahead of identifierWithArithmeticExpression, 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 lookaheadquotedIdentifierUnlessArithmetic declines to match only when an arithmetic operator is genuinely next. not(...) consumes nothing, so every other input reaches quotedIdentifier exactly as before.

Deliberately NOT applied to quotedIdentifier itself. In operand position — inside identifierWithIntervalFunction, reached from factor — matching the bare name is precisely what lets arithmeticExpressionLevel1s 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. 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

nameTail was rep("." ~> part), and RegexParsers skips whitespace before every terminal, so a name ending in a dot absorbed whatever followed:

SELECT a FROM t ORDER BY b. DESC   -- parsed as ORDER BY "b.DESC" ASC  <- direction lost, SILENTLY

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 . b widening 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, so SELECT a .b reads as a.b. That is a tolerance of an odd spelling — nothing is lost, no clause is mis-parsed.

Verification

Suite Result
sql/test 655 green — on both 2.13.16 and 2.12.20 (+ sql/test)
core/test 868 green
softclient4es-sql-bridge/test 129 green (run alone — a batched run can print [success] with no Tests: line on a stale generated tree)
macrosTests/test 19 green
scalafmtCheckAll, headerCheck clean

+ core/compile green on both versions. No es{6,7,8,9} source is touched; build.sbt is untouched (0.23.0-SNAPSHOT stays).

Two test rows I had to correct while writing this

Recorded because each looked like a code failure and was not:

  • ``SELECT 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.
  • The render of the fixed arithmetic carries the canonical quoting, so it is "amount" + 1, not amount + 1.

Notes

  • No new issues are filed by this PR. Anything found while writing it is fixed in it.
  • Documentation: the arithmetic limitation is removed from known_limitations.md (it is fixed) and the dot-adjacency rule is documented in its place, with the reason.

🤖 Generated with Claude Code

…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
fupelaqu marked this pull request as ready for review September 6, 2026 00:11
@fupelaqu
fupelaqu merged commit e55bd93 into main Sep 6, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant