Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
162 changes: 136 additions & 26 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ type CreateQuery struct {
Populate bool `json:"populate,omitempty"` // POPULATE for materialized views
Columns []*ColumnDeclaration `json:"columns,omitempty"`
Indexes []*IndexDefinition `json:"indexes,omitempty"`
Projections []*Projection `json:"projections,omitempty"`
Constraints []*Constraint `json:"constraints,omitempty"`
Engine *EngineClause `json:"engine,omitempty"`
OrderBy []Expression `json:"order_by,omitempty"`
Expand Down Expand Up @@ -467,19 +468,24 @@ func (t *TTLClause) End() token.Position { return t.Position }

// DropQuery represents a DROP statement.
type DropQuery struct {
Position token.Position `json:"-"`
IfExists bool `json:"if_exists,omitempty"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
Tables []*TableIdentifier `json:"tables,omitempty"` // For DROP TABLE t1, t2, t3
View string `json:"view,omitempty"`
User string `json:"user,omitempty"`
Function string `json:"function,omitempty"` // For DROP FUNCTION
Dictionary string `json:"-"` // For DROP DICTIONARY (format only, not in AST JSON)
Temporary bool `json:"temporary,omitempty"`
OnCluster string `json:"on_cluster,omitempty"`
DropDatabase bool `json:"drop_database,omitempty"`
Sync bool `json:"sync,omitempty"`
Position token.Position `json:"-"`
IfExists bool `json:"if_exists,omitempty"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
Tables []*TableIdentifier `json:"tables,omitempty"` // For DROP TABLE t1, t2, t3
View string `json:"view,omitempty"`
User string `json:"user,omitempty"`
Function string `json:"function,omitempty"` // For DROP FUNCTION
Dictionary string `json:"-"` // For DROP DICTIONARY (format only, not in AST JSON)
Role string `json:"role,omitempty"` // For DROP ROLE
Quota string `json:"quota,omitempty"` // For DROP QUOTA
Policy string `json:"policy,omitempty"` // For DROP POLICY
RowPolicy string `json:"row_policy,omitempty"` // For DROP ROW POLICY
SettingsProfile string `json:"settings_profile,omitempty"` // For DROP SETTINGS PROFILE
Temporary bool `json:"temporary,omitempty"`
OnCluster string `json:"on_cluster,omitempty"`
DropDatabase bool `json:"drop_database,omitempty"`
Sync bool `json:"sync,omitempty"`
}

func (d *DropQuery) Pos() token.Position { return d.Position }
Expand Down Expand Up @@ -542,7 +548,7 @@ type ProjectionSelectQuery struct {
Position token.Position `json:"-"`
Columns []Expression `json:"columns"`
GroupBy []Expression `json:"group_by,omitempty"`
OrderBy *Identifier `json:"order_by,omitempty"` // Single column for ORDER BY
OrderBy []Expression `json:"order_by,omitempty"` // ORDER BY columns
}

// Assignment represents a column assignment in UPDATE.
Expand Down Expand Up @@ -657,14 +663,15 @@ func (d *DescribeQuery) statementNode() {}

// ShowQuery represents a SHOW statement.
type ShowQuery struct {
Position token.Position `json:"-"`
ShowType ShowType `json:"show_type"`
Database string `json:"database,omitempty"`
From string `json:"from,omitempty"`
Like string `json:"like,omitempty"`
Where Expression `json:"where,omitempty"`
Limit Expression `json:"limit,omitempty"`
Format string `json:"format,omitempty"`
Position token.Position `json:"-"`
ShowType ShowType `json:"show_type"`
Database string `json:"database,omitempty"`
From string `json:"from,omitempty"`
Like string `json:"like,omitempty"`
Where Expression `json:"where,omitempty"`
Limit Expression `json:"limit,omitempty"`
Format string `json:"format,omitempty"`
HasSettings bool `json:"has_settings,omitempty"` // Whether SETTINGS clause was specified
}

func (s *ShowQuery) Pos() token.Position { return s.Position }
Expand Down Expand Up @@ -901,6 +908,104 @@ func (s *ShowCreateSettingsProfileQuery) Pos() token.Position { return s.Positio
func (s *ShowCreateSettingsProfileQuery) End() token.Position { return s.Position }
func (s *ShowCreateSettingsProfileQuery) statementNode() {}

// CreateRowPolicyQuery represents a CREATE ROW POLICY or ALTER ROW POLICY statement.
type CreateRowPolicyQuery struct {
Position token.Position `json:"-"`
IsAlter bool `json:"is_alter,omitempty"`
}

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

// DropRowPolicyQuery represents a DROP ROW POLICY statement.
type DropRowPolicyQuery struct {
Position token.Position `json:"-"`
IfExists bool `json:"if_exists,omitempty"`
}

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

// ShowCreateRowPolicyQuery represents a SHOW CREATE ROW POLICY statement.
type ShowCreateRowPolicyQuery struct {
Position token.Position `json:"-"`
}

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

// CreateRoleQuery represents a CREATE ROLE or ALTER ROLE statement.
type CreateRoleQuery struct {
Position token.Position `json:"-"`
IsAlter bool `json:"is_alter,omitempty"`
}

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

// DropRoleQuery represents a DROP ROLE statement.
type DropRoleQuery struct {
Position token.Position `json:"-"`
IfExists bool `json:"if_exists,omitempty"`
}

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

// ShowCreateRoleQuery represents a SHOW CREATE ROLE statement.
type ShowCreateRoleQuery struct {
Position token.Position `json:"-"`
RoleCount int `json:"role_count,omitempty"` // Number of roles specified
}

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

// CreateResourceQuery represents a CREATE RESOURCE statement.
type CreateResourceQuery struct {
Position token.Position `json:"-"`
Name string `json:"name"`
}

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

// DropResourceQuery represents a DROP RESOURCE statement.
type DropResourceQuery struct {
Position token.Position `json:"-"`
}

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

// CreateWorkloadQuery represents a CREATE WORKLOAD statement.
type CreateWorkloadQuery struct {
Position token.Position `json:"-"`
Name string `json:"name"`
Parent string `json:"parent,omitempty"` // Parent workload name (after IN)
}

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

// DropWorkloadQuery represents a DROP WORKLOAD statement.
type DropWorkloadQuery struct {
Position token.Position `json:"-"`
}

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

// CreateIndexQuery represents a CREATE INDEX statement.
type CreateIndexQuery struct {
Position token.Position `json:"-"`
Expand Down Expand Up @@ -959,6 +1064,7 @@ type Literal struct {
Position token.Position `json:"-"`
Type LiteralType `json:"type"`
Value interface{} `json:"value"`
Negative bool `json:"negative,omitempty"` // True if literal was explicitly negative (for -0)
}

func (l *Literal) Pos() token.Position { return l.Position }
Expand Down Expand Up @@ -1036,11 +1142,15 @@ type ReplaceExpr struct {
func (r *ReplaceExpr) Pos() token.Position { return r.Position }
func (r *ReplaceExpr) End() token.Position { return r.Position }

// ColumnsMatcher represents COLUMNS('pattern') expression.
// ColumnsMatcher represents COLUMNS('pattern') or COLUMNS(col1, col2) expression.
// When Pattern is set, it's a regex matcher (ColumnsRegexpMatcher in explain).
// When Columns is set, it's a list matcher (ColumnsListMatcher in explain).
type ColumnsMatcher struct {
Position token.Position `json:"-"`
Pattern string `json:"pattern"`
Except []string `json:"except,omitempty"`
Position token.Position `json:"-"`
Pattern string `json:"pattern,omitempty"`
Columns []Expression `json:"columns,omitempty"` // For COLUMNS(id, name) syntax
Except []string `json:"except,omitempty"`
Qualifier string `json:"qualifier,omitempty"` // For qualified matchers like table.COLUMNS(...)
}

func (c *ColumnsMatcher) Pos() token.Position { return c.Position }
Expand Down
37 changes: 36 additions & 1 deletion internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
case *ast.Asterisk:
explainAsterisk(sb, n, indent, depth)
case *ast.ColumnsMatcher:
fmt.Fprintf(sb, "%sColumnsRegexpMatcher\n", indent)
explainColumnsMatcher(sb, n, indent, depth)

// Functions
case *ast.FunctionCall:
Expand Down Expand Up @@ -143,6 +143,41 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
} else {
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILE query\n", indent)
}
case *ast.CreateRowPolicyQuery:
fmt.Fprintf(sb, "%sCREATE ROW POLICY or ALTER ROW POLICY query\n", indent)
case *ast.DropRowPolicyQuery:
fmt.Fprintf(sb, "%sDROP ROW POLICY query\n", indent)
case *ast.ShowCreateRowPolicyQuery:
fmt.Fprintf(sb, "%sSHOW CREATE ROW POLICY query\n", indent)
case *ast.CreateRoleQuery:
fmt.Fprintf(sb, "%sCreateRoleQuery\n", indent)
case *ast.DropRoleQuery:
fmt.Fprintf(sb, "%sDROP ROLE query\n", indent)
case *ast.ShowCreateRoleQuery:
// Use ROLES (plural) when multiple roles are specified
if n.RoleCount > 1 {
fmt.Fprintf(sb, "%sSHOW CREATE ROLES query\n", indent)
} else {
fmt.Fprintf(sb, "%sSHOW CREATE ROLE query\n", indent)
}
case *ast.CreateResourceQuery:
fmt.Fprintf(sb, "%sCreateResourceQuery %s (children 1)\n", indent, n.Name)
childIndent := indent + " "
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
case *ast.DropResourceQuery:
fmt.Fprintf(sb, "%sDropResourceQuery\n", indent)
case *ast.CreateWorkloadQuery:
childIndent := indent + " "
if n.Parent != "" {
fmt.Fprintf(sb, "%sCreateWorkloadQuery %s (children 2)\n", indent, n.Name)
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Parent}}, childIndent)
} else {
fmt.Fprintf(sb, "%sCreateWorkloadQuery %s (children 1)\n", indent, n.Name)
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
}
case *ast.DropWorkloadQuery:
fmt.Fprintf(sb, "%sDropWorkloadQuery\n", indent)
case *ast.ShowGrantsQuery:
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
case *ast.GrantQuery:
Expand Down
35 changes: 35 additions & 0 deletions internal/explain/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,41 @@ func explainColumnsTransformers(sb *strings.Builder, n *ast.Asterisk, indent str
}
}

func explainColumnsMatcher(sb *strings.Builder, n *ast.ColumnsMatcher, indent string, depth int) {
// Determine the matcher type based on whether it's a pattern or a list
if len(n.Columns) > 0 {
// ColumnsListMatcher for COLUMNS(col1, col2, ...)
typeName := "ColumnsListMatcher"
if n.Qualifier != "" {
typeName = "QualifiedColumnsListMatcher"
}
if n.Qualifier != "" {
// QualifiedColumnsListMatcher has qualifier as a child
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 2)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Qualifier)
} else {
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 1)
}
// Output the columns as ExpressionList
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Columns))
for _, col := range n.Columns {
Node(sb, col, depth+2)
}
} else {
// ColumnsRegexpMatcher for COLUMNS('pattern')
typeName := "ColumnsRegexpMatcher"
if n.Qualifier != "" {
typeName = "QualifiedColumnsRegexpMatcher"
}
if n.Qualifier != "" {
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 1)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Qualifier)
} else {
fmt.Fprintf(sb, "%s%s\n", indent, typeName)
}
}
}

func explainWithElement(sb *strings.Builder, n *ast.WithElement, indent string, depth int) {
// For WITH elements, we need to show the underlying expression with the name as alias
// When name is empty, don't show the alias part
Expand Down
24 changes: 24 additions & 0 deletions internal/explain/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,10 +354,34 @@ func UnaryOperatorToFunction(op string) string {
func formatExprAsString(expr ast.Expression) string {
switch e := expr.(type) {
case *ast.Literal:
// Handle explicitly negative literals (like -0 in -0::Int16)
prefix := ""
if e.Negative {
prefix = "-"
}
switch e.Type {
case ast.LiteralInteger:
// For explicitly negative literals, show the absolute value with prefix
if e.Negative {
switch v := e.Value.(type) {
case int64:
if v <= 0 {
return fmt.Sprintf("-%d", -v)
}
case uint64:
return fmt.Sprintf("-%d", v)
}
}
return fmt.Sprintf("%d", e.Value)
case ast.LiteralFloat:
if e.Negative {
switch v := e.Value.(type) {
case float64:
if v <= 0 {
return fmt.Sprintf("%s%v", prefix, -v)
}
}
}
return fmt.Sprintf("%v", e.Value)
case ast.LiteralString:
return e.Value.(string)
Expand Down
24 changes: 24 additions & 0 deletions internal/explain/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func explainCastExprWithAlias(sb *strings.Builder, n *ast.CastExpr, alias string
exprStr := formatExprAsString(lit)
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, exprStr)
}
} else if negatedLit := extractNegatedLiteral(n.Expr); negatedLit != "" {
// Handle negated literal like -0::Int16 -> CAST('-0', 'Int16')
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, negatedLit)
} else {
// Complex expression - use normal AST node
Node(sb, n.Expr, depth+2)
Expand Down Expand Up @@ -333,6 +336,27 @@ func exprToLiteral(expr ast.Expression) *ast.Literal {
return nil
}

// extractNegatedLiteral checks if expr is a negated literal (like -0, -12)
// and returns its string representation (like "-0", "-12") for :: cast expressions.
// Returns empty string if not a negated literal.
func extractNegatedLiteral(expr ast.Expression) string {
unary, ok := expr.(*ast.UnaryExpr)
if !ok || unary.Op != "-" {
return ""
}
lit, ok := unary.Operand.(*ast.Literal)
if !ok {
return ""
}
switch lit.Type {
case ast.LiteralInteger:
return "-" + formatExprAsString(lit)
case ast.LiteralFloat:
return "-" + formatExprAsString(lit)
}
return ""
}

func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) {
// IN is represented as Function in
fnName := "in"
Expand Down
Loading