|
| 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, depth) |
| 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 | + Node(sb, col.Type, depth+1) |
| 169 | + } |
| 170 | + if col.Default != nil { |
| 171 | + Node(sb, col.Default, depth+1) |
| 172 | + } |
| 173 | +} |
0 commit comments