Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
975e048
Fix nested empty array handling in aliased expressions
claude Jan 1, 2026
b2c6671
Fix binary string literal parsing (b'...')
claude Jan 1, 2026
57c6049
Fix IS NOT DISTINCT FROM operator and boolean tuple handling
claude Jan 1, 2026
b29318b
Handle non-literal expressions in aliased array formatting
claude Jan 1, 2026
a165527
Handle STRICT modifier for EXCEPT and REPLACE column transformers
claude Jan 1, 2026
fd631be
Add support for RENAME DICTIONARY statements
claude Jan 1, 2026
0646651
Add DISTINCT ON (col1, col2, ...) syntax support
claude Jan 1, 2026
0cad447
Add support for tagged dollar-quoted strings ($tag$...$tag$)
claude Jan 1, 2026
a66e309
Add PARTITION ID syntax support for ALTER commands
claude Jan 1, 2026
c58ba37
Add WITH RECURSIVE support for CTEs
claude Jan 1, 2026
1b88092
Add REPLACE/EXCHANGE DICTIONARY and fix RENAME/dict source parsing
claude Jan 1, 2026
3e56e47
Add support for MATERIALIZE TTL ALTER command
claude Jan 1, 2026
a32664b
Support dotted column names in INSERT column lists
claude Jan 1, 2026
2e1c16b
Fix ALTER query FORMAT and SETTINGS clause ordering
claude Jan 1, 2026
6751071
Support trailing commas in expression lists
claude Jan 1, 2026
838f6b4
Include type in QueryParameter EXPLAIN output
claude Jan 1, 2026
db2e211
Add PARALLEL WITH statement chaining and fix EXISTS TABLE explain format
claude Jan 1, 2026
4fda7c3
Fix Identifier handling in FormatDataType and escape strings in CAST …
claude Jan 1, 2026
a83261b
Add STALENESS support for WITH FILL and fix OrderByElement/Interpolat…
claude Jan 1, 2026
44a42e1
Add MODIFY ORDER BY support in ALTER statements
claude Jan 1, 2026
40b7213
Handle +Inf and -Inf as infinity literals in parser
claude Jan 1, 2026
b1cea52
Add ATTACH TABLE schema parsing and fix multi-column PRIMARY KEY
claude Jan 1, 2026
a960bd7
Add MOVE PARTITION and fix OPTIMIZE TABLE database name
claude Jan 1, 2026
cd205b3
Fix nested array literal format for arrays with negative numbers
claude Jan 1, 2026
5f1d19c
Add CONSTRAINT ASSUME support in CREATE TABLE parsing
claude Jan 1, 2026
bb337ed
Fix SYSTEM distributed commands to output table name twice
claude Jan 1, 2026
95a51dc
Simplify SQL standard TRIM with empty string to just the literal
claude Jan 1, 2026
bf444e7
Fix CAST expression handling for arrays containing CastExpr elements
claude Jan 1, 2026
7b8bc9d
Add ON CLUSTER support and duplicate output for SYSTEM distributed co…
claude Jan 1, 2026
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
80 changes: 53 additions & 27 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type SelectQuery struct {
Position token.Position `json:"-"`
With []Expression `json:"with,omitempty"`
Distinct bool `json:"distinct,omitempty"`
DistinctOn []Expression `json:"distinct_on,omitempty"` // DISTINCT ON (col1, col2, ...) syntax
Top Expression `json:"top,omitempty"`
Columns []Expression `json:"columns"`
From *TablesInSelectQuery `json:"from,omitempty"`
Expand Down Expand Up @@ -199,15 +200,16 @@ const (

// OrderByElement represents an ORDER BY element.
type OrderByElement struct {
Position token.Position `json:"-"`
Expression Expression `json:"expression"`
Descending bool `json:"descending,omitempty"`
NullsFirst *bool `json:"nulls_first,omitempty"`
Collate string `json:"collate,omitempty"`
WithFill bool `json:"with_fill,omitempty"`
FillFrom Expression `json:"fill_from,omitempty"`
FillTo Expression `json:"fill_to,omitempty"`
FillStep Expression `json:"fill_step,omitempty"`
Position token.Position `json:"-"`
Expression Expression `json:"expression"`
Descending bool `json:"descending,omitempty"`
NullsFirst *bool `json:"nulls_first,omitempty"`
Collate string `json:"collate,omitempty"`
WithFill bool `json:"with_fill,omitempty"`
FillFrom Expression `json:"fill_from,omitempty"`
FillTo Expression `json:"fill_to,omitempty"`
FillStep Expression `json:"fill_step,omitempty"`
FillStaleness Expression `json:"fill_staleness,omitempty"`
}

func (o *OrderByElement) Pos() token.Position { return o.Position }
Expand Down Expand Up @@ -574,8 +576,11 @@ type AlterCommand struct {
Constraint *Constraint `json:"constraint,omitempty"`
ConstraintName string `json:"constraint_name,omitempty"`
Partition Expression `json:"partition,omitempty"`
PartitionIsID bool `json:"partition_is_id,omitempty"` // True when using PARTITION ID 'value' syntax
FromTable string `json:"from_table,omitempty"`
FromPath string `json:"from_path,omitempty"` // For FETCH PARTITION FROM
ToDatabase string `json:"to_database,omitempty"` // For MOVE PARTITION TO TABLE
ToTable string `json:"to_table,omitempty"` // For MOVE PARTITION TO TABLE
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 All @@ -585,6 +590,7 @@ type AlterCommand struct {
StatisticsColumns []string `json:"statistics_columns,omitempty"` // For ADD/DROP/CLEAR/MATERIALIZE STATISTICS
StatisticsTypes []*FunctionCall `json:"statistics_types,omitempty"` // For ADD/MODIFY STATISTICS TYPE
Comment string `json:"comment,omitempty"` // For COMMENT COLUMN
OrderByExpr []Expression `json:"order_by_expr,omitempty"` // For MODIFY ORDER BY
}

// Projection represents a projection definition.
Expand Down Expand Up @@ -635,6 +641,7 @@ const (
AlterAddConstraint AlterCommandType = "ADD_CONSTRAINT"
AlterDropConstraint AlterCommandType = "DROP_CONSTRAINT"
AlterModifyTTL AlterCommandType = "MODIFY_TTL"
AlterMaterializeTTL AlterCommandType = "MATERIALIZE_TTL"
AlterModifySetting AlterCommandType = "MODIFY_SETTING"
AlterDropPartition AlterCommandType = "DROP_PARTITION"
AlterDetachPartition AlterCommandType = "DETACH_PARTITION"
Expand All @@ -653,10 +660,11 @@ const (
AlterClearProjection AlterCommandType = "CLEAR_PROJECTION"
AlterAddStatistics AlterCommandType = "ADD_STATISTICS"
AlterModifyStatistics AlterCommandType = "MODIFY_STATISTICS"
AlterDropStatistics AlterCommandType = "DROP_STATISTICS"
AlterClearStatistics AlterCommandType = "CLEAR_STATISTICS"
AlterDropStatistics AlterCommandType = "DROP_STATISTICS"
AlterClearStatistics AlterCommandType = "CLEAR_STATISTICS"
AlterMaterializeStatistics AlterCommandType = "MATERIALIZE_STATISTICS"
AlterModifyComment AlterCommandType = "MODIFY_COMMENT"
AlterModifyOrderBy AlterCommandType = "MODIFY_ORDER_BY"
)

// TruncateQuery represents a TRUNCATE statement.
Expand Down Expand Up @@ -707,9 +715,14 @@ func (d *DetachQuery) statementNode() {}

// AttachQuery represents an ATTACH statement.
type AttachQuery struct {
Position token.Position `json:"-"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
Position token.Position `json:"-"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
Columns []*ColumnDeclaration `json:"columns,omitempty"`
ColumnsPrimaryKey []Expression `json:"columns_primary_key,omitempty"` // PRIMARY KEY in column list
Engine *EngineClause `json:"engine,omitempty"`
OrderBy []Expression `json:"order_by,omitempty"`
PrimaryKey []Expression `json:"primary_key,omitempty"`
}

func (a *AttachQuery) Pos() token.Position { return a.Position }
Expand Down Expand Up @@ -840,10 +853,12 @@ func (c *CheckQuery) statementNode() {}

// SystemQuery represents a SYSTEM statement.
type SystemQuery struct {
Position token.Position `json:"-"`
Command string `json:"command"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
Position token.Position `json:"-"`
Command string `json:"command"`
Database string `json:"database,omitempty"`
Table string `json:"table,omitempty"`
OnCluster string `json:"on_cluster,omitempty"`
DuplicateTableOutput bool `json:"duplicate_table_output,omitempty"` // True for commands that need database/table output twice
}

func (s *SystemQuery) Pos() token.Position { return s.Position }
Expand Down Expand Up @@ -1290,14 +1305,15 @@ func (c *ColumnsMatcher) expressionNode() {}

// FunctionCall represents a function call.
type FunctionCall struct {
Position token.Position `json:"-"`
Name string `json:"name"`
Parameters []Expression `json:"parameters,omitempty"` // For parametric functions like quantile(0.9)(x)
Arguments []Expression `json:"arguments,omitempty"`
Settings []*SettingExpr `json:"settings,omitempty"` // For table functions with SETTINGS
Distinct bool `json:"distinct,omitempty"`
Over *WindowSpec `json:"over,omitempty"`
Alias string `json:"alias,omitempty"`
Position token.Position `json:"-"`
Name string `json:"name"`
Parameters []Expression `json:"parameters,omitempty"` // For parametric functions like quantile(0.9)(x)
Arguments []Expression `json:"arguments,omitempty"`
Settings []*SettingExpr `json:"settings,omitempty"` // For table functions with SETTINGS
Distinct bool `json:"distinct,omitempty"`
Over *WindowSpec `json:"over,omitempty"`
Alias string `json:"alias,omitempty"`
SQLStandard bool `json:"sql_standard,omitempty"` // True for SQL standard syntax like TRIM(... FROM ...)
}

func (f *FunctionCall) Pos() token.Position { return f.Position }
Expand Down Expand Up @@ -1594,3 +1610,13 @@ type ExistsExpr struct {
func (e *ExistsExpr) Pos() token.Position { return e.Position }
func (e *ExistsExpr) End() token.Position { return e.Position }
func (e *ExistsExpr) expressionNode() {}

// ParallelWithQuery represents multiple statements executed in parallel with PARALLEL WITH.
type ParallelWithQuery struct {
Position token.Position `json:"-"`
Statements []Statement `json:"statements"`
}

func (p *ParallelWithQuery) Pos() token.Position { return p.Position }
func (p *ParallelWithQuery) End() token.Position { return p.Position }
func (p *ParallelWithQuery) statementNode() {}
4 changes: 3 additions & 1 deletion internal/explain/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
case *ast.DetachQuery:
explainDetachQuery(sb, n, indent)
case *ast.AttachQuery:
explainAttachQuery(sb, n, indent)
explainAttachQuery(sb, n, indent, depth)
case *ast.AlterQuery:
explainAlterQuery(sb, n, indent, depth)
case *ast.OptimizeQuery:
Expand All @@ -246,6 +246,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
explainCreateIndexQuery(sb, n, indent, depth)
case *ast.UpdateQuery:
explainUpdateQuery(sb, n, indent, depth)
case *ast.ParallelWithQuery:
explainParallelWithQuery(sb, n, indent, depth)

// Types
case *ast.DataType:
Expand Down
58 changes: 56 additions & 2 deletions internal/explain/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,16 @@ func containsOnlyArraysOrTuples(exprs []ast.Expression) bool {
// any non-literal expressions (identifiers, function calls, etc.)
func containsNonLiteralExpressions(exprs []ast.Expression) bool {
for _, e := range exprs {
if _, ok := e.(*ast.Literal); !ok {
return true
if _, ok := e.(*ast.Literal); ok {
continue
}
// Unary minus of a literal (negative number) is also acceptable
if unary, ok := e.(*ast.UnaryExpr); ok && unary.Op == "-" {
if _, ok := unary.Operand.(*ast.Literal); ok {
continue
}
}
return true
}
return false
}
Expand Down Expand Up @@ -498,6 +505,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
if e.Type == ast.LiteralArray {
if exprs, ok := e.Value.([]ast.Expression); ok {
needsFunctionFormat := false
hasNestedArrays := false
// Empty arrays always use Function array format
if len(exprs) == 0 {
needsFunctionFormat = true
Expand All @@ -508,6 +516,17 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
needsFunctionFormat = true
break
}
// Check for nested arrays
if lit, ok := expr.(*ast.Literal); ok && lit.Type == ast.LiteralArray {
hasNestedArrays = true
// Check if inner array is empty or contains empty arrays
if innerExprs, ok := lit.Value.([]ast.Expression); ok {
if len(innerExprs) == 0 || containsEmptyArrays(innerExprs) {
needsFunctionFormat = true
break
}
}
}
// Check for identifiers - use Function array
if _, ok := expr.(*ast.Identifier); ok {
needsFunctionFormat = true
Expand All @@ -518,6 +537,41 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
needsFunctionFormat = true
break
}
// Check for CAST expressions - use Function array
if _, ok := expr.(*ast.CastExpr); ok {
needsFunctionFormat = true
break
}
// Check for binary expressions - use Function array
if _, ok := expr.(*ast.BinaryExpr); ok {
needsFunctionFormat = true
break
}
// Check for other non-literal expressions (skip arrays/tuples which are handled separately)
if lit, ok := expr.(*ast.Literal); !ok {
// Not a literal - check if it's a unary negation of a number (which is OK)
if unary, ok := expr.(*ast.UnaryExpr); ok && unary.Op == "-" {
if innerLit, ok := unary.Operand.(*ast.Literal); ok {
if innerLit.Type == ast.LiteralInteger || innerLit.Type == ast.LiteralFloat {
continue // Negated number is OK
}
}
}
needsFunctionFormat = true
break
} else if lit.Type != ast.LiteralArray && lit.Type != ast.LiteralTuple {
// Simple literal (not array/tuple) - OK
continue
}
// Arrays and tuples are handled by the earlier checks for nested arrays
}
// Also check for empty arrays at any depth within nested arrays
if hasNestedArrays && containsEmptyArraysRecursive(exprs) {
needsFunctionFormat = true
}
// Also check for tuples at any depth within nested arrays
if hasNestedArrays && containsTuplesRecursive(exprs) {
needsFunctionFormat = true
}
if needsFunctionFormat {
// Render as Function array with alias
Expand Down
3 changes: 3 additions & 0 deletions internal/explain/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,9 @@ func FormatDataType(dt *ast.DataType) string {
} else {
params = append(params, fmt.Sprintf("%v", p))
}
} else if ident, ok := p.(*ast.Identifier); ok {
// Identifier (e.g., function name in AggregateFunction types)
params = append(params, ident.Name())
} else {
params = append(params, fmt.Sprintf("%v", p))
}
Expand Down
Loading