Skip to content

oracle: parse PIVOT/UNPIVOT inside the table_reference chain#373

Merged
rebelice merged 3 commits into
mainfrom
fix-oracle-pivot-alias
Jul 9, 2026
Merged

oracle: parse PIVOT/UNPIVOT inside the table_reference chain#373
rebelice merged 3 commits into
mainfrom
fix-oracle-pivot-alias

Conversation

@rebelice

@rebelice rebelice commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

PIVOT/UNPIVOT were parsed at the select level after the whole FROM clause, retro-attaching the last from-item as Source. Oracle's grammar places them inside table_reference:

table_reference ::= query_table_expression [pivot_clause | unpivot_clause] [t_alias]

so a pivoted result can carry a trailing bare-identifier alias and participate in joins. The old placement rejected both:

SELECT p.a FROM (SELECT a, b, m FROM t1) PIVOT (SUM(b) FOR m IN (1, 2)) p ORDER BY p.a
SELECT p.a FROM t1 PIVOT (SUM(b) FOR m IN (1, 2)) p JOIN d ON p.a = d.a

Reported via a PL/SQL function using OPEN ... FOR (subquery) PIVOT (...) alias, which failed to parse while being valid Oracle.

Fix

  • New parsePivotSuffix attaches PIVOT/UNPIVOT (and the optional t_alias) to the just-parsed table reference; wired into all three table_reference entry paths (plain table, subquery, parenthesized table reference). The clause becomes the from-item itself — PivotClause/UnpivotClause already implement TableExpr and already had Alias/Source fields.
  • AS p and column alias lists p(x, y) after the pivot stay rejected — Oracle raises ORA-03048 on both (t_alias takes neither).
  • Removed the dead select-level branch, SelectStmt.Pivot/Unpivot fields, their outfuncs writes, and regenerated the walker. This is an intentional fail-loud API break for consumers reading SelectStmt.Pivot; the Bytebase span extractor already handles the from-item form (case *ast.PivotClause) and will be updated in the bump PR.

Verification

Every accept/reject shape in pivot_alias_test.go was run against Oracle 23ai Free: the 5 positive shapes (table/subquery/XML pivot + alias, unpivot + alias, pivot-then-join) execute cleanly, the 2 negative shapes fail with ORA-03048, and the PL/SQL cursor function compiles with no errors. Full ./oracle/... suite passes.

PIVOT and UNPIVOT were parsed at the select level after the whole FROM
clause, retro-attaching the last from-item as Source. That misplaces them
in the grammar: Oracle's table_reference is

    query_table_expression [pivot_clause | unpivot_clause] [t_alias]

so the pivoted result can carry a trailing bare-identifier alias and
participate in joins. The old shape rejected both:

    SELECT p.a FROM (SELECT a, b, m FROM t1) PIVOT (SUM(b) FOR m IN (1, 2)) p
    SELECT ... FROM t1 PIVOT (...) p JOIN d ON p.a = d.a

Move the parse into the table_reference chain (plain table, subquery, and
parenthesized table reference paths) via parsePivotSuffix, which attaches
the clause to its source and consumes the optional t_alias. AS aliases and
column alias lists stay rejected, matching Oracle (ORA-03048; t_alias
takes neither). Remove the now-dead SelectStmt.Pivot/Unpivot fields, their
outfuncs writes, and the generated walker edges; the clauses already
implement TableExpr and appear as from-items.

All accept/reject shapes in the new tests verified against Oracle 23ai
Free, including the PL/SQL OPEN ... FOR cursor context the report came
from.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1eada5e6cd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread oracle/parser/select.go
Comment thread oracle/parser/select.go Outdated
Comment thread oracle/parser/select.go Outdated
Address three review findings, each adjudicated against Oracle 23ai Free:

- Wire parsePivotSuffix into the query_table_expression paths that
  returned early: TABLE() collections, XMLTABLE, JSON_TABLE,
  CONTAINERS/SHARDS, and MATCH_RECOGNIZE (standalone and post-table) —
  Oracle accepts PIVOT/UNPIVOT after all of them. LATERAL stays
  unwired (ORA-56905: pivot not supported on lateral views) and inline
  EXTERNAL tables sit outside query_table_expression in the BNF.
- Gate the suffix on the clause body actually following (PIVOT + '('
  or XML; UNPIVOT + '(' or INCLUDE/EXCLUDE), and make the clause
  parsers hard-error on a missing '(' instead of returning a partial
  node — 't PIVOT p' no longer parses as an empty pivot aliased p
  (Oracle: ORA-03049). PIVOT/UNPIVOT are non-reserved, so a bare
  trailing PIVOT is an alias position in Oracle; we keep rejecting
  that degenerate form, as before this change.
- Emit :source from writePivotClause/writeUnpivotClause: the base
  relation now lives only in the clause's Source field, so outfuncs
  must serialize it or NodeToString loses the table.

Also loop the suffix: Oracle chains pivot/unpivot clauses with
optional per-step aliases (t PIVOT (...) a PIVOT (...) b), nesting
each clause as the next one's Source. New tests cover the full
source-form × pivot/unpivot matrix, chaining, and the reject shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 02967b4176

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread oracle/parser/select.go
Comment thread oracle/parser/select.go
Comment thread oracle/parser/select.go
@rebelice rebelice enabled auto-merge (squash) July 9, 2026 10:23
Round-2 review findings, adjudicated against Oracle 23ai Free:

- FOR and IN are mandatory inside pivot_clause/unpivot_clause (Oracle:
  ORA-02000 missing FOR, ORA-01738 missing IN), as is the closing
  paren. The parsers previously treated them as optional, so malformed
  bodies like PIVOT (SUM(b)) parsed and then took a trailing alias.
- Oracle accepts a pivot AFTER an aliased source (t alias PIVOT (...)
  p) even though the documented grammar orders the alias last; TABLE()
  and the other wrapped sources already handled this via their internal
  alias parse, but the plain-table, subquery, and parenthesized paths
  returned right after the alias and rejected valid SQL. All three now
  tail into parsePivotSuffix, which no-ops when nothing follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rebelice rebelice merged commit 72e9a03 into main Jul 9, 2026
21 checks passed
rebelice added a commit to bytebase/bytebase that referenced this pull request Jul 10, 2026
omni now parses PIVOT/UNPIVOT inside the table_reference chain
(bytebase/omni#373): the clauses appear as from-items carrying their
source and optional trailing t_alias, and SelectStmt.Pivot/Unpivot are
removed. The span extractor already handles the from-item form, so the
dead select-level branches go away, along with the redundant
isSimpleOracleSelect conjuncts (a pivot from-item is not a DUAL
TableRef, so the FROM check already excludes it).

This makes the customer-reported shape parse end to end:

    OPEN cur FOR
      SELECT p.a FROM (SELECT ...) PIVOT (SUM(b) FOR m IN (...)) p
      ORDER BY p.a;

New parity tests cover the trailing-alias and pivot-join shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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