Skip to content

Commit c73dd25

Browse files
committed
Fix REPLACE modifier, CREATE AS with subquery, INTERSECT as implicit alias
- Allow REPLACE modifier without parentheses: SELECT * REPLACE expr AS col - Support parenthesized subqueries in CREATE TABLE AS clause - Prevent INTERSECT from being consumed as implicit alias Parse errors: 61 -> 57 Skipped tests: 777
1 parent 5e9cd52 commit c73dd25

2 files changed

Lines changed: 35 additions & 7 deletions

File tree

parser/expression.go

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,13 @@ func (p *Parser) parseFunctionArgumentList() []ast.Expression {
165165
func (p *Parser) parseImplicitAlias(expr ast.Expression) ast.Expression {
166166
// If next token is a plain identifier (not a keyword), treat as implicit alias
167167
// Keywords like FROM, WHERE etc. are tokenized as their own token types, not IDENT
168+
// INTERSECT is not a keyword but should not be treated as an alias
168169
if p.currentIs(token.IDENT) {
170+
upper := strings.ToUpper(p.current.Value)
171+
// Don't consume SQL set operation keywords that aren't tokens
172+
if upper == "INTERSECT" {
173+
return expr
174+
}
169175
alias := p.current.Value
170176
p.nextToken()
171177

@@ -1709,16 +1715,29 @@ func (p *Parser) parseAsteriskExcept(asterisk *ast.Asterisk) ast.Expression {
17091715
func (p *Parser) parseAsteriskReplace(asterisk *ast.Asterisk) ast.Expression {
17101716
p.nextToken() // skip REPLACE
17111717

1712-
if !p.expect(token.LPAREN) {
1713-
return asterisk
1718+
// REPLACE can have optional parentheses: REPLACE (expr AS col) or REPLACE expr AS col
1719+
hasParens := p.currentIs(token.LPAREN)
1720+
if hasParens {
1721+
p.nextToken()
17141722
}
17151723

1716-
for !p.currentIs(token.RPAREN) && !p.currentIs(token.EOF) {
1724+
for {
1725+
// Stop conditions based on context
1726+
if hasParens && p.currentIs(token.RPAREN) {
1727+
break
1728+
}
1729+
if !hasParens && (p.currentIs(token.FROM) || p.currentIs(token.WHERE) || p.currentIs(token.EOF) ||
1730+
p.currentIs(token.GROUP) || p.currentIs(token.ORDER) || p.currentIs(token.HAVING) ||
1731+
p.currentIs(token.LIMIT) || p.currentIs(token.SETTINGS) || p.currentIs(token.FORMAT) ||
1732+
p.currentIs(token.UNION) || p.currentIs(token.EXCEPT) || p.currentIs(token.COMMA)) {
1733+
break
1734+
}
1735+
17171736
replace := &ast.ReplaceExpr{
17181737
Position: p.current.Pos,
17191738
}
17201739

1721-
replace.Expr = p.parseExpression(LOWEST)
1740+
replace.Expr = p.parseExpression(ALIAS_PREC)
17221741

17231742
if p.currentIs(token.AS) {
17241743
p.nextToken()
@@ -1732,10 +1751,18 @@ func (p *Parser) parseAsteriskReplace(asterisk *ast.Asterisk) ast.Expression {
17321751

17331752
if p.currentIs(token.COMMA) {
17341753
p.nextToken()
1754+
// If no parens and we see comma, might be end of select column
1755+
if !hasParens {
1756+
break
1757+
}
1758+
} else if !hasParens {
1759+
break
17351760
}
17361761
}
17371762

1738-
p.expect(token.RPAREN)
1763+
if hasParens {
1764+
p.expect(token.RPAREN)
1765+
}
17391766

17401767
return asterisk
17411768
}

parser/parser.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1288,10 +1288,11 @@ func (p *Parser) parseCreateTable(create *ast.CreateQuery) {
12881288
}
12891289
done_table_options:
12901290

1291-
// Parse AS SELECT or AS table_function() or AS database.table
1291+
// Parse AS SELECT or AS (subquery) or AS table_function() or AS database.table
12921292
if p.currentIs(token.AS) {
12931293
p.nextToken()
1294-
if p.currentIs(token.SELECT) || p.currentIs(token.WITH) {
1294+
if p.currentIs(token.SELECT) || p.currentIs(token.WITH) || p.currentIs(token.LPAREN) {
1295+
// AS SELECT... or AS (SELECT...) INTERSECT ...
12951296
create.AsSelect = p.parseSelectWithUnion()
12961297
} else if p.currentIs(token.IDENT) || p.current.Token.IsKeyword() {
12971298
// AS table_function(...) or AS database.table

0 commit comments

Comments
 (0)