Skip to content

Commit 2bbdbcf

Browse files
committed
Add more Explain function improvements
- Handle empty tuples (nil arguments) as Function tuple with empty ExpressionList - Add array handling for complex expressions (renders as Function array) - Normalize function names (ltrim -> trimLeft, etc.) - Fix ShowQuery capitalization - Fix SystemQuery format to "SYSTEM query" Tests improved from ~73% to ~74% passing (5057 of 6824).
1 parent cec802e commit 2bbdbcf

1 file changed

Lines changed: 77 additions & 5 deletions

File tree

parser/explain.go

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ func Explain(stmt ast.Statement) string {
1717
// explainNode writes the EXPLAIN AST output for an AST node.
1818
func explainNode(sb *strings.Builder, node interface{}, depth int) {
1919
if node == nil {
20+
// nil can represent an empty tuple in function arguments
21+
indent := strings.Repeat(" ", depth)
22+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
23+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
2024
return
2125
}
2226

@@ -144,9 +148,16 @@ func explainNode(sb *strings.Builder, node interface{}, depth int) {
144148
}
145149

146150
case *ast.Literal:
147-
// Check if this is a tuple with complex expressions that should be rendered as Function tuple
151+
// Check if this is a tuple - either with expressions or empty
148152
if n.Type == ast.LiteralTuple {
149153
if exprs, ok := n.Value.([]ast.Expression); ok {
154+
// Check if empty tuple or has complex expressions
155+
if len(exprs) == 0 {
156+
// Empty tuple renders as Function tuple with empty ExpressionList
157+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
158+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
159+
return
160+
}
150161
hasComplexExpr := false
151162
for _, e := range exprs {
152163
if _, isLit := e.(*ast.Literal); !isLit {
@@ -163,6 +174,32 @@ func explainNode(sb *strings.Builder, node interface{}, depth int) {
163174
}
164175
return
165176
}
177+
} else if n.Value == nil {
178+
// nil value means empty tuple
179+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
180+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
181+
return
182+
}
183+
}
184+
// Check if this is an array with complex expressions that should be rendered as Function array
185+
if n.Type == ast.LiteralArray {
186+
if exprs, ok := n.Value.([]ast.Expression); ok {
187+
hasComplexExpr := false
188+
for _, e := range exprs {
189+
if _, isLit := e.(*ast.Literal); !isLit {
190+
hasComplexExpr = true
191+
break
192+
}
193+
}
194+
if hasComplexExpr {
195+
// Render as Function array instead of Literal
196+
fmt.Fprintf(sb, "%sFunction array (children %d)\n", indent, 1)
197+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
198+
for _, e := range exprs {
199+
explainNode(sb, e, depth+2)
200+
}
201+
return
202+
}
166203
}
167204
}
168205
fmt.Fprintf(sb, "%sLiteral %s\n", indent, formatLiteral(n))
@@ -172,10 +209,12 @@ func explainNode(sb *strings.Builder, node interface{}, depth int) {
172209
if len(n.Parameters) > 0 {
173210
children++ // parameters ExpressionList
174211
}
212+
// Normalize function name
213+
fnName := normalizeFunctionName(n.Name)
175214
if n.Alias != "" {
176-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, n.Name, n.Alias, children)
215+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, children)
177216
} else {
178-
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, n.Name, children)
217+
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, children)
179218
}
180219
// Arguments
181220
fmt.Fprintf(sb, "%s ExpressionList", indent)
@@ -469,14 +508,16 @@ func explainNode(sb *strings.Builder, node interface{}, depth int) {
469508
}
470509

471510
case *ast.SystemQuery:
472-
fmt.Fprintf(sb, "%sSystem %s\n", indent, n.Command)
511+
fmt.Fprintf(sb, "%sSYSTEM query\n", indent)
473512

474513
case *ast.ExplainQuery:
475514
fmt.Fprintf(sb, "%sExplain %s (children %d)\n", indent, n.ExplainType, 1)
476515
explainNode(sb, n.Statement, depth+1)
477516

478517
case *ast.ShowQuery:
479-
fmt.Fprintf(sb, "%sShow%s\n", indent, n.ShowType)
518+
// Capitalize ShowType correctly for display
519+
showType := strings.Title(strings.ToLower(string(n.ShowType)))
520+
fmt.Fprintf(sb, "%sShow%s\n", indent, showType)
480521

481522
case *ast.UseQuery:
482523
fmt.Fprintf(sb, "%sUse %s\n", indent, n.Database)
@@ -692,6 +733,37 @@ func formatDataType(dt *ast.DataType) string {
692733
return fmt.Sprintf("%s(%s)", dt.Name, strings.Join(params, ", "))
693734
}
694735

736+
// normalizeFunctionName normalizes function names to match ClickHouse's EXPLAIN AST output
737+
func normalizeFunctionName(name string) string {
738+
// ClickHouse normalizes certain function names in EXPLAIN AST
739+
normalized := map[string]string{
740+
"ltrim": "trimLeft",
741+
"rtrim": "trimRight",
742+
"lcase": "lower",
743+
"ucase": "upper",
744+
"mid": "substring",
745+
"substr": "substring",
746+
"pow": "power",
747+
"ceil": "ceiling",
748+
"ln": "log",
749+
"log10": "log10",
750+
"log2": "log2",
751+
"rand": "rand",
752+
"ifnull": "ifNull",
753+
"nullif": "nullIf",
754+
"coalesce": "coalesce",
755+
"greatest": "greatest",
756+
"least": "least",
757+
"concat_ws": "concat",
758+
"length": "length",
759+
"char_length": "length",
760+
}
761+
if n, ok := normalized[strings.ToLower(name)]; ok {
762+
return n
763+
}
764+
return name
765+
}
766+
695767
// operatorToFunction maps binary operators to ClickHouse function names
696768
func operatorToFunction(op string) string {
697769
switch op {

0 commit comments

Comments
 (0)