Skip to content

parser: accept a LEADING parenthesized set-operation operand#50

Merged
chiradip merged 1 commit into
mainfrom
feat-leading-paren-setop-operand
Jul 27, 2026
Merged

parser: accept a LEADING parenthesized set-operation operand#50
chiradip merged 1 commit into
mainfrom
feat-leading-paren-setop-operand

Conversation

@chiradip

@chiradip chiradip commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

A statement could begin with a parenthesized query block only as the right-hand operand of a set operation. A leading ( at statement level — the canonical (SELECT 1) UNION (SELECT 2) — was never dispatched: parse_statement bailed out the moment it saw a token that was neither a keyword nor an identifier, so the whole statement failed to parse. This closes that gap.

Change

  • Extract the set-operation fold out of parse_select_stmt into a member fold_set_operations(first_operand), and the trailing ORDER BY / LIMIT attach into attach_trailing_order_limit(target). This is a pure extraction — no change to precedence (INTERSECT binds tighter than UNION/EXCEPT/MINUS), left-associativity, ALL-modifier placement, or trailing-clause binding.
  • Add parse_parenthesized_query() for a ( <query> ) operand, reused by both the RHS-operand path and the new leading path. It is DepthGuard-protected and recurses for nested ((...)).
  • parse_statement now routes a leading ( through parse_parenthesized_query() then fold_set_operations(), so a leading parenthesized operand folds like any other and a trailing ORDER BY / LIMIT binds to the whole result.

Parentheses correctly override the INTERSECT-binds-tighter precedence: (A UNION B) INTERSECT C roots at INTERSECT with the UNION as its left operand — the opposite grouping from the unparenthesized A UNION B INTERSECT C.

Verification

  • 5 new regression tests pin the AST shape for the leading-paren cases: both-paren, paren-left/bare-right, precedence override, nested + trailing ORDER BY, and lone-paren unwrap. Falsifiable — all 5 fail with the dispatch hook reverted and pass with it.
  • Malformed / adversarial inputs reject gracefully (no crash); leftover-token leniency is identical to the unparenthesized forms (parity, not new laxness).
  • 200k-deep ((((...)))) nesting is rejected via the DepthGuard — no stack overflow.
  • Full parser suite 37/37; the precedence suite (21 tests) is clean under ASan/UBSan.

Scope

Top-level leading ( only. A leading ( after a WITH clause (WITH t AS (...) (SELECT 1) UNION (SELECT 2)) remains a separate, rarer follow-up.

A statement could begin with a parenthesized query block only in the RHS
of a set operation; a LEADING '(' at statement level -- e.g.
`(SELECT 1) UNION (SELECT 2)` -- was never dispatched (parse_statement
bailed on the non-keyword/identifier token) and the whole statement
failed to parse.

Change:
- Extract the set-operation fold out of parse_select_stmt into a member
  `fold_set_operations(first_operand)`, and the trailing ORDER BY / LIMIT
  attach into `attach_trailing_order_limit(target)`. Pure extraction --
  no change to precedence (INTERSECT tighter than UNION/EXCEPT), left-
  associativity, ALL-modifier placement, or trailing-clause binding.
- Add `parse_parenthesized_query()` for a `( <query> )` operand, reused by
  both the RHS operand path and the new leading path. It is DepthGuard-
  protected and recurses for nested `((...))`.
- parse_statement now dispatches a leading '(' to
  parse_parenthesized_query() then fold_set_operations(), so a leading
  parenthesized operand folds like any other and a trailing ORDER BY /
  LIMIT binds to the whole result.

Parentheses correctly override the INTERSECT-binds-tighter precedence:
`(A UNION B) INTERSECT C` roots at INTERSECT with the UNION as its left
operand, the opposite grouping from the unparenthesized form.

Verification:
- 5 new regression tests pinning AST shape for leading-paren cases
  (both-paren, paren-left/bare-right, precedence override, nested +
  trailing ORDER BY, lone-paren unwrap). Falsifiable: all 5 fail with the
  dispatch hook reverted, pass with it.
- Malformed/adversarial inputs reject gracefully (no crash); leftover-
  token leniency is identical to the unparenthesized forms (parity).
- 200k-deep `((((...))))` nesting is rejected via the DepthGuard, no
  stack overflow.
- Full parser suite 37/37; precedence suite (21 tests) clean under
  ASan/UBSan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FyKDAbMWGwiZq4iJx3imsg
@chiradip
chiradip merged commit 3e2b70f into main Jul 27, 2026
8 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

Development

Successfully merging this pull request may close these issues.

1 participant