Skip to content

Commit 60101ec

Browse files
ajitpratap0Ajit Pratap Singh
andauthored
feat(parser): Snowflake USE and DESCRIBE object-kind prefixes (#483) (#491)
Co-authored-by: Ajit Pratap Singh <ajitpratapsingh@Ajits-Mac-mini-2655.local>
1 parent 6318d7d commit 60101ec

3 files changed

Lines changed: 88 additions & 1 deletion

File tree

pkg/sql/parser/mysql.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,23 @@ func (p *Parser) parseDescribeStatement() (ast.Statement, error) {
164164
return desc, nil
165165
}
166166

167+
// Snowflake: DESCRIBE TABLE <name>, DESCRIBE VIEW <name>, DESCRIBE STAGE
168+
// <name>, etc. Also MySQL's DESCRIBE <db>.<table>. Accept and consume a
169+
// leading object-kind keyword (TABLE, VIEW, DATABASE, SCHEMA) before the
170+
// name so we don't fail on "DESCRIBE TABLE users".
171+
if p.isType(models.TokenTypeTable) || p.isType(models.TokenTypeView) ||
172+
p.isType(models.TokenTypeDatabase) ||
173+
strings.EqualFold(p.currentToken.Token.Value, "SCHEMA") ||
174+
strings.EqualFold(p.currentToken.Token.Value, "STAGE") ||
175+
strings.EqualFold(p.currentToken.Token.Value, "STREAM") ||
176+
strings.EqualFold(p.currentToken.Token.Value, "TASK") ||
177+
strings.EqualFold(p.currentToken.Token.Value, "PIPE") ||
178+
strings.EqualFold(p.currentToken.Token.Value, "FUNCTION") ||
179+
strings.EqualFold(p.currentToken.Token.Value, "PROCEDURE") ||
180+
strings.EqualFold(p.currentToken.Token.Value, "WAREHOUSE") {
181+
p.advance()
182+
}
183+
167184
name, err := p.parseQualifiedName()
168185
if err != nil {
169186
return nil, p.expectedError("table name")

pkg/sql/parser/parser.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,9 @@ func (p *Parser) parseStatement() (ast.Statement, error) {
691691
case models.TokenTypeShow:
692692
p.advance()
693693
return p.parseShowStatement()
694-
case models.TokenTypeDescribe, models.TokenTypeExplain:
694+
case models.TokenTypeDescribe, models.TokenTypeExplain, models.TokenTypeDesc:
695+
// DESC is the ORDER-BY sort-direction token but also a synonym for
696+
// DESCRIBE at statement position (Oracle, Snowflake, MySQL).
695697
p.advance()
696698
return p.parseDescribeStatement()
697699
case models.TokenTypeReplace:
@@ -713,10 +715,39 @@ func (p *Parser) parseStatement() (ast.Statement, error) {
713715
p.advance()
714716
return p.parsePragmaStatement()
715717
}
718+
// Snowflake session-context switches: USE [WAREHOUSE|DATABASE|SCHEMA|ROLE] <name>.
719+
// USE is not tokenized as a keyword; dispatch by value in the Snowflake dialect.
720+
if p.dialect == string(keywords.DialectSnowflake) &&
721+
strings.EqualFold(p.currentToken.Token.Value, "USE") {
722+
return p.parseSnowflakeUseStatement()
723+
}
716724
}
717725
return nil, p.expectedError("statement")
718726
}
719727

728+
// parseSnowflakeUseStatement parses:
729+
//
730+
// USE [WAREHOUSE | DATABASE | SCHEMA | ROLE] <name>
731+
//
732+
// The object-kind keyword is optional (plain "USE <name>" switches the current
733+
// database). We parse-only; the statement is represented as a DescribeStatement
734+
// placeholder until a dedicated UseStatement node is introduced.
735+
func (p *Parser) parseSnowflakeUseStatement() (ast.Statement, error) {
736+
p.advance() // Consume USE
737+
// Optional object kind.
738+
switch strings.ToUpper(p.currentToken.Token.Value) {
739+
case "WAREHOUSE", "DATABASE", "SCHEMA", "ROLE":
740+
p.advance()
741+
}
742+
name, err := p.parseQualifiedName()
743+
if err != nil {
744+
return nil, p.expectedError("name after USE")
745+
}
746+
stmt := ast.GetDescribeStatement()
747+
stmt.TableName = "USE " + name
748+
return stmt, nil
749+
}
750+
720751
// NewParser creates a new parser with optional configuration.
721752
func NewParser(opts ...ParserOption) *Parser {
722753
p := &Parser{}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// Copyright 2026 GoSQLX Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
5+
package parser_test
6+
7+
import (
8+
"testing"
9+
10+
"github.com/ajitpratap0/GoSQLX/pkg/gosqlx"
11+
"github.com/ajitpratap0/GoSQLX/pkg/sql/keywords"
12+
)
13+
14+
// TestSnowflakeUseAndDescribe verifies Snowflake session-context statements
15+
// (USE WAREHOUSE/DATABASE/SCHEMA/ROLE) and DESCRIBE/DESC with object-kind
16+
// prefixes. Regression for #483.
17+
func TestSnowflakeUseAndDescribe(t *testing.T) {
18+
queries := []string{
19+
`USE WAREHOUSE compute_wh`,
20+
`USE DATABASE my_db`,
21+
`USE SCHEMA analytics`,
22+
`USE ROLE analyst`,
23+
`USE my_db`,
24+
`USE my_db.public`,
25+
`DESCRIBE TABLE users`,
26+
`DESCRIBE VIEW user_summary`,
27+
`DESCRIBE STAGE my_stage`,
28+
`DESC TABLE users`,
29+
`DESC users`,
30+
}
31+
for _, q := range queries {
32+
q := q
33+
t.Run(q, func(t *testing.T) {
34+
if _, err := gosqlx.ParseWithDialect(q, keywords.DialectSnowflake); err != nil {
35+
t.Fatalf("parse failed: %v", err)
36+
}
37+
})
38+
}
39+
}

0 commit comments

Comments
 (0)