Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,15 +652,17 @@ func (s *ShowQuery) statementNode() {}
type ShowType string

const (
ShowTables ShowType = "TABLES"
ShowDatabases ShowType = "DATABASES"
ShowProcesses ShowType = "PROCESSLIST"
ShowCreate ShowType = "CREATE"
ShowCreateDB ShowType = "CREATE_DATABASE"
ShowColumns ShowType = "COLUMNS"
ShowDictionaries ShowType = "DICTIONARIES"
ShowFunctions ShowType = "FUNCTIONS"
ShowSettings ShowType = "SETTINGS"
ShowTables ShowType = "TABLES"
ShowDatabases ShowType = "DATABASES"
ShowProcesses ShowType = "PROCESSLIST"
ShowCreate ShowType = "CREATE"
ShowCreateDB ShowType = "CREATE_DATABASE"
ShowCreateDictionary ShowType = "CREATE_DICTIONARY"
ShowCreateView ShowType = "CREATE_VIEW"
ShowColumns ShowType = "COLUMNS"
ShowDictionaries ShowType = "DICTIONARIES"
ShowFunctions ShowType = "FUNCTIONS"
ShowSettings ShowType = "SETTINGS"
)

// ExplainQuery represents an EXPLAIN statement.
Expand Down Expand Up @@ -770,17 +772,47 @@ func (e *ExchangeQuery) Pos() token.Position { return e.Position }
func (e *ExchangeQuery) End() token.Position { return e.Position }
func (e *ExchangeQuery) statementNode() {}

// ExistsType represents the type of EXISTS query.
type ExistsType string

const (
ExistsTable ExistsType = "TABLE"
ExistsDictionary ExistsType = "DICTIONARY"
ExistsDatabase ExistsType = "DATABASE"
ExistsView ExistsType = "VIEW"
)

// ExistsQuery represents an EXISTS table_name statement (check if table exists).
type ExistsQuery struct {
Position token.Position `json:"-"`
Database string `json:"database,omitempty"`
Table string `json:"table"`
Position token.Position `json:"-"`
ExistsType ExistsType `json:"exists_type,omitempty"`
Database string `json:"database,omitempty"`
Table string `json:"table"`
}

func (e *ExistsQuery) Pos() token.Position { return e.Position }
func (e *ExistsQuery) End() token.Position { return e.Position }
func (e *ExistsQuery) statementNode() {}

// GrantQuery represents a GRANT or REVOKE statement.
type GrantQuery struct {
Position token.Position `json:"-"`
IsRevoke bool `json:"is_revoke,omitempty"`
}

func (g *GrantQuery) Pos() token.Position { return g.Position }
func (g *GrantQuery) End() token.Position { return g.Position }
func (g *GrantQuery) statementNode() {}

// ShowGrantsQuery represents a SHOW GRANTS statement.
type ShowGrantsQuery struct {
Position token.Position `json:"-"`
}

func (s *ShowGrantsQuery) Pos() token.Position { return s.Position }
func (s *ShowGrantsQuery) End() token.Position { return s.Position }
func (s *ShowGrantsQuery) statementNode() {}

// ShowPrivilegesQuery represents a SHOW PRIVILEGES statement.
type ShowPrivilegesQuery struct {
Position token.Position `json:"-"`
Expand Down
4 changes: 4 additions & 0 deletions internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
fmt.Fprintf(sb, "%sShowPrivilegesQuery\n", indent)
case *ast.ShowCreateQuotaQuery:
fmt.Fprintf(sb, "%sSHOW CREATE QUOTA query\n", indent)
case *ast.ShowGrantsQuery:
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
case *ast.GrantQuery:
fmt.Fprintf(sb, "%sGrantQuery\n", indent)
case *ast.UseQuery:
explainUseQuery(sb, n, indent)
case *ast.DescribeQuery:
Expand Down
57 changes: 55 additions & 2 deletions internal/explain/statements.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,38 @@ func explainShowQuery(sb *strings.Builder, n *ast.ShowQuery, indent string) {
return
}

// SHOW CREATE DICTIONARY has special output format
if n.ShowType == ast.ShowCreateDictionary && (n.Database != "" || n.From != "") {
if n.Database != "" && n.From != "" {
fmt.Fprintf(sb, "%sShowCreateDictionaryQuery %s %s (children 2)\n", indent, n.Database, n.From)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.From)
} else if n.From != "" {
fmt.Fprintf(sb, "%sShowCreateDictionaryQuery %s (children 1)\n", indent, n.From)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.From)
} else if n.Database != "" {
fmt.Fprintf(sb, "%sShowCreateDictionaryQuery %s (children 1)\n", indent, n.Database)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
}
return
}

// SHOW CREATE VIEW has special output format
if n.ShowType == ast.ShowCreateView && (n.Database != "" || n.From != "") {
if n.Database != "" && n.From != "" {
fmt.Fprintf(sb, "%sShowCreateViewQuery %s %s (children 2)\n", indent, n.Database, n.From)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.From)
} else if n.From != "" {
fmt.Fprintf(sb, "%sShowCreateViewQuery %s (children 1)\n", indent, n.From)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.From)
} else if n.Database != "" {
fmt.Fprintf(sb, "%sShowCreateViewQuery %s (children 1)\n", indent, n.Database)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
}
return
}

// SHOW CREATE TABLE has special output format with database and table identifiers
if n.ShowType == ast.ShowCreate && (n.Database != "" || n.From != "") {
// Format: ShowCreateTableQuery database table (children 2)
Expand Down Expand Up @@ -550,12 +582,33 @@ func explainDescribeQuery(sb *strings.Builder, n *ast.DescribeQuery, indent stri
}

func explainExistsTableQuery(sb *strings.Builder, n *ast.ExistsQuery, indent string) {
// EXISTS TABLE/DATABASE/DICTIONARY query
// Determine query type name based on ExistsType
queryType := "ExistsTableQuery"
switch n.ExistsType {
case ast.ExistsDictionary:
queryType = "ExistsDictionaryQuery"
case ast.ExistsDatabase:
queryType = "ExistsDatabaseQuery"
case ast.ExistsView:
queryType = "ExistsViewQuery"
}

// EXISTS DATABASE has only one child (the database name stored in Table)
if n.ExistsType == ast.ExistsDatabase {
name := n.Table
fmt.Fprintf(sb, "%s%s %s (children %d)\n", indent, queryType, name, 1)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
return
}

// For TABLE/DICTIONARY/VIEW, show database and object name
name := n.Table
children := 1
if n.Database != "" {
name = n.Database + " " + n.Table
children = 2
}
fmt.Fprintf(sb, "%sExistsTableQuery %s (children %d)\n", indent, name, 2)
fmt.Fprintf(sb, "%s%s %s (children %d)\n", indent, queryType, name, children)
if n.Database != "" {
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Database)
}
Expand Down
79 changes: 70 additions & 9 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ func (p *Parser) parseStatement() ast.Statement {
return p.parseAttach()
case token.CHECK:
return p.parseCheck()
case token.GRANT:
return p.parseGrant()
case token.REVOKE:
return p.parseRevoke()
default:
p.errors = append(p.errors, fmt.Errorf("unexpected token %s at line %d, column %d",
p.current.Token, p.current.Pos.Line, p.current.Pos.Column))
Expand Down Expand Up @@ -3188,6 +3192,15 @@ func (p *Parser) parseShow() ast.Statement {
return &ast.ShowPrivilegesQuery{Position: pos}
}

// Handle SHOW GRANTS - it has its own statement type
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "GRANTS" {
// Skip all remaining tokens until end of statement
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}
return &ast.ShowGrantsQuery{Position: pos}
}

show := &ast.ShowQuery{
Position: pos,
}
Expand Down Expand Up @@ -3216,6 +3229,12 @@ func (p *Parser) parseShow() ast.Statement {
p.nextToken()
}
return &ast.ShowCreateQuotaQuery{Position: pos, Name: name}
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "DICTIONARY" {
show.ShowType = ast.ShowCreateDictionary
p.nextToken()
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "VIEW" {
show.ShowType = ast.ShowCreateView
p.nextToken()
} else {
show.ShowType = ast.ShowCreate
// Handle SHOW CREATE TABLE, etc.
Expand All @@ -3242,8 +3261,9 @@ func (p *Parser) parseShow() ast.Statement {
}
}

// Parse FROM clause (or table/database name for SHOW CREATE TABLE/DATABASE)
if p.currentIs(token.FROM) || ((show.ShowType == ast.ShowCreate || show.ShowType == ast.ShowCreateDB) && (p.currentIs(token.IDENT) || p.current.Token.IsKeyword())) {
// Parse FROM clause (or table/database name for SHOW CREATE TABLE/DATABASE/DICTIONARY/VIEW)
showCreateTypes := show.ShowType == ast.ShowCreate || show.ShowType == ast.ShowCreateDB || show.ShowType == ast.ShowCreateDictionary || show.ShowType == ast.ShowCreateView
if p.currentIs(token.FROM) || (showCreateTypes && (p.currentIs(token.IDENT) || p.current.Token.IsKeyword())) {
if p.currentIs(token.FROM) {
p.nextToken()
}
Expand Down Expand Up @@ -3921,25 +3941,36 @@ func (p *Parser) parseParenthesizedSelect() *ast.SelectWithUnionQuery {
// parseExistsStatement handles EXISTS table_name syntax
func (p *Parser) parseExistsStatement() *ast.ExistsQuery {
exists := &ast.ExistsQuery{
Position: p.current.Pos,
Position: p.current.Pos,
ExistsType: ast.ExistsTable, // default to TABLE
}

p.nextToken() // skip EXISTS

// Skip optional TABLE keyword
// Check for DICTIONARY, DATABASE, VIEW, or TABLE keyword
if p.currentIs(token.TABLE) {
exists.ExistsType = ast.ExistsTable
p.nextToken()
} else if p.currentIs(token.DATABASE) {
exists.ExistsType = ast.ExistsDatabase
p.nextToken()
} else if p.currentIs(token.VIEW) {
exists.ExistsType = ast.ExistsView
p.nextToken()
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "DICTIONARY" {
exists.ExistsType = ast.ExistsDictionary
p.nextToken()
}

// Parse table name (database.table or just table)
tableName := p.parseIdentifierName()
if tableName != "" {
// Parse table/database/dictionary/view name
name := p.parseIdentifierName()
if name != "" {
if p.currentIs(token.DOT) {
p.nextToken()
exists.Database = tableName
exists.Database = name
exists.Table = p.parseIdentifierName()
} else {
exists.Table = tableName
exists.Table = name
}
}

Expand Down Expand Up @@ -4034,3 +4065,33 @@ func (p *Parser) parseProjection() *ast.Projection {

return proj
}

// parseGrant handles GRANT statements
func (p *Parser) parseGrant() *ast.GrantQuery {
grant := &ast.GrantQuery{
Position: p.current.Pos,
IsRevoke: false,
}

// Skip all tokens until end of statement
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return grant
}

// parseRevoke handles REVOKE statements
func (p *Parser) parseRevoke() *ast.GrantQuery {
grant := &ast.GrantQuery{
Position: p.current.Pos,
IsRevoke: true,
}

// Skip all tokens until end of statement
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return grant
}
7 changes: 0 additions & 7 deletions parser/testdata/01018_ddl_dictionaries_create/metadata.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
{
"explain_todo": {
"stmt11": true,
"stmt12": true,
"stmt13": true,
"stmt14": true,
"stmt17": true,
"stmt18": true,
"stmt19": true,
"stmt22": true,
"stmt23": true,
"stmt24": true,
"stmt28": true,
"stmt29": true,
"stmt35": true,
"stmt36": true,
"stmt37": true,
"stmt38": true,
"stmt43": true,
"stmt47": true,
"stmt48": true,
Expand Down
18 changes: 1 addition & 17 deletions parser/testdata/01048_exists_query/metadata.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
{
"explain_todo": {
"stmt11": true,
"stmt12": true,
"stmt15": true,
"stmt19": true,
"stmt22": true,
"stmt23": true,
"stmt24": true,
"stmt25": true,
"stmt28": true,
"stmt3": true,
"stmt33": true,
"stmt34": true,
"stmt35": true,
"stmt36": true,
"stmt37": true,
"stmt38": true,
"stmt40": true,
"stmt41": true,
"stmt42": true,
"stmt43": true,
"stmt48": true,
"stmt5": true,
"stmt7": true
"stmt35": true
}
}
Loading