Skip to content

Commit 106be54

Browse files
committed
Flatten chained || (concat) operations in explain output
ClickHouse EXPLAIN AST flattens multiple || operators into a single concat function with all operands as children. This handles BinaryExpr, AliasedExpr, and WithElement cases.
1 parent 8370b18 commit 106be54

1 file changed

Lines changed: 61 additions & 8 deletions

File tree

internal/explain/expressions.go

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -92,12 +92,45 @@ func explainLiteral(sb *strings.Builder, n *ast.Literal, indent string, depth in
9292
func explainBinaryExpr(sb *strings.Builder, n *ast.BinaryExpr, indent string, depth int) {
9393
// Convert operator to function name
9494
fnName := OperatorToFunction(n.Op)
95+
96+
// For || (concat) operator, flatten chained concatenations
97+
if n.Op == "||" {
98+
operands := collectConcatOperands(n)
99+
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
100+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
101+
for _, op := range operands {
102+
Node(sb, op, depth+2)
103+
}
104+
return
105+
}
106+
95107
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
96108
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
97109
Node(sb, n.Left, depth+2)
98110
Node(sb, n.Right, depth+2)
99111
}
100112

113+
// collectConcatOperands flattens chained || (concat) operations into a list of operands
114+
func collectConcatOperands(n *ast.BinaryExpr) []ast.Expression {
115+
var operands []ast.Expression
116+
117+
// Recursively collect from left side if it's also a concat
118+
if left, ok := n.Left.(*ast.BinaryExpr); ok && left.Op == "||" {
119+
operands = append(operands, collectConcatOperands(left)...)
120+
} else {
121+
operands = append(operands, n.Left)
122+
}
123+
124+
// Recursively collect from right side if it's also a concat
125+
if right, ok := n.Right.(*ast.BinaryExpr); ok && right.Op == "||" {
126+
operands = append(operands, collectConcatOperands(right)...)
127+
} else {
128+
operands = append(operands, n.Right)
129+
}
130+
131+
return operands
132+
}
133+
101134
func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, depth int) {
102135
fnName := UnaryOperatorToFunction(n.Op)
103136
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
@@ -142,10 +175,20 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
142175
case *ast.BinaryExpr:
143176
// Binary expressions become functions with alias
144177
fnName := OperatorToFunction(e.Op)
145-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
146-
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
147-
Node(sb, e.Left, depth+2)
148-
Node(sb, e.Right, depth+2)
178+
// For || (concat) operator, flatten chained concatenations
179+
if e.Op == "||" {
180+
operands := collectConcatOperands(e)
181+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
182+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
183+
for _, op := range operands {
184+
Node(sb, op, depth+2)
185+
}
186+
} else {
187+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
188+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
189+
Node(sb, e.Left, depth+2)
190+
Node(sb, e.Right, depth+2)
191+
}
149192
case *ast.UnaryExpr:
150193
// Unary expressions become functions with alias
151194
fnName := UnaryOperatorToFunction(e.Op)
@@ -185,10 +228,20 @@ func explainWithElement(sb *strings.Builder, n *ast.WithElement, indent string,
185228
case *ast.BinaryExpr:
186229
// Binary expressions become functions
187230
fnName := OperatorToFunction(e.Op)
188-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Name, 1)
189-
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
190-
Node(sb, e.Left, depth+2)
191-
Node(sb, e.Right, depth+2)
231+
// For || (concat) operator, flatten chained concatenations
232+
if e.Op == "||" {
233+
operands := collectConcatOperands(e)
234+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Name, 1)
235+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
236+
for _, op := range operands {
237+
Node(sb, op, depth+2)
238+
}
239+
} else {
240+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Name, 1)
241+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
242+
Node(sb, e.Left, depth+2)
243+
Node(sb, e.Right, depth+2)
244+
}
192245
case *ast.Subquery:
193246
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, n.Name, 1)
194247
Node(sb, e.Query, depth+1)

0 commit comments

Comments
 (0)