Skip to content

[bug] parenthesized left operand of UNION/EXCEPT/INTERSECT is rejected #311

Description

@therealpandey

Summary

A set operator after a parenthesized SELECT never binds: parseSelectQuery checks for UNION/EXCEPT/INTERSECT before consuming the closing ), so any parenthesized left operand is rejected.

Reproduces on v0.5.5 and on master (44047b0).

Reproduction

_, err := parser.NewParser("SELECT a FROM ((SELECT 1 AS a) UNION ALL (SELECT 2 AS a))").ParseStmts()
line 1:32 expected ')', but got '<keyword>'
SELECT a FROM ((SELECT 1 AS a) UNION ALL (SELECT 2 AS a))
                               ^^^^^

Rejected:

SELECT a FROM ((SELECT 1 AS a) UNION ALL (SELECT 2 AS a))       -- expected ')', but got '<keyword>'
SELECT a FROM ((SELECT 1 AS a) UNION DISTINCT (SELECT 2 AS a))  -- expected ')', but got '<keyword>'
SELECT a FROM ((SELECT 1 AS a) EXCEPT (SELECT 2 AS a))          -- expected ')', but got '<keyword>'
SELECT a FROM ((SELECT 1 AS a) INTERSECT (SELECT 2 AS a))       -- expected ')', but got '<keyword>'
(SELECT 1 AS a) UNION ALL SELECT 2 AS a                         -- unexpected token: "("
SELECT 1 UNION ALL (SELECT 2) UNION ALL SELECT 3                -- <EOF> or ';' was expected, but got: "UNION"
SELECT a FROM (((SELECT 1 AS a)))                               -- expected keyword <"SELECT">, but got '('

Accepted:

SELECT a FROM (SELECT 1 AS a UNION ALL SELECT 2 AS a)
SELECT a FROM (SELECT 1 AS a UNION ALL (SELECT 2 AS a))
SELECT a FROM ((SELECT 1 AS a))
SELECT 1 AS a UNION ALL (SELECT 2 AS a)

ClickHouse accepts these

clickhouse local -q "SELECT a FROM ((SELECT 1 AS a) UNION ALL (SELECT 2 AS a))"

Every rejected statement above runs on 26.8.1.337.

Proposed fix

parseSelectQuery in parser/parser_query.go:

hasParen := p.tryConsumeTokenKind(TokenKindLParen) != nil
selectStmt, err := p.parseSelectStmt(p.Pos())
if err != nil {
	return nil, err
}
switch {
case p.tryConsumeKeywords(KeywordUnion):
	// ...
}
if hasParen {
	if err := p.expectTokenKind(TokenKindRParen); err != nil {
		return nil, err
	}
}
  1. On (, recurse into parseSelectQuery and consume ) first, then bind the set operator, attaching the right operand to the tail of the chain the left operand already carries.
  2. Dispatch statements starting with ( to parseSelectQuery in parseStmt (parser/parser_table.go).
  3. Fixtures under parser/testdata/query/.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions