Skip to content

Commit 797b449

Browse files
authored
Fix INTERSECT/EXCEPT operator precedence and WITH clause inheritance (#111)
1 parent 1bc5dae commit 797b449

File tree

145 files changed

+699
-879
lines changed

Some content is hidden

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

145 files changed

+699
-879
lines changed

ast/ast.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -272,11 +272,12 @@ type CreateQuery struct {
272272
Columns []*ColumnDeclaration `json:"columns,omitempty"`
273273
Indexes []*IndexDefinition `json:"indexes,omitempty"`
274274
Projections []*Projection `json:"projections,omitempty"`
275-
Constraints []*Constraint `json:"constraints,omitempty"`
276-
Engine *EngineClause `json:"engine,omitempty"`
277-
OrderBy []Expression `json:"order_by,omitempty"`
278-
PartitionBy Expression `json:"partition_by,omitempty"`
279-
PrimaryKey []Expression `json:"primary_key,omitempty"`
275+
Constraints []*Constraint `json:"constraints,omitempty"`
276+
ColumnsPrimaryKey []Expression `json:"columns_primary_key,omitempty"` // PRIMARY KEY in column list
277+
Engine *EngineClause `json:"engine,omitempty"`
278+
OrderBy []Expression `json:"order_by,omitempty"`
279+
PartitionBy Expression `json:"partition_by,omitempty"`
280+
PrimaryKey []Expression `json:"primary_key,omitempty"`
280281
SampleBy Expression `json:"sample_by,omitempty"`
281282
TTL *TTLClause `json:"ttl,omitempty"`
282283
Settings []*SettingExpr `json:"settings,omitempty"`
@@ -502,6 +503,7 @@ type DropQuery struct {
502503
Policy string `json:"policy,omitempty"` // For DROP POLICY
503504
RowPolicy string `json:"row_policy,omitempty"` // For DROP ROW POLICY
504505
SettingsProfile string `json:"settings_profile,omitempty"` // For DROP SETTINGS PROFILE
506+
Index string `json:"index,omitempty"` // For DROP INDEX
505507
Temporary bool `json:"temporary,omitempty"`
506508
OnCluster string `json:"on_cluster,omitempty"`
507509
DropDatabase bool `json:"drop_database,omitempty"`
@@ -642,6 +644,7 @@ const (
642644
AlterMovePartition AlterCommandType = "MOVE_PARTITION"
643645
AlterFreezePartition AlterCommandType = "FREEZE_PARTITION"
644646
AlterFreeze AlterCommandType = "FREEZE"
647+
AlterApplyPatches AlterCommandType = "APPLY_PATCHES"
645648
AlterDeleteWhere AlterCommandType = "DELETE_WHERE"
646649
AlterUpdate AlterCommandType = "UPDATE"
647650
AlterAddProjection AlterCommandType = "ADD_PROJECTION"
@@ -669,6 +672,18 @@ func (t *TruncateQuery) Pos() token.Position { return t.Position }
669672
func (t *TruncateQuery) End() token.Position { return t.Position }
670673
func (t *TruncateQuery) statementNode() {}
671674

675+
// DeleteQuery represents a lightweight DELETE statement.
676+
type DeleteQuery struct {
677+
Position token.Position `json:"-"`
678+
Database string `json:"database,omitempty"`
679+
Table string `json:"table"`
680+
Where Expression `json:"where,omitempty"`
681+
}
682+
683+
func (d *DeleteQuery) Pos() token.Position { return d.Position }
684+
func (d *DeleteQuery) End() token.Position { return d.Position }
685+
func (d *DeleteQuery) statementNode() {}
686+
672687
// UseQuery represents a USE statement.
673688
type UseQuery struct {
674689
Position token.Position `json:"-"`
@@ -1106,10 +1121,12 @@ func (d *DropWorkloadQuery) statementNode() {}
11061121

11071122
// CreateIndexQuery represents a CREATE INDEX statement.
11081123
type CreateIndexQuery struct {
1109-
Position token.Position `json:"-"`
1110-
IndexName string `json:"index_name"`
1111-
Table string `json:"table"`
1112-
Columns []Expression `json:"columns,omitempty"`
1124+
Position token.Position `json:"-"`
1125+
IndexName string `json:"index_name"`
1126+
Table string `json:"table"`
1127+
Columns []Expression `json:"columns,omitempty"`
1128+
Type string `json:"type,omitempty"` // Index type (minmax, bloom_filter, etc.)
1129+
Granularity int `json:"granularity,omitempty"` // GRANULARITY value
11131130
}
11141131

11151132
func (c *CreateIndexQuery) Pos() token.Position { return c.Position }

internal/explain/explain.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
238238
explainOptimizeQuery(sb, n, indent, depth)
239239
case *ast.TruncateQuery:
240240
explainTruncateQuery(sb, n, indent)
241+
case *ast.DeleteQuery:
242+
explainDeleteQuery(sb, n, indent, depth)
241243
case *ast.CheckQuery:
242244
explainCheckQuery(sb, n, indent)
243245
case *ast.CreateIndexQuery:

internal/explain/expressions.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,35 @@ func explainWithElement(sb *strings.Builder, n *ast.WithElement, indent string,
864864
return
865865
}
866866
}
867+
// Arrays containing non-literal expressions should be rendered as Function array
868+
if e.Type == ast.LiteralArray {
869+
if exprs, ok := e.Value.([]ast.Expression); ok {
870+
needsFunctionFormat := false
871+
for _, elem := range exprs {
872+
if !isSimpleLiteralOrNegation(elem) {
873+
needsFunctionFormat = true
874+
break
875+
}
876+
}
877+
if needsFunctionFormat {
878+
// Render as Function array with alias
879+
if n.Name != "" {
880+
fmt.Fprintf(sb, "%sFunction array (alias %s) (children %d)\n", indent, n.Name, 1)
881+
} else {
882+
fmt.Fprintf(sb, "%sFunction array (children %d)\n", indent, 1)
883+
}
884+
if len(exprs) > 0 {
885+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
886+
} else {
887+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
888+
}
889+
for _, elem := range exprs {
890+
Node(sb, elem, depth+2)
891+
}
892+
return
893+
}
894+
}
895+
}
867896
if n.Name != "" {
868897
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), n.Name)
869898
} else {

internal/explain/format.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,8 @@ func NormalizeFunctionName(name string) string {
357357
"least": "least",
358358
"concat_ws": "concat",
359359
"position": "position",
360+
"date_diff": "dateDiff",
361+
"datediff": "dateDiff",
360362
// SQL standard ANY/ALL subquery operators - simple cases
361363
"anyequals": "in",
362364
"allnotequals": "notIn",

internal/explain/functions.go

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -372,9 +372,9 @@ func explainDateAddSubWithInterval(sb *strings.Builder, opFunc string, arg1, arg
372372
}
373373

374374
// handleDateDiff handles DATE_DIFF/DATEDIFF
375-
// DATE_DIFF(unit, date1, date2) -> dateDiff('unit', date1, date2)
375+
// DATE_DIFF(unit, date1, date2[, timezone]) -> dateDiff('unit', date1, date2[, timezone])
376376
func handleDateDiff(sb *strings.Builder, n *ast.FunctionCall, alias string, indent string, depth int) bool {
377-
if len(n.Arguments) != 3 {
377+
if len(n.Arguments) < 3 || len(n.Arguments) > 4 {
378378
return false
379379
}
380380

@@ -392,12 +392,17 @@ func handleDateDiff(sb *strings.Builder, n *ast.FunctionCall, alias string, inde
392392
return false
393393
}
394394

395+
argCount := 3
396+
if len(n.Arguments) == 4 {
397+
argCount = 4
398+
}
399+
395400
if alias != "" {
396401
fmt.Fprintf(sb, "%sFunction dateDiff (alias %s) (children %d)\n", indent, alias, 1)
397402
} else {
398403
fmt.Fprintf(sb, "%sFunction dateDiff (children %d)\n", indent, 1)
399404
}
400-
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 3)
405+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, argCount)
401406

402407
// First arg: unit as lowercase string literal
403408
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, strings.ToLower(unitName))
@@ -406,6 +411,11 @@ func handleDateDiff(sb *strings.Builder, n *ast.FunctionCall, alias string, inde
406411
Node(sb, date1Arg, depth+2)
407412
Node(sb, date2Arg, depth+2)
408413

414+
// Fourth arg: optional timezone
415+
if len(n.Arguments) == 4 {
416+
Node(sb, n.Arguments[3], depth+2)
417+
}
418+
409419
return true
410420
}
411421

@@ -1334,13 +1344,10 @@ func explainExtractExprWithAlias(sb *strings.Builder, n *ast.ExtractExpr, alias
13341344
// EXTRACT is represented as Function toYear, toMonth, etc.
13351345
// ClickHouse uses specific function names for date/time extraction
13361346
fnName := extractFieldToFunction(n.Field)
1337-
// Use alias from parameter, or fall back to expression's alias
1338-
effectiveAlias := alias
1339-
if effectiveAlias == "" {
1340-
effectiveAlias = n.Alias
1341-
}
1342-
if effectiveAlias != "" {
1343-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, effectiveAlias, 1)
1347+
// Only use the external alias parameter (from explicit AS on EXTRACT itself)
1348+
// NOT the alias from the From expression - that stays on the inner expression
1349+
if alias != "" {
1350+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, alias, 1)
13441351
} else {
13451352
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
13461353
}

internal/explain/select.go

Lines changed: 149 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,165 @@ func explainSelectIntersectExceptQuery(sb *strings.Builder, n *ast.SelectInterse
1919
}
2020
}
2121

22+
// Check if first operand has a WITH clause to be inherited by subsequent operands
23+
var inheritedWith []ast.Expression
24+
if len(n.Selects) > 0 {
25+
inheritedWith = extractWithClause(n.Selects[0])
26+
}
27+
2228
childIndent := strings.Repeat(" ", depth+1)
2329
for i, sel := range n.Selects {
2430
if hasExcept && i == 0 {
2531
// Wrap first operand in SelectWithUnionQuery -> ExpressionList format
26-
fmt.Fprintf(sb, "%sSelectWithUnionQuery (children 1)\n", childIndent)
27-
fmt.Fprintf(sb, "%s ExpressionList (children 1)\n", childIndent)
28-
Node(sb, sel, depth+3)
32+
// But if it's already a SelectWithUnionQuery, don't double-wrap
33+
if _, isUnion := sel.(*ast.SelectWithUnionQuery); isUnion {
34+
Node(sb, sel, depth+1)
35+
} else {
36+
fmt.Fprintf(sb, "%sSelectWithUnionQuery (children 1)\n", childIndent)
37+
fmt.Fprintf(sb, "%s ExpressionList (children 1)\n", childIndent)
38+
Node(sb, sel, depth+3)
39+
}
40+
} else if i > 0 && len(inheritedWith) > 0 {
41+
// Subsequent operands inherit the WITH clause from the first operand
42+
explainSelectQueryWithInheritedWith(sb, sel, inheritedWith, depth+1)
2943
} else {
3044
Node(sb, sel, depth+1)
3145
}
3246
}
3347
}
3448

49+
// extractWithClause extracts the WITH clause from a statement (if it's a SelectQuery)
50+
func extractWithClause(stmt ast.Statement) []ast.Expression {
51+
switch s := stmt.(type) {
52+
case *ast.SelectQuery:
53+
return s.With
54+
case *ast.SelectWithUnionQuery:
55+
// Check the first select in the union
56+
if len(s.Selects) > 0 {
57+
return extractWithClause(s.Selects[0])
58+
}
59+
}
60+
return nil
61+
}
62+
63+
// explainSelectQueryWithInheritedWith outputs a SELECT with an inherited WITH clause
64+
// The inherited WITH clause is output AFTER the columns (not before, like a regular WITH)
65+
func explainSelectQueryWithInheritedWith(sb *strings.Builder, stmt ast.Statement, inheritedWith []ast.Expression, depth int) {
66+
sq, ok := stmt.(*ast.SelectQuery)
67+
if !ok {
68+
// Not a SelectQuery, output normally
69+
Node(sb, stmt, depth)
70+
return
71+
}
72+
73+
// If the SelectQuery already has a WITH clause, output normally
74+
if len(sq.With) > 0 {
75+
Node(sb, stmt, depth)
76+
return
77+
}
78+
79+
// Output SelectQuery with inherited WITH clause after columns
80+
indent := strings.Repeat(" ", depth)
81+
children := countSelectQueryChildren(sq) + 1 // +1 for inherited WITH clause
82+
fmt.Fprintf(sb, "%sSelectQuery (children %d)\n", indent, children)
83+
84+
// Columns (ExpressionList) - output BEFORE inherited WITH
85+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.Columns))
86+
for _, col := range sq.Columns {
87+
Node(sb, col, depth+2)
88+
}
89+
90+
// Inherited WITH clause (ExpressionList) - output AFTER columns
91+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(inheritedWith))
92+
for _, w := range inheritedWith {
93+
Node(sb, w, depth+2)
94+
}
95+
96+
// FROM (including ARRAY JOIN as part of TablesInSelectQuery)
97+
if sq.From != nil || sq.ArrayJoin != nil {
98+
TablesWithArrayJoin(sb, sq.From, sq.ArrayJoin, depth+1)
99+
}
100+
// PREWHERE
101+
if sq.PreWhere != nil {
102+
Node(sb, sq.PreWhere, depth+1)
103+
}
104+
// WHERE
105+
if sq.Where != nil {
106+
Node(sb, sq.Where, depth+1)
107+
}
108+
// GROUP BY (skip for GROUP BY ALL which doesn't output an expression list)
109+
if len(sq.GroupBy) > 0 && !sq.GroupByAll {
110+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.GroupBy))
111+
for _, g := range sq.GroupBy {
112+
Node(sb, g, depth+2)
113+
}
114+
}
115+
// HAVING
116+
if sq.Having != nil {
117+
Node(sb, sq.Having, depth+1)
118+
}
119+
// QUALIFY
120+
if sq.Qualify != nil {
121+
Node(sb, sq.Qualify, depth+1)
122+
}
123+
// WINDOW clause
124+
if len(sq.Window) > 0 {
125+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.Window))
126+
for range sq.Window {
127+
fmt.Fprintf(sb, "%s WindowListElement\n", indent)
128+
}
129+
}
130+
// ORDER BY
131+
if len(sq.OrderBy) > 0 {
132+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.OrderBy))
133+
for _, o := range sq.OrderBy {
134+
Node(sb, o, depth+2)
135+
}
136+
}
137+
// INTERPOLATE
138+
if len(sq.Interpolate) > 0 {
139+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.Interpolate))
140+
for _, i := range sq.Interpolate {
141+
Node(sb, i, depth+2)
142+
}
143+
}
144+
// OFFSET
145+
if sq.Offset != nil {
146+
Node(sb, sq.Offset, depth+1)
147+
}
148+
// LIMIT BY handling
149+
if sq.LimitByLimit != nil {
150+
Node(sb, sq.LimitByLimit, depth+1)
151+
if len(sq.LimitBy) > 0 {
152+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.LimitBy))
153+
for _, expr := range sq.LimitBy {
154+
Node(sb, expr, depth+2)
155+
}
156+
}
157+
if sq.Limit != nil {
158+
Node(sb, sq.Limit, depth+1)
159+
}
160+
} else if len(sq.LimitBy) > 0 {
161+
if sq.Limit != nil {
162+
Node(sb, sq.Limit, depth+1)
163+
}
164+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.LimitBy))
165+
for _, expr := range sq.LimitBy {
166+
Node(sb, expr, depth+2)
167+
}
168+
} else if sq.Limit != nil {
169+
Node(sb, sq.Limit, depth+1)
170+
}
171+
// SETTINGS
172+
if len(sq.Settings) > 0 && !sq.SettingsAfterFormat {
173+
fmt.Fprintf(sb, "%s Set\n", indent)
174+
}
175+
// TOP clause
176+
if sq.Top != nil {
177+
Node(sb, sq.Top, depth+1)
178+
}
179+
}
180+
35181
func explainSelectWithUnionQuery(sb *strings.Builder, n *ast.SelectWithUnionQuery, indent string, depth int) {
36182
if n == nil {
37183
return

0 commit comments

Comments
 (0)