Skip to content

Commit fcfae2e

Browse files
ajitpratap0Ajit Pratap Singh
andauthored
feat(parser): Snowflake MINUS fix, CLUSTER BY, COPY GRANTS, CTAS (#483) (#504)
Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local>
1 parent d065a28 commit fcfae2e

File tree

3 files changed

+45
-3
lines changed

3 files changed

+45
-3
lines changed

pkg/sql/parser/ddl.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,29 @@ func (p *Parser) parseCreateTable(temporary bool) (*ast.CreateTableStatement, er
179179
}
180180
stmt.Name = createTableName
181181

182+
// Snowflake: COPY GRANTS modifier before the column list or AS SELECT.
183+
// Consumed but not modeled on the AST.
184+
if strings.EqualFold(p.currentToken.Token.Value, "COPY") &&
185+
strings.EqualFold(p.peekToken().Token.Value, "GRANTS") {
186+
p.advance() // COPY
187+
p.advance() // GRANTS
188+
}
189+
190+
// CREATE TABLE ... AS SELECT — no column list, just a query.
191+
if p.isType(models.TokenTypeAs) {
192+
p.advance() // AS
193+
if p.isType(models.TokenTypeSelect) || p.isType(models.TokenTypeWith) {
194+
p.advance() // SELECT / WITH
195+
query, err := p.parseSelectWithSetOperations()
196+
if err != nil {
197+
return nil, err
198+
}
199+
_ = query // CTAS query not modeled on CreateTableStatement yet
200+
return stmt, nil
201+
}
202+
return nil, p.expectedError("SELECT after AS")
203+
}
204+
182205
// Expect opening parenthesis for column definitions
183206
if !p.isType(models.TokenTypeLParen) {
184207
return nil, p.expectedError("(")
@@ -379,6 +402,19 @@ func (p *Parser) parseCreateTable(temporary bool) (*ast.CreateTableStatement, er
379402
break
380403
}
381404

405+
// Snowflake: CLUSTER BY (expr, ...) — defines the clustering key.
406+
if strings.EqualFold(p.currentToken.Token.Value, "CLUSTER") {
407+
p.advance() // CLUSTER
408+
if p.isType(models.TokenTypeBy) {
409+
p.advance() // BY
410+
}
411+
if p.isType(models.TokenTypeLParen) {
412+
if err := p.skipClickHouseClauseExpr(); err != nil {
413+
return nil, err
414+
}
415+
}
416+
}
417+
382418
// SQLite: optional WITHOUT ROWID clause
383419
if p.isTokenMatch("WITHOUT") {
384420
p.advance() // Consume WITHOUT

pkg/sql/parser/parser.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,9 +1080,14 @@ func (p *Parser) isNonReservedKeyword() bool {
10801080
return false
10811081
}
10821082

1083-
// canBeAlias checks if current token can be used as an alias
1084-
// Aliases can be IDENT, double-quoted identifiers, or certain non-reserved keywords
1083+
// canBeAlias checks if current token can be used as an alias.
1084+
// Aliases can be IDENT, double-quoted identifiers, or certain non-reserved keywords,
1085+
// but NOT contextual clause keywords that would be consumed as aliases by mistake
1086+
// (e.g. MINUS in Snowflake/Oracle, QUALIFY in Snowflake/BigQuery).
10851087
func (p *Parser) canBeAlias() bool {
1088+
if p.isMinusSetOp() || p.isQualifyKeyword() {
1089+
return false
1090+
}
10861091
return p.isIdentifier() || p.isNonReservedKeyword()
10871092
}
10881093

pkg/sql/parser/select_clauses.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ func (p *Parser) parseFromClause() (tableName string, tables []ast.TableReferenc
6161
if !p.isType(models.TokenTypeEOF) &&
6262
!p.isType(models.TokenTypeSemicolon) &&
6363
!p.isType(models.TokenTypeRParen) &&
64-
!p.isAnyType(models.TokenTypeUnion, models.TokenTypeExcept, models.TokenTypeIntersect) {
64+
!p.isAnyType(models.TokenTypeUnion, models.TokenTypeExcept, models.TokenTypeIntersect) &&
65+
!p.isMinusSetOp() {
6566
return "", nil, nil, p.expectedError("FROM, semicolon, or end of statement")
6667
}
6768
return "", nil, nil, nil

0 commit comments

Comments
 (0)