Skip to content

Commit e4528da

Browse files
committed
Refactor explain.go into internal/explain package
Reorganize the explain functionality into a dedicated internal package with separate files for each category of AST types: - internal/explain/explain.go - main Explain function and core dispatcher - internal/explain/select.go - SelectQuery and related types - internal/explain/expressions.go - expressions (Literal, BinaryExpr, etc.) - internal/explain/functions.go - FunctionCall, Lambda, CAST, IN, etc. - internal/explain/tables.go - table-related types - internal/explain/statements.go - DDL statements (Create, Drop, etc.) - internal/explain/format.go - formatting and normalization functions The parser package now delegates to the internal package. No functional changes - all 5057 tests still pass.
1 parent 2bbdbcf commit e4528da

8 files changed

Lines changed: 1043 additions & 858 deletions

File tree

internal/explain/explain.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
// Package explain provides EXPLAIN AST output functionality for ClickHouse SQL.
2+
package explain
3+
4+
import (
5+
"fmt"
6+
"strings"
7+
8+
"github.com/kyleconroy/doubleclick/ast"
9+
)
10+
11+
// Explain returns the EXPLAIN AST output for a statement, matching ClickHouse's format.
12+
func Explain(stmt ast.Statement) string {
13+
var sb strings.Builder
14+
Node(&sb, stmt, 0)
15+
return sb.String()
16+
}
17+
18+
// Node writes the EXPLAIN AST output for an AST node.
19+
func Node(sb *strings.Builder, node interface{}, depth int) {
20+
if node == nil {
21+
// nil can represent an empty tuple in function arguments
22+
indent := strings.Repeat(" ", depth)
23+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
24+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
25+
return
26+
}
27+
28+
indent := strings.Repeat(" ", depth)
29+
30+
switch n := node.(type) {
31+
// Select statements
32+
case *ast.SelectWithUnionQuery:
33+
explainSelectWithUnionQuery(sb, n, indent, depth)
34+
case *ast.SelectQuery:
35+
explainSelectQuery(sb, n, indent, depth)
36+
37+
// Tables
38+
case *ast.TablesInSelectQuery:
39+
explainTablesInSelectQuery(sb, n, indent, depth)
40+
case *ast.TablesInSelectQueryElement:
41+
explainTablesInSelectQueryElement(sb, n, indent, depth)
42+
case *ast.TableExpression:
43+
explainTableExpression(sb, n, indent, depth)
44+
case *ast.TableIdentifier:
45+
explainTableIdentifier(sb, n, indent)
46+
case *ast.ArrayJoinClause:
47+
explainArrayJoinClause(sb, n, indent, depth)
48+
case *ast.TableJoin:
49+
explainTableJoin(sb, n, indent, depth)
50+
51+
// Expressions
52+
case *ast.OrderByElement:
53+
explainOrderByElement(sb, n, indent, depth)
54+
case *ast.Identifier:
55+
explainIdentifier(sb, n, indent)
56+
case *ast.Literal:
57+
explainLiteral(sb, n, indent, depth)
58+
case *ast.BinaryExpr:
59+
explainBinaryExpr(sb, n, indent, depth)
60+
case *ast.UnaryExpr:
61+
explainUnaryExpr(sb, n, indent, depth)
62+
case *ast.Subquery:
63+
explainSubquery(sb, n, indent, depth)
64+
case *ast.AliasedExpr:
65+
explainAliasedExpr(sb, n, depth)
66+
case *ast.Asterisk:
67+
explainAsterisk(sb, n, indent)
68+
69+
// Functions
70+
case *ast.FunctionCall:
71+
explainFunctionCall(sb, n, indent, depth)
72+
case *ast.Lambda:
73+
explainLambda(sb, n, indent, depth)
74+
case *ast.CastExpr:
75+
explainCastExpr(sb, n, indent, depth)
76+
case *ast.InExpr:
77+
explainInExpr(sb, n, indent, depth)
78+
case *ast.TernaryExpr:
79+
explainTernaryExpr(sb, n, indent, depth)
80+
case *ast.ArrayAccess:
81+
explainArrayAccess(sb, n, indent, depth)
82+
case *ast.TupleAccess:
83+
explainTupleAccess(sb, n, indent, depth)
84+
case *ast.LikeExpr:
85+
explainLikeExpr(sb, n, indent, depth)
86+
case *ast.BetweenExpr:
87+
explainBetweenExpr(sb, n, indent, depth)
88+
case *ast.IsNullExpr:
89+
explainIsNullExpr(sb, n, indent, depth)
90+
case *ast.CaseExpr:
91+
explainCaseExpr(sb, n, indent, depth)
92+
case *ast.IntervalExpr:
93+
explainIntervalExpr(sb, n, indent, depth)
94+
case *ast.ExistsExpr:
95+
explainExistsExpr(sb, n, indent, depth)
96+
case *ast.ExtractExpr:
97+
explainExtractExpr(sb, n, indent, depth)
98+
99+
// DDL statements
100+
case *ast.CreateQuery:
101+
explainCreateQuery(sb, n, indent, depth)
102+
case *ast.DropQuery:
103+
explainDropQuery(sb, n, indent)
104+
case *ast.SetQuery:
105+
explainSetQuery(sb, indent)
106+
case *ast.SystemQuery:
107+
explainSystemQuery(sb, indent)
108+
case *ast.ExplainQuery:
109+
explainExplainQuery(sb, n, indent, depth)
110+
case *ast.ShowQuery:
111+
explainShowQuery(sb, n, indent)
112+
case *ast.UseQuery:
113+
explainUseQuery(sb, n, indent)
114+
case *ast.DescribeQuery:
115+
explainDescribeQuery(sb, n, indent)
116+
117+
// Types
118+
case *ast.DataType:
119+
explainDataType(sb, n, indent)
120+
case *ast.Parameter:
121+
explainParameter(sb, n, indent)
122+
123+
default:
124+
// For unhandled types, just print the type name
125+
fmt.Fprintf(sb, "%s%T\n", indent, node)
126+
}
127+
}
128+
129+
// TablesWithArrayJoin handles FROM and ARRAY JOIN together as TablesInSelectQuery
130+
func TablesWithArrayJoin(sb *strings.Builder, from *ast.TablesInSelectQuery, arrayJoin *ast.ArrayJoinClause, depth int) {
131+
indent := strings.Repeat(" ", depth)
132+
133+
tableCount := 0
134+
if from != nil {
135+
tableCount = len(from.Tables)
136+
}
137+
if arrayJoin != nil {
138+
tableCount++
139+
}
140+
141+
fmt.Fprintf(sb, "%sTablesInSelectQuery (children %d)\n", indent, tableCount)
142+
143+
if from != nil {
144+
for _, t := range from.Tables {
145+
Node(sb, t, depth+1)
146+
}
147+
}
148+
149+
if arrayJoin != nil {
150+
// ARRAY JOIN is wrapped in TablesInSelectQueryElement
151+
fmt.Fprintf(sb, "%s TablesInSelectQueryElement (children %d)\n", indent, 1)
152+
Node(sb, arrayJoin, depth+2)
153+
}
154+
}
155+
156+
// Column handles column declarations
157+
func Column(sb *strings.Builder, col *ast.ColumnDeclaration, depth int) {
158+
indent := strings.Repeat(" ", depth)
159+
children := 0
160+
if col.Type != nil {
161+
children++
162+
}
163+
if col.Default != nil {
164+
children++
165+
}
166+
fmt.Fprintf(sb, "%sColumnDeclaration %s (children %d)\n", indent, col.Name, children)
167+
if col.Type != nil {
168+
fmt.Fprintf(sb, "%s DataType %s\n", indent, FormatDataType(col.Type))
169+
}
170+
if col.Default != nil {
171+
Node(sb, col.Default, depth+1)
172+
}
173+
}

internal/explain/expressions.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package explain
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/kyleconroy/doubleclick/ast"
8+
)
9+
10+
func explainIdentifier(sb *strings.Builder, n *ast.Identifier, indent string) {
11+
name := n.Name()
12+
if n.Alias != "" {
13+
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, name, n.Alias)
14+
} else {
15+
fmt.Fprintf(sb, "%sIdentifier %s\n", indent, name)
16+
}
17+
}
18+
19+
func explainLiteral(sb *strings.Builder, n *ast.Literal, indent string, depth int) {
20+
// Check if this is a tuple - either with expressions or empty
21+
if n.Type == ast.LiteralTuple {
22+
if exprs, ok := n.Value.([]ast.Expression); ok {
23+
// Check if empty tuple or has complex expressions
24+
if len(exprs) == 0 {
25+
// Empty tuple renders as Function tuple with empty ExpressionList
26+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
27+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
28+
return
29+
}
30+
hasComplexExpr := false
31+
for _, e := range exprs {
32+
if _, isLit := e.(*ast.Literal); !isLit {
33+
hasComplexExpr = true
34+
break
35+
}
36+
}
37+
if hasComplexExpr {
38+
// Render as Function tuple instead of Literal
39+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
40+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
41+
for _, e := range exprs {
42+
Node(sb, e, depth+2)
43+
}
44+
return
45+
}
46+
} else if n.Value == nil {
47+
// nil value means empty tuple
48+
fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1)
49+
fmt.Fprintf(sb, "%s ExpressionList\n", indent)
50+
return
51+
}
52+
}
53+
// Check if this is an array with complex expressions that should be rendered as Function array
54+
if n.Type == ast.LiteralArray {
55+
if exprs, ok := n.Value.([]ast.Expression); ok {
56+
hasComplexExpr := false
57+
for _, e := range exprs {
58+
if _, isLit := e.(*ast.Literal); !isLit {
59+
hasComplexExpr = true
60+
break
61+
}
62+
}
63+
if hasComplexExpr {
64+
// Render as Function array instead of Literal
65+
fmt.Fprintf(sb, "%sFunction array (children %d)\n", indent, 1)
66+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
67+
for _, e := range exprs {
68+
Node(sb, e, depth+2)
69+
}
70+
return
71+
}
72+
}
73+
}
74+
fmt.Fprintf(sb, "%sLiteral %s\n", indent, FormatLiteral(n))
75+
}
76+
77+
func explainBinaryExpr(sb *strings.Builder, n *ast.BinaryExpr, indent string, depth int) {
78+
// Convert operator to function name
79+
fnName := OperatorToFunction(n.Op)
80+
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
81+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
82+
Node(sb, n.Left, depth+2)
83+
Node(sb, n.Right, depth+2)
84+
}
85+
86+
func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, depth int) {
87+
fnName := UnaryOperatorToFunction(n.Op)
88+
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
89+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
90+
Node(sb, n.Operand, depth+2)
91+
}
92+
93+
func explainSubquery(sb *strings.Builder, n *ast.Subquery, indent string, depth int) {
94+
children := 1
95+
fmt.Fprintf(sb, "%sSubquery (children %d)\n", indent, children)
96+
Node(sb, n.Query, depth+1)
97+
}
98+
99+
func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
100+
// For aliased expressions, we need to show the underlying expression with the alias
101+
indent := strings.Repeat(" ", depth)
102+
103+
switch e := n.Expr.(type) {
104+
case *ast.Literal:
105+
// Check if this is a tuple with complex expressions that should be rendered as Function tuple
106+
if e.Type == ast.LiteralTuple {
107+
if exprs, ok := e.Value.([]ast.Expression); ok {
108+
hasComplexExpr := false
109+
for _, expr := range exprs {
110+
if _, isLit := expr.(*ast.Literal); !isLit {
111+
hasComplexExpr = true
112+
break
113+
}
114+
}
115+
if hasComplexExpr {
116+
// Render as Function tuple with alias
117+
fmt.Fprintf(sb, "%sFunction tuple (alias %s) (children %d)\n", indent, n.Alias, 1)
118+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
119+
for _, expr := range exprs {
120+
Node(sb, expr, depth+2)
121+
}
122+
return
123+
}
124+
}
125+
}
126+
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), n.Alias)
127+
default:
128+
// For other types, recursively explain and add alias info
129+
Node(sb, n.Expr, depth)
130+
}
131+
}
132+
133+
func explainAsterisk(sb *strings.Builder, n *ast.Asterisk, indent string) {
134+
if n.Table != "" {
135+
fmt.Fprintf(sb, "%sQualifiedAsterisk (children %d)\n", indent, 1)
136+
fmt.Fprintf(sb, "%s Identifier %s\n", indent, n.Table)
137+
} else {
138+
fmt.Fprintf(sb, "%sAsterisk\n", indent)
139+
}
140+
}

0 commit comments

Comments
 (0)