Skip to content

Set operations with three or more branches drop every middle branch #60

Description

@gmr

Every style drops the middle branch of a UNION / INTERSECT / EXCEPT chain with three or more branches. Confirmed on the published v1.4.0 and on main.

SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3;
-- formats to
SELECT 1 UNION ALL SELECT 3;
SELECT 1 UNION SELECT 2 EXCEPT SELECT 3 INTERSECT SELECT 4;
-- formats to
SELECT 1 EXCEPT SELECT 3 INTERSECT SELECT 4;

The output parses and runs, and returns different rows.

Cause

A UNION B UNION C parses left-nested: ((A UNION B) UNION C). collect_clauses_recursive() recurses into the left select_clause, sets clauses.set_op to UNION B, then returns to the outer level and sets clauses.set_op to UNION C, replacing it. SelectClauses holds one set operation, so B is lost.

Right-nested chains (A UNION (B INTERSECT C), which is how INTERSECT's higher precedence parses) are not affected, because the right side is collected recursively with its own set_op.

Fix

Append the outer operation to the end of the existing chain instead of replacing it. The formatter emits the branches as a flat sequence in source order, and PostgreSQL applies the same precedence when it parses that sequence, so the chain's grouping does not need to be kept.

Found by the token-loss guard added in #59; part of #58.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions