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
}
}
- 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.
- Dispatch statements starting with
( to parseSelectQuery in parseStmt (parser/parser_table.go).
- Fixtures under
parser/testdata/query/.
Summary
A set operator after a parenthesized SELECT never binds:
parseSelectQuerychecks forUNION/EXCEPT/INTERSECTbefore consuming the closing), so any parenthesized left operand is rejected.Reproduces on v0.5.5 and on master (
44047b0).Reproduction
Rejected:
Accepted:
ClickHouse accepts these
Every rejected statement above runs on 26.8.1.337.
Proposed fix
parseSelectQueryinparser/parser_query.go:(, recurse intoparseSelectQueryand consume)first, then bind the set operator, attaching the right operand to the tail of the chain the left operand already carries.(toparseSelectQueryinparseStmt(parser/parser_table.go).parser/testdata/query/.