Skip to content

parser: keep parenthesized set-op operand and bind trailing ORDER BY/LIMIT to the whole set#49

Merged
chiradip merged 1 commit into
mainfrom
fix-setop-paren-operand-and-trailing-orderby
Jul 27, 2026
Merged

parser: keep parenthesized set-op operand and bind trailing ORDER BY/LIMIT to the whole set#49
chiradip merged 1 commit into
mainfrom
fix-setop-paren-operand-and-trailing-orderby

Conversation

@chiradip

@chiradip chiradip commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

What

Two set-operation parsing defects (parser robustness audit):

  1. Parenthesized right operand silently dropped. parse_setop_operand only accepted a bare SELECT keyword, so SELECT 1 UNION (SELECT 2) returned just SELECT 1 and abandoned UNION (SELECT 2) as trailing tokens. It now also accepts a parenthesized query block ( <query> ) — which may itself be a set operation and keeps its own ORDER BY/LIMIT — consuming the matching ).

  2. Trailing ORDER BY/LIMIT bound to the right branch, not the whole set. The right operand (parsed via parse_select_stmt in in_setop_rhs_ mode) consumed the ORDER BY/LIMIT before the set-op-rhs early return, so SELECT 1 UNION SELECT 2 ORDER BY 1 attached ORDER BY to the RHS SELECT and then failed validation. The ORDER BY/LIMIT parsing is factored into a lambda, skipped for bare set-op operands, and re-run once after folding so it attaches to the combined set-op node. A plain SELECT is unaffected (consumes its clauses on the first pass; the post-fold pass is a no-op).

Before / after

  • SELECT 1 UNION (SELECT 2) — before: SelectStmt(1) (4 trailing tokens); after: UnionStmt → [SelectStmt(1), SelectStmt(2)].
  • SELECT 1 UNION SELECT 2 ORDER BY 1 — before: PARSE ERROR: validation failed; after: UnionStmt → [SelectStmt(1), SelectStmt(2), OrderByClause].

Known remaining gap (follow-up)

A leading parenthesized operand (SELECT 1) UNION (SELECT 2) still errors — parse_select_stmt's entry point doesn't yet accept a leading (. That's a separate, more invasive change and is tracked as a follow-up; this PR is scoped to the two confirmed silent-drop / mis-parse findings.

Tests

test_precedence_regression: parenthesized right operand keeps both branches; trailing ORDER BY and ORDER BY + LIMIT attach to the UNION node; a plain SELECT still attaches its own ORDER BY/LIMIT (regression guard). Full suite 37/37 green.

Completes the parser-layer set-operation robustness (CASE #46, IS DISTINCT FROM #47, IN #48 already up/merged).

…LIMIT to the whole set

Two set-operation parsing defects (parser robustness audit):

1. A parenthesized right operand was silently dropped. parse_setop_operand only
   accepted a bare SELECT keyword, so `SELECT 1 UNION (SELECT 2)` returned just
   `SELECT 1` and abandoned `UNION (SELECT 2)` as trailing tokens. It now also
   accepts a parenthesized query block `( <query> )` (which may itself be a set
   operation and keeps its own ORDER BY / LIMIT), consuming the matching ')'.

2. A trailing ORDER BY / LIMIT bound to the right branch instead of the whole
   set operation. The right operand (parsed via parse_select_stmt in
   in_setop_rhs_ mode) consumed the ORDER BY / LIMIT before the set-op-rhs early
   return, so `SELECT 1 UNION SELECT 2 ORDER BY 1` attached ORDER BY to the RHS
   SELECT and then failed validation. The ORDER BY / LIMIT parsing is factored
   into a lambda, skipped for bare set-op operands, and re-run once after folding
   so it attaches to the combined set-op node. A plain SELECT is unaffected (it
   consumes its clauses on the first pass; the post-fold pass is a no-op).

Known remaining gap (separate, more invasive entry-point change; tracked as a
follow-up): a LEADING parenthesized operand `(SELECT 1) UNION (SELECT 2)` still
errors, because parse_select_stmt's entry does not yet accept a leading '('.

Tests (test_precedence_regression): parenthesized right operand keeps both
branches; trailing ORDER BY and ORDER BY + LIMIT attach to the UNION node; a
plain SELECT still attaches its own ORDER BY / LIMIT. Full suite 37/37 green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FyKDAbMWGwiZq4iJx3imsg
@chiradip
chiradip merged commit 9c3d4a3 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.

2 participants