Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4f3870c
Fix JSON path type accessor parsing and projection ORDER BY
claude Dec 31, 2025
da36378
Add FETCH PARTITION support and fix PARTITION ALL child count
claude Dec 31, 2025
b3b61bf
Allow keywords as table aliases without AS keyword
claude Dec 31, 2025
63368b8
Add MySQL INT type display width and SHOW CREATE TEMPORARY TABLE support
claude Dec 31, 2025
f9c043f
Add ANY/ALL subquery operator normalization
claude Dec 31, 2025
af0114b
Add DATE/TIMESTAMP/TIME typed literal support
claude Dec 31, 2025
06d4215
Add GROUP BY ALL support
claude Dec 31, 2025
4aa3243
Update 01099 test metadata (19 more tests pass with DATE literal supp…
claude Dec 31, 2025
3ed41f6
Add SKIP and SKIP REGEXP support for JSON type parameters
claude Dec 31, 2025
dbd2b5e
Add column transformer ordering support for APPLY, EXCEPT, REPLACE
claude Dec 31, 2025
8fce7a9
Add support for CREATE QUOTA, SET DEFAULT ROLE, and MODIFY COLUMN REM…
claude Dec 31, 2025
5bff0fe
Add special function transformations for DATE_ADD/SUB/DIFF and POSITION
claude Dec 31, 2025
3c0c798
Add SHOW INDEX parsing and escape single quotes in identifiers
claude Dec 31, 2025
efa0542
Fix identifier and materialized view parsing for 03711 test
claude Dec 31, 2025
9239c04
Fix :: cast formatting for arrays in EXPLAIN AST
claude Dec 31, 2025
b622b65
Add Parenthesized flag to Lambda AST node to fix multi-param lambda m…
claude Dec 31, 2025
0a10b2c
Add UNDROP TABLE statement support
claude Dec 31, 2025
d1edb0d
Allow NULL values in IN clause tuple literal formatting
claude Dec 31, 2025
947e6a9
Add lambda support for APPLY transformer on * and COLUMNS()
claude Dec 31, 2025
da40d74
Add plural interval unit normalization (years -> Year)
claude Dec 31, 2025
141903c
Handle INTERVAL '2' AS alias unit syntax in parser
claude Dec 31, 2025
4c50282
Fix NULL::Type cast to keep Literal NULL format
claude Dec 31, 2025
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
117 changes: 90 additions & 27 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type SelectQuery struct {
PreWhere Expression `json:"prewhere,omitempty"`
Where Expression `json:"where,omitempty"`
GroupBy []Expression `json:"group_by,omitempty"`
GroupByAll bool `json:"group_by_all,omitempty"` // true if GROUP BY ALL was used
GroupingSets bool `json:"grouping_sets,omitempty"` // true if GROUP BY uses GROUPING SETS
WithRollup bool `json:"with_rollup,omitempty"`
WithCube bool `json:"with_cube,omitempty"`
Expand Down Expand Up @@ -253,8 +254,9 @@ type CreateQuery struct {
Table string `json:"table,omitempty"`
View string `json:"view,omitempty"`
Materialized bool `json:"materialized,omitempty"`
To string `json:"to,omitempty"` // Target table for materialized views
Populate bool `json:"populate,omitempty"` // POPULATE for materialized views
ToDatabase string `json:"to_database,omitempty"` // Target database for materialized views
To string `json:"to,omitempty"` // Target table for materialized views
Populate bool `json:"populate,omitempty"` // POPULATE for materialized views
Columns []*ColumnDeclaration `json:"columns,omitempty"`
Indexes []*IndexDefinition `json:"indexes,omitempty"`
Projections []*Projection `json:"projections,omitempty"`
Expand Down Expand Up @@ -497,6 +499,19 @@ func (d *DropQuery) Pos() token.Position { return d.Position }
func (d *DropQuery) End() token.Position { return d.Position }
func (d *DropQuery) statementNode() {}

// UndropQuery represents an UNDROP TABLE statement.
type UndropQuery struct {
Position token.Position `json:"-"`
Database string `json:"database,omitempty"`
Table string `json:"table"`
OnCluster string `json:"on_cluster,omitempty"`
UUID string `json:"uuid,omitempty"`
}

func (u *UndropQuery) Pos() token.Position { return u.Position }
func (u *UndropQuery) End() token.Position { return u.Position }
func (u *UndropQuery) statementNode() {}

// AlterQuery represents an ALTER statement.
type AlterQuery struct {
Position token.Position `json:"-"`
Expand Down Expand Up @@ -529,6 +544,7 @@ type AlterCommand struct {
ConstraintName string `json:"constraint_name,omitempty"`
Partition Expression `json:"partition,omitempty"`
FromTable string `json:"from_table,omitempty"`
FromPath string `json:"from_path,omitempty"` // For FETCH PARTITION FROM
TTL *TTLClause `json:"ttl,omitempty"`
Settings []*SettingExpr `json:"settings,omitempty"`
Where Expression `json:"where,omitempty"` // For DELETE WHERE
Expand Down Expand Up @@ -593,6 +609,8 @@ const (
AlterDetachPartition AlterCommandType = "DETACH_PARTITION"
AlterAttachPartition AlterCommandType = "ATTACH_PARTITION"
AlterReplacePartition AlterCommandType = "REPLACE_PARTITION"
AlterFetchPartition AlterCommandType = "FETCH_PARTITION"
AlterMovePartition AlterCommandType = "MOVE_PARTITION"
AlterFreezePartition AlterCommandType = "FREEZE_PARTITION"
AlterFreeze AlterCommandType = "FREEZE"
AlterDeleteWhere AlterCommandType = "DELETE_WHERE"
Expand Down Expand Up @@ -689,18 +707,24 @@ func (s *ShowQuery) statementNode() {}
type ShowType string

const (
ShowTables ShowType = "TABLES"
ShowDatabases ShowType = "DATABASES"
ShowProcesses ShowType = "PROCESSLIST"
ShowCreate ShowType = "CREATE"
ShowCreateDB ShowType = "CREATE_DATABASE"
ShowCreateDictionary ShowType = "CREATE_DICTIONARY"
ShowCreateView ShowType = "CREATE_VIEW"
ShowCreateUser ShowType = "CREATE_USER"
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"
ShowCreateUser ShowType = "CREATE_USER"
ShowCreateRole ShowType = "CREATE_ROLE"
ShowCreatePolicy ShowType = "CREATE_POLICY"
ShowCreateRowPolicy ShowType = "CREATE_ROW_POLICY"
ShowCreateQuota ShowType = "CREATE_QUOTA"
ShowCreateSettingsProfile ShowType = "CREATE_SETTINGS_PROFILE"
ShowColumns ShowType = "COLUMNS"
ShowDictionaries ShowType = "DICTIONARIES"
ShowFunctions ShowType = "FUNCTIONS"
ShowSettings ShowType = "SETTINGS"
ShowGrants ShowType = "GRANTS"
)

// ExplainQuery represents an EXPLAIN statement.
Expand Down Expand Up @@ -862,6 +886,7 @@ func (g *GrantQuery) statementNode() {}
// ShowGrantsQuery represents a SHOW GRANTS statement.
type ShowGrantsQuery struct {
Position token.Position `json:"-"`
Format string `json:"format,omitempty"`
}

func (s *ShowGrantsQuery) Pos() token.Position { return s.Position }
Expand All @@ -881,12 +906,23 @@ func (s *ShowPrivilegesQuery) statementNode() {}
type ShowCreateQuotaQuery struct {
Position token.Position `json:"-"`
Name string `json:"name,omitempty"`
Format string `json:"format,omitempty"`
}

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

// CreateQuotaQuery represents a CREATE QUOTA statement.
type CreateQuotaQuery struct {
Position token.Position `json:"-"`
Name string `json:"name,omitempty"`
}

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

// CreateSettingsProfileQuery represents a CREATE SETTINGS PROFILE statement.
type CreateSettingsProfileQuery struct {
Position token.Position `json:"-"`
Expand Down Expand Up @@ -922,6 +958,7 @@ func (d *DropSettingsProfileQuery) statementNode() {}
type ShowCreateSettingsProfileQuery struct {
Position token.Position `json:"-"`
Names []string `json:"names,omitempty"`
Format string `json:"format,omitempty"`
}

func (s *ShowCreateSettingsProfileQuery) Pos() token.Position { return s.Position }
Expand Down Expand Up @@ -951,6 +988,7 @@ func (d *DropRowPolicyQuery) statementNode() {}
// ShowCreateRowPolicyQuery represents a SHOW CREATE ROW POLICY statement.
type ShowCreateRowPolicyQuery struct {
Position token.Position `json:"-"`
Format string `json:"format,omitempty"`
}

func (s *ShowCreateRowPolicyQuery) Pos() token.Position { return s.Position }
Expand Down Expand Up @@ -981,12 +1019,22 @@ func (d *DropRoleQuery) statementNode() {}
type ShowCreateRoleQuery struct {
Position token.Position `json:"-"`
RoleCount int `json:"role_count,omitempty"` // Number of roles specified
Format string `json:"format,omitempty"`
}

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

// SetRoleQuery represents a SET DEFAULT ROLE statement.
type SetRoleQuery struct {
Position token.Position `json:"-"`
}

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

// CreateResourceQuery represents a CREATE RESOURCE statement.
type CreateResourceQuery struct {
Position token.Position `json:"-"`
Expand Down Expand Up @@ -1143,11 +1191,12 @@ const (

// Asterisk represents a *.
type Asterisk struct {
Position token.Position `json:"-"`
Table string `json:"table,omitempty"` // for table.*
Except []string `json:"except,omitempty"` // for * EXCEPT (col1, col2)
Replace []*ReplaceExpr `json:"replace,omitempty"` // for * REPLACE (expr AS col)
Apply []string `json:"apply,omitempty"` // for * APPLY (func1) APPLY(func2)
Position token.Position `json:"-"`
Table string `json:"table,omitempty"` // for table.*
Except []string `json:"except,omitempty"` // for * EXCEPT (col1, col2) - deprecated, use Transformers
Replace []*ReplaceExpr `json:"replace,omitempty"` // for * REPLACE (expr AS col) - deprecated, use Transformers
Apply []string `json:"apply,omitempty"` // for * APPLY (func1) APPLY(func2) - deprecated, use Transformers
Transformers []*ColumnTransformer `json:"transformers,omitempty"` // ordered list of transformers
}

func (a *Asterisk) Pos() token.Position { return a.Position }
Expand All @@ -1164,15 +1213,28 @@ type ReplaceExpr struct {
func (r *ReplaceExpr) Pos() token.Position { return r.Position }
func (r *ReplaceExpr) End() token.Position { return r.Position }

// ColumnTransformer represents a single transformer (APPLY, EXCEPT, or REPLACE) in order.
type ColumnTransformer struct {
Position token.Position `json:"-"`
Type string `json:"type"` // "apply", "except", "replace"
Apply string `json:"apply,omitempty"` // function name for APPLY
ApplyLambda Expression `json:"apply_lambda,omitempty"` // lambda expression for APPLY x -> expr
Except []string `json:"except,omitempty"` // column names for EXCEPT
Replaces []*ReplaceExpr `json:"replaces,omitempty"` // replacement expressions for REPLACE
}

// 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,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(...)
Position token.Position `json:"-"`
Pattern string `json:"pattern,omitempty"`
Columns []Expression `json:"columns,omitempty"` // For COLUMNS(id, name) syntax
Except []string `json:"except,omitempty"` // for EXCEPT (col1, col2) - deprecated, use Transformers
Replace []*ReplaceExpr `json:"replace,omitempty"` // for REPLACE (expr AS col) - deprecated, use Transformers
Apply []string `json:"apply,omitempty"` // for APPLY (func1) APPLY(func2) - deprecated, use Transformers
Qualifier string `json:"qualifier,omitempty"` // For qualified matchers like table.COLUMNS(...)
Transformers []*ColumnTransformer `json:"transformers,omitempty"` // ordered list of transformers
}

func (c *ColumnsMatcher) Pos() token.Position { return c.Position }
Expand Down Expand Up @@ -1392,9 +1454,10 @@ func (t *TupleAccess) expressionNode() {}

// Lambda represents a lambda expression.
type Lambda struct {
Position token.Position `json:"-"`
Parameters []string `json:"parameters"`
Body Expression `json:"body"`
Position token.Position `json:"-"`
Parameters []string `json:"parameters"`
Body Expression `json:"body"`
Parenthesized bool `json:"-"` // True if wrapped in explicit parentheses
}

func (l *Lambda) Pos() token.Position { return l.Position }
Expand Down
52 changes: 44 additions & 8 deletions internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,16 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
explainCreateQuery(sb, n, indent, depth)
case *ast.DropQuery:
explainDropQuery(sb, n, indent, depth)
case *ast.UndropQuery:
explainUndropQuery(sb, n, indent, depth)
case *ast.RenameQuery:
explainRenameQuery(sb, n, indent, depth)
case *ast.ExchangeQuery:
explainExchangeQuery(sb, n, indent)
case *ast.SetQuery:
explainSetQuery(sb, indent)
case *ast.SetRoleQuery:
fmt.Fprintf(sb, "%sSetRoleQuery\n", indent)
case *ast.SystemQuery:
explainSystemQuery(sb, n, indent)
case *ast.TransactionControlQuery:
Expand All @@ -130,7 +134,14 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
case *ast.ShowPrivilegesQuery:
fmt.Fprintf(sb, "%sShowPrivilegesQuery\n", indent)
case *ast.ShowCreateQuotaQuery:
fmt.Fprintf(sb, "%sSHOW CREATE QUOTA query\n", indent)
if n.Format != "" {
fmt.Fprintf(sb, "%sSHOW CREATE QUOTA query (children 1)\n", indent)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
} else {
fmt.Fprintf(sb, "%sSHOW CREATE QUOTA query\n", indent)
}
case *ast.CreateQuotaQuery:
fmt.Fprintf(sb, "%sCreateQuotaQuery\n", indent)
case *ast.CreateSettingsProfileQuery:
fmt.Fprintf(sb, "%sCreateSettingsProfileQuery\n", indent)
case *ast.AlterSettingsProfileQuery:
Expand All @@ -140,27 +151,43 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
fmt.Fprintf(sb, "%sDROP SETTINGS PROFILE query\n", indent)
case *ast.ShowCreateSettingsProfileQuery:
// Use PROFILES (plural) when multiple profiles are specified
queryName := "SHOW CREATE SETTINGS PROFILE query"
if len(n.Names) > 1 {
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILES query\n", indent)
queryName = "SHOW CREATE SETTINGS PROFILES query"
}
if n.Format != "" {
fmt.Fprintf(sb, "%s%s (children 1)\n", indent, queryName)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
} else {
fmt.Fprintf(sb, "%sSHOW CREATE SETTINGS PROFILE query\n", indent)
fmt.Fprintf(sb, "%s%s\n", indent, queryName)
}
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)
// ClickHouse uses "ROW POLICIES" (plural) when FORMAT is present
if n.Format != "" {
fmt.Fprintf(sb, "%sSHOW CREATE ROW POLICIES query (children 1)\n", indent)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
} else {
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
queryName := "SHOW CREATE ROLE query"
if n.RoleCount > 1 {
fmt.Fprintf(sb, "%sSHOW CREATE ROLES query\n", indent)
queryName = "SHOW CREATE ROLES query"
}
if n.Format != "" {
fmt.Fprintf(sb, "%s%s (children 1)\n", indent, queryName)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
} else {
fmt.Fprintf(sb, "%sSHOW CREATE ROLE query\n", indent)
fmt.Fprintf(sb, "%s%s\n", indent, queryName)
}
case *ast.CreateResourceQuery:
fmt.Fprintf(sb, "%sCreateResourceQuery %s (children 1)\n", indent, n.Name)
Expand All @@ -181,7 +208,12 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
case *ast.DropWorkloadQuery:
fmt.Fprintf(sb, "%sDropWorkloadQuery\n", indent)
case *ast.ShowGrantsQuery:
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
if n.Format != "" {
fmt.Fprintf(sb, "%sShowGrantsQuery (children 1)\n", indent)
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Format)
} else {
fmt.Fprintf(sb, "%sShowGrantsQuery\n", indent)
}
case *ast.GrantQuery:
fmt.Fprintf(sb, "%sGrantQuery\n", indent)
case *ast.UseQuery:
Expand Down Expand Up @@ -285,7 +317,11 @@ func Column(sb *strings.Builder, col *ast.ColumnDeclaration, depth int) {
if col.Codec != nil {
children++
}
fmt.Fprintf(sb, "%sColumnDeclaration %s (children %d)\n", indent, col.Name, children)
if children > 0 {
fmt.Fprintf(sb, "%sColumnDeclaration %s (children %d)\n", indent, col.Name, children)
} else {
fmt.Fprintf(sb, "%sColumnDeclaration %s\n", indent, col.Name)
}
if col.Type != nil {
Node(sb, col.Type, depth+1)
}
Expand Down
Loading