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
41 changes: 41 additions & 0 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,47 @@ func (s *ShowCreateQuotaQuery) Pos() token.Position { return s.Position }
func (s *ShowCreateQuotaQuery) End() token.Position { return s.Position }
func (s *ShowCreateQuotaQuery) statementNode() {}

// CreateSettingsProfileQuery represents a CREATE SETTINGS PROFILE statement.
type CreateSettingsProfileQuery struct {
Position token.Position `json:"-"`
Names []string `json:"names,omitempty"`
}

func (c *CreateSettingsProfileQuery) Pos() token.Position { return c.Position }
func (c *CreateSettingsProfileQuery) End() token.Position { return c.Position }
func (c *CreateSettingsProfileQuery) statementNode() {}

// AlterSettingsProfileQuery represents an ALTER SETTINGS PROFILE statement.
type AlterSettingsProfileQuery struct {
Position token.Position `json:"-"`
Names []string `json:"names,omitempty"`
}

func (a *AlterSettingsProfileQuery) Pos() token.Position { return a.Position }
func (a *AlterSettingsProfileQuery) End() token.Position { return a.Position }
func (a *AlterSettingsProfileQuery) statementNode() {}

// DropSettingsProfileQuery represents a DROP SETTINGS PROFILE statement.
type DropSettingsProfileQuery struct {
Position token.Position `json:"-"`
Names []string `json:"names,omitempty"`
IfExists bool `json:"if_exists,omitempty"`
}

func (d *DropSettingsProfileQuery) Pos() token.Position { return d.Position }
func (d *DropSettingsProfileQuery) End() token.Position { return d.Position }
func (d *DropSettingsProfileQuery) statementNode() {}

// ShowCreateSettingsProfileQuery represents a SHOW CREATE SETTINGS PROFILE statement.
type ShowCreateSettingsProfileQuery struct {
Position token.Position `json:"-"`
Names []string `json:"names,omitempty"`
}

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

// CreateIndexQuery represents a CREATE INDEX statement.
type CreateIndexQuery struct {
Position token.Position `json:"-"`
Expand Down
14 changes: 14 additions & 0 deletions internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,20 @@ 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.CreateSettingsProfileQuery:
fmt.Fprintf(sb, "%sCreateSettingsProfileQuery\n", indent)
case *ast.AlterSettingsProfileQuery:
// ALTER SETTINGS PROFILE uses CreateSettingsProfileQuery in ClickHouse's explain
fmt.Fprintf(sb, "%sCreateSettingsProfileQuery\n", indent)
case *ast.DropSettingsProfileQuery:
fmt.Fprintf(sb, "%sDROP SETTINGS PROFILE query\n", indent)
case *ast.ShowCreateSettingsProfileQuery:
// Use PROFILES (plural) when multiple profiles are specified
if len(n.Names) > 1 {
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILES query\n", indent)
} else {
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILE query\n", indent)
}
case *ast.ShowGrantsQuery:
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
case *ast.GrantQuery:
Expand Down
203 changes: 202 additions & 1 deletion parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,28 @@ func (p *Parser) parseStatement() ast.Statement {
case token.CREATE:
return p.parseCreate()
case token.DROP:
// Check for DROP SETTINGS PROFILE
if p.peekIs(token.SETTINGS) {
return p.parseDropSettingsProfile()
}
// Check for DROP PROFILE
if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "PROFILE" {
return p.parseDropSettingsProfile()
}
return p.parseDrop()
case token.ALTER:
// Check for ALTER USER
if p.peekIs(token.USER) {
return p.parseAlterUser()
}
// Check for ALTER SETTINGS PROFILE
if p.peekIs(token.SETTINGS) {
return p.parseAlterSettingsProfile()
}
// Check for ALTER PROFILE
if p.peek.Token == token.IDENT && strings.ToUpper(p.peek.Value) == "PROFILE" {
return p.parseAlterSettingsProfile()
}
return p.parseAlter()
case token.TRUNCATE:
return p.parseTruncate()
Expand Down Expand Up @@ -1314,6 +1330,9 @@ func (p *Parser) parseCreate() ast.Statement {
create.CreateUser = true
p.nextToken()
p.parseCreateUser(create)
case token.SETTINGS:
// CREATE SETTINGS PROFILE
return p.parseCreateSettingsProfile(pos)
case token.IDENT:
// Handle CREATE DICTIONARY, CREATE RESOURCE, CREATE WORKLOAD, CREATE NAMED COLLECTION, etc.
identUpper := strings.ToUpper(p.current.Value)
Expand All @@ -1330,7 +1349,10 @@ func (p *Parser) parseCreate() ast.Statement {
p.nextToken()
}
p.parseCreateGeneric(create)
case "RESOURCE", "WORKLOAD", "POLICY", "ROLE", "QUOTA", "PROFILE":
case "PROFILE":
// CREATE PROFILE (without SETTINGS keyword)
return p.parseCreateSettingsProfile(pos)
case "RESOURCE", "WORKLOAD", "POLICY", "ROLE", "QUOTA":
// Skip these statements - just consume tokens until semicolon
p.parseCreateGeneric(create)
default:
Expand Down Expand Up @@ -1866,6 +1888,182 @@ func (p *Parser) parseCreateGeneric(create *ast.CreateQuery) {
}
}

func (p *Parser) parseCreateSettingsProfile(pos token.Position) *ast.CreateSettingsProfileQuery {
query := &ast.CreateSettingsProfileQuery{
Position: pos,
}

// Skip SETTINGS if present (CREATE SETTINGS PROFILE vs CREATE PROFILE)
if p.currentIs(token.SETTINGS) {
p.nextToken()
}

// Skip PROFILE
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" {
p.nextToken()
}

// Handle IF NOT EXISTS
if p.currentIs(token.IF) {
p.nextToken()
if p.currentIs(token.NOT) {
p.nextToken()
}
if p.currentIs(token.EXISTS) {
p.nextToken()
}
}

// Parse profile names (can be multiple: s1, s2, s3)
for {
name := p.parseIdentifierName()
if name != "" {
query.Names = append(query.Names, name)
}
if p.currentIs(token.COMMA) {
p.nextToken()
continue
}
break
}

// Skip the rest of the statement (SETTINGS, TO, etc.)
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return query
}

func (p *Parser) parseDropSettingsProfile() *ast.DropSettingsProfileQuery {
query := &ast.DropSettingsProfileQuery{
Position: p.current.Pos,
}

p.nextToken() // skip DROP

// Skip SETTINGS if present (DROP SETTINGS PROFILE vs DROP PROFILE)
if p.currentIs(token.SETTINGS) {
p.nextToken()
}

// Skip PROFILE
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" {
p.nextToken()
}

// Handle IF EXISTS
if p.currentIs(token.IF) {
p.nextToken()
if p.currentIs(token.EXISTS) {
query.IfExists = true
p.nextToken()
}
}

// Parse profile names (can be multiple: s1, s2, s3)
for {
name := p.parseIdentifierName()
if name != "" {
query.Names = append(query.Names, name)
}
if p.currentIs(token.COMMA) {
p.nextToken()
continue
}
break
}

// Skip the rest of the statement
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return query
}

func (p *Parser) parseAlterSettingsProfile() *ast.AlterSettingsProfileQuery {
query := &ast.AlterSettingsProfileQuery{
Position: p.current.Pos,
}

p.nextToken() // skip ALTER

// Skip SETTINGS if present (ALTER SETTINGS PROFILE vs ALTER PROFILE)
if p.currentIs(token.SETTINGS) {
p.nextToken()
}

// Skip PROFILE
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" {
p.nextToken()
}

// Handle IF EXISTS
if p.currentIs(token.IF) {
p.nextToken()
if p.currentIs(token.EXISTS) {
p.nextToken()
}
}

// Parse profile names (can be multiple: s1, s2, s3)
for {
name := p.parseIdentifierName()
if name != "" {
query.Names = append(query.Names, name)
}
if p.currentIs(token.COMMA) {
p.nextToken()
continue
}
break
}

// Skip the rest of the statement (SETTINGS, RENAME TO, etc.)
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return query
}

func (p *Parser) parseShowCreateSettingsProfile(pos token.Position) *ast.ShowCreateSettingsProfileQuery {
query := &ast.ShowCreateSettingsProfileQuery{
Position: pos,
}

// Skip SETTINGS if present (SHOW CREATE SETTINGS PROFILE vs SHOW CREATE PROFILE)
if p.currentIs(token.SETTINGS) {
p.nextToken()
}

// Skip PROFILE
if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE" {
p.nextToken()
}

// Parse profile names (can be multiple: s1, s2, s3)
for {
name := p.parseIdentifierName()
if name != "" {
query.Names = append(query.Names, name)
}
if p.currentIs(token.COMMA) {
p.nextToken()
continue
}
break
}

// Skip the rest of the statement
for !p.currentIs(token.EOF) && !p.currentIs(token.SEMICOLON) {
p.nextToken()
}

return query
}

func (p *Parser) parseCreateDictionary(create *ast.CreateQuery) {
// Handle IF NOT EXISTS
if p.currentIs(token.IF) {
Expand Down Expand Up @@ -3358,6 +3556,9 @@ func (p *Parser) parseShow() ast.Statement {
p.nextToken()
}
return &ast.ShowCreateQuotaQuery{Position: pos, Name: name}
} else if p.currentIs(token.SETTINGS) || (p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "PROFILE") {
// SHOW CREATE SETTINGS PROFILE or SHOW CREATE PROFILE
return p.parseShowCreateSettingsProfile(pos)
} else if p.currentIs(token.IDENT) && strings.ToUpper(p.current.Value) == "DICTIONARY" {
show.ShowType = ast.ShowCreateDictionary
p.nextToken()
Expand Down
Loading