Skip to content

Commit 8790477

Browse files
kyleconroyclaude
andcommitted
Handle INSERT VALUES followed by SELECT on same line (#118)
ClickHouse has special handling where INSERT VALUES followed by SELECT on the same line outputs the INSERT AST and then executes the SELECT, printing its result. - Add ExplainStatements() function to handle multi-statement explain output - When first statement is INSERT and subsequent are SELECT with simple literals, append the literal values to match ClickHouse behavior - Update test to use ExplainStatements() for all explain output Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8941020 commit 8790477

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-6
lines changed

internal/explain/explain.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,85 @@ func Explain(stmt ast.Statement) string {
2323
return sb.String()
2424
}
2525

26+
// ExplainStatements returns the EXPLAIN AST output for multiple statements.
27+
// This handles the special ClickHouse behavior where INSERT VALUES followed by SELECT
28+
// on the same line outputs the INSERT AST and then executes the SELECT, printing its result.
29+
func ExplainStatements(stmts []ast.Statement) string {
30+
if len(stmts) == 0 {
31+
return ""
32+
}
33+
34+
var sb strings.Builder
35+
Node(&sb, stmts[0], 0)
36+
37+
// If the first statement is an INSERT and there are subsequent SELECT statements
38+
// with simple literals, append those literal values (matching ClickHouse's behavior)
39+
if _, isInsert := stmts[0].(*ast.InsertQuery); isInsert {
40+
for i := 1; i < len(stmts); i++ {
41+
if result := getSimpleSelectResult(stmts[i]); result != "" {
42+
sb.WriteString(result)
43+
sb.WriteString("\n")
44+
}
45+
}
46+
}
47+
48+
return sb.String()
49+
}
50+
51+
// getSimpleSelectResult extracts the literal value from a simple SELECT statement
52+
// like "SELECT 11111" and returns it as a string. Returns empty string if not a simple SELECT.
53+
func getSimpleSelectResult(stmt ast.Statement) string {
54+
// Check if it's a SelectWithUnionQuery
55+
selectUnion, ok := stmt.(*ast.SelectWithUnionQuery)
56+
if !ok {
57+
return ""
58+
}
59+
60+
// Must have exactly one select query
61+
if len(selectUnion.Selects) != 1 {
62+
return ""
63+
}
64+
65+
// Get the inner select query
66+
selectQuery, ok := selectUnion.Selects[0].(*ast.SelectQuery)
67+
if !ok {
68+
return ""
69+
}
70+
71+
// Must have exactly one expression in the select list
72+
if len(selectQuery.Columns) != 1 {
73+
return ""
74+
}
75+
76+
// Must be a literal
77+
literal, ok := selectQuery.Columns[0].(*ast.Literal)
78+
if !ok {
79+
return ""
80+
}
81+
82+
// Format the literal value
83+
return formatLiteralValue(literal)
84+
}
85+
86+
// formatLiteralValue formats a literal value as it would appear in query results
87+
func formatLiteralValue(lit *ast.Literal) string {
88+
switch v := lit.Value.(type) {
89+
case int64:
90+
return fmt.Sprintf("%d", v)
91+
case float64:
92+
return fmt.Sprintf("%v", v)
93+
case string:
94+
return v
95+
case bool:
96+
if v {
97+
return "1"
98+
}
99+
return "0"
100+
default:
101+
return fmt.Sprintf("%v", v)
102+
}
103+
}
104+
26105
// Node writes the EXPLAIN AST output for an AST node.
27106
func Node(sb *strings.Builder, node interface{}, depth int) {
28107
if node == nil {

parser/explain.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,10 @@ import (
99
func Explain(stmt ast.Statement) string {
1010
return explain.Explain(stmt)
1111
}
12+
13+
// ExplainStatements returns the EXPLAIN AST output for multiple statements.
14+
// This handles the special ClickHouse behavior where INSERT VALUES followed by SELECT
15+
// on the same line outputs the INSERT AST and then executes the SELECT, printing its result.
16+
func ExplainStatements(stmts []ast.Statement) string {
17+
return explain.ExplainStatements(stmts)
18+
}

parser/parser_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ func TestParser(t *testing.T) {
307307
t.Skipf("Skipping: empty expected output with clientError annotation")
308308
return
309309
}
310-
actual := strings.TrimSpace(parser.Explain(stmts[0]))
310+
actual := strings.TrimSpace(parser.ExplainStatements(stmts))
311311
// Use case-insensitive comparison since ClickHouse EXPLAIN AST has inconsistent casing
312312
if !strings.EqualFold(actual, expected) {
313313
if isExplainTodo && *checkExplain {
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt7": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)