Skip to content

Commit f1157ad

Browse files
authored
Add CREATE/ALTER/DROP/SHOW CREATE ROW POLICY support (#106)
1 parent c74d6c6 commit f1157ad

File tree

274 files changed

+1183
-1854
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

274 files changed

+1183
-1854
lines changed

ast/ast.go

Lines changed: 136 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ type CreateQuery struct {
253253
Populate bool `json:"populate,omitempty"` // POPULATE for materialized views
254254
Columns []*ColumnDeclaration `json:"columns,omitempty"`
255255
Indexes []*IndexDefinition `json:"indexes,omitempty"`
256+
Projections []*Projection `json:"projections,omitempty"`
256257
Constraints []*Constraint `json:"constraints,omitempty"`
257258
Engine *EngineClause `json:"engine,omitempty"`
258259
OrderBy []Expression `json:"order_by,omitempty"`
@@ -467,19 +468,24 @@ func (t *TTLClause) End() token.Position { return t.Position }
467468

468469
// DropQuery represents a DROP statement.
469470
type DropQuery struct {
470-
Position token.Position `json:"-"`
471-
IfExists bool `json:"if_exists,omitempty"`
472-
Database string `json:"database,omitempty"`
473-
Table string `json:"table,omitempty"`
474-
Tables []*TableIdentifier `json:"tables,omitempty"` // For DROP TABLE t1, t2, t3
475-
View string `json:"view,omitempty"`
476-
User string `json:"user,omitempty"`
477-
Function string `json:"function,omitempty"` // For DROP FUNCTION
478-
Dictionary string `json:"-"` // For DROP DICTIONARY (format only, not in AST JSON)
479-
Temporary bool `json:"temporary,omitempty"`
480-
OnCluster string `json:"on_cluster,omitempty"`
481-
DropDatabase bool `json:"drop_database,omitempty"`
482-
Sync bool `json:"sync,omitempty"`
471+
Position token.Position `json:"-"`
472+
IfExists bool `json:"if_exists,omitempty"`
473+
Database string `json:"database,omitempty"`
474+
Table string `json:"table,omitempty"`
475+
Tables []*TableIdentifier `json:"tables,omitempty"` // For DROP TABLE t1, t2, t3
476+
View string `json:"view,omitempty"`
477+
User string `json:"user,omitempty"`
478+
Function string `json:"function,omitempty"` // For DROP FUNCTION
479+
Dictionary string `json:"-"` // For DROP DICTIONARY (format only, not in AST JSON)
480+
Role string `json:"role,omitempty"` // For DROP ROLE
481+
Quota string `json:"quota,omitempty"` // For DROP QUOTA
482+
Policy string `json:"policy,omitempty"` // For DROP POLICY
483+
RowPolicy string `json:"row_policy,omitempty"` // For DROP ROW POLICY
484+
SettingsProfile string `json:"settings_profile,omitempty"` // For DROP SETTINGS PROFILE
485+
Temporary bool `json:"temporary,omitempty"`
486+
OnCluster string `json:"on_cluster,omitempty"`
487+
DropDatabase bool `json:"drop_database,omitempty"`
488+
Sync bool `json:"sync,omitempty"`
483489
}
484490

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

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

658664
// ShowQuery represents a SHOW statement.
659665
type ShowQuery struct {
660-
Position token.Position `json:"-"`
661-
ShowType ShowType `json:"show_type"`
662-
Database string `json:"database,omitempty"`
663-
From string `json:"from,omitempty"`
664-
Like string `json:"like,omitempty"`
665-
Where Expression `json:"where,omitempty"`
666-
Limit Expression `json:"limit,omitempty"`
667-
Format string `json:"format,omitempty"`
666+
Position token.Position `json:"-"`
667+
ShowType ShowType `json:"show_type"`
668+
Database string `json:"database,omitempty"`
669+
From string `json:"from,omitempty"`
670+
Like string `json:"like,omitempty"`
671+
Where Expression `json:"where,omitempty"`
672+
Limit Expression `json:"limit,omitempty"`
673+
Format string `json:"format,omitempty"`
674+
HasSettings bool `json:"has_settings,omitempty"` // Whether SETTINGS clause was specified
668675
}
669676

670677
func (s *ShowQuery) Pos() token.Position { return s.Position }
@@ -901,6 +908,104 @@ func (s *ShowCreateSettingsProfileQuery) Pos() token.Position { return s.Positio
901908
func (s *ShowCreateSettingsProfileQuery) End() token.Position { return s.Position }
902909
func (s *ShowCreateSettingsProfileQuery) statementNode() {}
903910

911+
// CreateRowPolicyQuery represents a CREATE ROW POLICY or ALTER ROW POLICY statement.
912+
type CreateRowPolicyQuery struct {
913+
Position token.Position `json:"-"`
914+
IsAlter bool `json:"is_alter,omitempty"`
915+
}
916+
917+
func (c *CreateRowPolicyQuery) Pos() token.Position { return c.Position }
918+
func (c *CreateRowPolicyQuery) End() token.Position { return c.Position }
919+
func (c *CreateRowPolicyQuery) statementNode() {}
920+
921+
// DropRowPolicyQuery represents a DROP ROW POLICY statement.
922+
type DropRowPolicyQuery struct {
923+
Position token.Position `json:"-"`
924+
IfExists bool `json:"if_exists,omitempty"`
925+
}
926+
927+
func (d *DropRowPolicyQuery) Pos() token.Position { return d.Position }
928+
func (d *DropRowPolicyQuery) End() token.Position { return d.Position }
929+
func (d *DropRowPolicyQuery) statementNode() {}
930+
931+
// ShowCreateRowPolicyQuery represents a SHOW CREATE ROW POLICY statement.
932+
type ShowCreateRowPolicyQuery struct {
933+
Position token.Position `json:"-"`
934+
}
935+
936+
func (s *ShowCreateRowPolicyQuery) Pos() token.Position { return s.Position }
937+
func (s *ShowCreateRowPolicyQuery) End() token.Position { return s.Position }
938+
func (s *ShowCreateRowPolicyQuery) statementNode() {}
939+
940+
// CreateRoleQuery represents a CREATE ROLE or ALTER ROLE statement.
941+
type CreateRoleQuery struct {
942+
Position token.Position `json:"-"`
943+
IsAlter bool `json:"is_alter,omitempty"`
944+
}
945+
946+
func (c *CreateRoleQuery) Pos() token.Position { return c.Position }
947+
func (c *CreateRoleQuery) End() token.Position { return c.Position }
948+
func (c *CreateRoleQuery) statementNode() {}
949+
950+
// DropRoleQuery represents a DROP ROLE statement.
951+
type DropRoleQuery struct {
952+
Position token.Position `json:"-"`
953+
IfExists bool `json:"if_exists,omitempty"`
954+
}
955+
956+
func (d *DropRoleQuery) Pos() token.Position { return d.Position }
957+
func (d *DropRoleQuery) End() token.Position { return d.Position }
958+
func (d *DropRoleQuery) statementNode() {}
959+
960+
// ShowCreateRoleQuery represents a SHOW CREATE ROLE statement.
961+
type ShowCreateRoleQuery struct {
962+
Position token.Position `json:"-"`
963+
RoleCount int `json:"role_count,omitempty"` // Number of roles specified
964+
}
965+
966+
func (s *ShowCreateRoleQuery) Pos() token.Position { return s.Position }
967+
func (s *ShowCreateRoleQuery) End() token.Position { return s.Position }
968+
func (s *ShowCreateRoleQuery) statementNode() {}
969+
970+
// CreateResourceQuery represents a CREATE RESOURCE statement.
971+
type CreateResourceQuery struct {
972+
Position token.Position `json:"-"`
973+
Name string `json:"name"`
974+
}
975+
976+
func (c *CreateResourceQuery) Pos() token.Position { return c.Position }
977+
func (c *CreateResourceQuery) End() token.Position { return c.Position }
978+
func (c *CreateResourceQuery) statementNode() {}
979+
980+
// DropResourceQuery represents a DROP RESOURCE statement.
981+
type DropResourceQuery struct {
982+
Position token.Position `json:"-"`
983+
}
984+
985+
func (d *DropResourceQuery) Pos() token.Position { return d.Position }
986+
func (d *DropResourceQuery) End() token.Position { return d.Position }
987+
func (d *DropResourceQuery) statementNode() {}
988+
989+
// CreateWorkloadQuery represents a CREATE WORKLOAD statement.
990+
type CreateWorkloadQuery struct {
991+
Position token.Position `json:"-"`
992+
Name string `json:"name"`
993+
Parent string `json:"parent,omitempty"` // Parent workload name (after IN)
994+
}
995+
996+
func (c *CreateWorkloadQuery) Pos() token.Position { return c.Position }
997+
func (c *CreateWorkloadQuery) End() token.Position { return c.Position }
998+
func (c *CreateWorkloadQuery) statementNode() {}
999+
1000+
// DropWorkloadQuery represents a DROP WORKLOAD statement.
1001+
type DropWorkloadQuery struct {
1002+
Position token.Position `json:"-"`
1003+
}
1004+
1005+
func (d *DropWorkloadQuery) Pos() token.Position { return d.Position }
1006+
func (d *DropWorkloadQuery) End() token.Position { return d.Position }
1007+
func (d *DropWorkloadQuery) statementNode() {}
1008+
9041009
// CreateIndexQuery represents a CREATE INDEX statement.
9051010
type CreateIndexQuery struct {
9061011
Position token.Position `json:"-"`
@@ -959,6 +1064,7 @@ type Literal struct {
9591064
Position token.Position `json:"-"`
9601065
Type LiteralType `json:"type"`
9611066
Value interface{} `json:"value"`
1067+
Negative bool `json:"negative,omitempty"` // True if literal was explicitly negative (for -0)
9621068
}
9631069

9641070
func (l *Literal) Pos() token.Position { return l.Position }
@@ -1036,11 +1142,15 @@ type ReplaceExpr struct {
10361142
func (r *ReplaceExpr) Pos() token.Position { return r.Position }
10371143
func (r *ReplaceExpr) End() token.Position { return r.Position }
10381144

1039-
// ColumnsMatcher represents COLUMNS('pattern') expression.
1145+
// ColumnsMatcher represents COLUMNS('pattern') or COLUMNS(col1, col2) expression.
1146+
// When Pattern is set, it's a regex matcher (ColumnsRegexpMatcher in explain).
1147+
// When Columns is set, it's a list matcher (ColumnsListMatcher in explain).
10401148
type ColumnsMatcher struct {
1041-
Position token.Position `json:"-"`
1042-
Pattern string `json:"pattern"`
1043-
Except []string `json:"except,omitempty"`
1149+
Position token.Position `json:"-"`
1150+
Pattern string `json:"pattern,omitempty"`
1151+
Columns []Expression `json:"columns,omitempty"` // For COLUMNS(id, name) syntax
1152+
Except []string `json:"except,omitempty"`
1153+
Qualifier string `json:"qualifier,omitempty"` // For qualified matchers like table.COLUMNS(...)
10441154
}
10451155

10461156
func (c *ColumnsMatcher) Pos() token.Position { return c.Position }

internal/explain/explain.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
7474
case *ast.Asterisk:
7575
explainAsterisk(sb, n, indent, depth)
7676
case *ast.ColumnsMatcher:
77-
fmt.Fprintf(sb, "%sColumnsRegexpMatcher\n", indent)
77+
explainColumnsMatcher(sb, n, indent, depth)
7878

7979
// Functions
8080
case *ast.FunctionCall:
@@ -143,6 +143,41 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
143143
} else {
144144
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILE query\n", indent)
145145
}
146+
case *ast.CreateRowPolicyQuery:
147+
fmt.Fprintf(sb, "%sCREATE ROW POLICY or ALTER ROW POLICY query\n", indent)
148+
case *ast.DropRowPolicyQuery:
149+
fmt.Fprintf(sb, "%sDROP ROW POLICY query\n", indent)
150+
case *ast.ShowCreateRowPolicyQuery:
151+
fmt.Fprintf(sb, "%sSHOW CREATE ROW POLICY query\n", indent)
152+
case *ast.CreateRoleQuery:
153+
fmt.Fprintf(sb, "%sCreateRoleQuery\n", indent)
154+
case *ast.DropRoleQuery:
155+
fmt.Fprintf(sb, "%sDROP ROLE query\n", indent)
156+
case *ast.ShowCreateRoleQuery:
157+
// Use ROLES (plural) when multiple roles are specified
158+
if n.RoleCount > 1 {
159+
fmt.Fprintf(sb, "%sSHOW CREATE ROLES query\n", indent)
160+
} else {
161+
fmt.Fprintf(sb, "%sSHOW CREATE ROLE query\n", indent)
162+
}
163+
case *ast.CreateResourceQuery:
164+
fmt.Fprintf(sb, "%sCreateResourceQuery %s (children 1)\n", indent, n.Name)
165+
childIndent := indent + " "
166+
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
167+
case *ast.DropResourceQuery:
168+
fmt.Fprintf(sb, "%sDropResourceQuery\n", indent)
169+
case *ast.CreateWorkloadQuery:
170+
childIndent := indent + " "
171+
if n.Parent != "" {
172+
fmt.Fprintf(sb, "%sCreateWorkloadQuery %s (children 2)\n", indent, n.Name)
173+
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
174+
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Parent}}, childIndent)
175+
} else {
176+
fmt.Fprintf(sb, "%sCreateWorkloadQuery %s (children 1)\n", indent, n.Name)
177+
explainIdentifier(sb, &ast.Identifier{Parts: []string{n.Name}}, childIndent)
178+
}
179+
case *ast.DropWorkloadQuery:
180+
fmt.Fprintf(sb, "%sDropWorkloadQuery\n", indent)
146181
case *ast.ShowGrantsQuery:
147182
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
148183
case *ast.GrantQuery:

internal/explain/expressions.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,41 @@ func explainColumnsTransformers(sb *strings.Builder, n *ast.Asterisk, indent str
585585
}
586586
}
587587

588+
func explainColumnsMatcher(sb *strings.Builder, n *ast.ColumnsMatcher, indent string, depth int) {
589+
// Determine the matcher type based on whether it's a pattern or a list
590+
if len(n.Columns) > 0 {
591+
// ColumnsListMatcher for COLUMNS(col1, col2, ...)
592+
typeName := "ColumnsListMatcher"
593+
if n.Qualifier != "" {
594+
typeName = "QualifiedColumnsListMatcher"
595+
}
596+
if n.Qualifier != "" {
597+
// QualifiedColumnsListMatcher has qualifier as a child
598+
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 2)
599+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Qualifier)
600+
} else {
601+
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 1)
602+
}
603+
// Output the columns as ExpressionList
604+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.Columns))
605+
for _, col := range n.Columns {
606+
Node(sb, col, depth+2)
607+
}
608+
} else {
609+
// ColumnsRegexpMatcher for COLUMNS('pattern')
610+
typeName := "ColumnsRegexpMatcher"
611+
if n.Qualifier != "" {
612+
typeName = "QualifiedColumnsRegexpMatcher"
613+
}
614+
if n.Qualifier != "" {
615+
fmt.Fprintf(sb, "%s%s (children %d)\n", indent, typeName, 1)
616+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Qualifier)
617+
} else {
618+
fmt.Fprintf(sb, "%s%s\n", indent, typeName)
619+
}
620+
}
621+
}
622+
588623
func explainWithElement(sb *strings.Builder, n *ast.WithElement, indent string, depth int) {
589624
// For WITH elements, we need to show the underlying expression with the name as alias
590625
// When name is empty, don't show the alias part

internal/explain/format.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,34 @@ func UnaryOperatorToFunction(op string) string {
354354
func formatExprAsString(expr ast.Expression) string {
355355
switch e := expr.(type) {
356356
case *ast.Literal:
357+
// Handle explicitly negative literals (like -0 in -0::Int16)
358+
prefix := ""
359+
if e.Negative {
360+
prefix = "-"
361+
}
357362
switch e.Type {
358363
case ast.LiteralInteger:
364+
// For explicitly negative literals, show the absolute value with prefix
365+
if e.Negative {
366+
switch v := e.Value.(type) {
367+
case int64:
368+
if v <= 0 {
369+
return fmt.Sprintf("-%d", -v)
370+
}
371+
case uint64:
372+
return fmt.Sprintf("-%d", v)
373+
}
374+
}
359375
return fmt.Sprintf("%d", e.Value)
360376
case ast.LiteralFloat:
377+
if e.Negative {
378+
switch v := e.Value.(type) {
379+
case float64:
380+
if v <= 0 {
381+
return fmt.Sprintf("%s%v", prefix, -v)
382+
}
383+
}
384+
}
361385
return fmt.Sprintf("%v", e.Value)
362386
case ast.LiteralString:
363387
return e.Value.(string)

internal/explain/functions.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,9 @@ func explainCastExprWithAlias(sb *strings.Builder, n *ast.CastExpr, alias string
143143
exprStr := formatExprAsString(lit)
144144
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, exprStr)
145145
}
146+
} else if negatedLit := extractNegatedLiteral(n.Expr); negatedLit != "" {
147+
// Handle negated literal like -0::Int16 -> CAST('-0', 'Int16')
148+
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, negatedLit)
146149
} else {
147150
// Complex expression - use normal AST node
148151
Node(sb, n.Expr, depth+2)
@@ -333,6 +336,27 @@ func exprToLiteral(expr ast.Expression) *ast.Literal {
333336
return nil
334337
}
335338

339+
// extractNegatedLiteral checks if expr is a negated literal (like -0, -12)
340+
// and returns its string representation (like "-0", "-12") for :: cast expressions.
341+
// Returns empty string if not a negated literal.
342+
func extractNegatedLiteral(expr ast.Expression) string {
343+
unary, ok := expr.(*ast.UnaryExpr)
344+
if !ok || unary.Op != "-" {
345+
return ""
346+
}
347+
lit, ok := unary.Operand.(*ast.Literal)
348+
if !ok {
349+
return ""
350+
}
351+
switch lit.Type {
352+
case ast.LiteralInteger:
353+
return "-" + formatExprAsString(lit)
354+
case ast.LiteralFloat:
355+
return "-" + formatExprAsString(lit)
356+
}
357+
return ""
358+
}
359+
336360
func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) {
337361
// IN is represented as Function in
338362
fnName := "in"

0 commit comments

Comments
 (0)