Skip to content

Commit f7a6459

Browse files
committed
Add support for WITH...INSERT...SELECT syntax
- Store inherited WITH clause in InsertQuery.With instead of propagating to SelectQuery.With - Add recursive explain functions to handle inherited WITH in select trees - Output inherited WITH at the end of each SelectQuery's children (after tables) - Handle SelectWithUnionQuery and SelectIntersectExceptQuery with inherited WITH Fixes 3 statements in 03248_with_insert and 5 additional statements in other tests.
1 parent 5c8df3c commit f7a6459

File tree

8 files changed

+351
-35
lines changed

8 files changed

+351
-35
lines changed

ast/ast.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ type InsertQuery struct {
254254
Compression string `json:"compression,omitempty"` // For COMPRESSION clause
255255
Values [][]Expression `json:"-"` // For VALUES clause (format only, not in AST JSON)
256256
Select Statement `json:"select,omitempty"`
257+
With []Expression `json:"with,omitempty"` // For WITH ... INSERT ... SELECT syntax
257258
Format *Identifier `json:"format,omitempty"`
258259
HasSettings bool `json:"has_settings,omitempty"` // For SETTINGS clause
259260
Settings []*SettingExpr `json:"settings,omitempty"` // For SETTINGS clause in INSERT

internal/explain/select.go

Lines changed: 102 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func extractWithClause(stmt ast.Statement) []ast.Expression {
6161
}
6262

6363
// explainSelectQueryWithInheritedWith outputs a SELECT with an inherited WITH clause
64-
// The inherited WITH clause is output AFTER the columns (not before, like a regular WITH)
64+
// The inherited WITH clause is output at the END of children (after columns and tables)
6565
func explainSelectQueryWithInheritedWith(sb *strings.Builder, stmt ast.Statement, inheritedWith []ast.Expression, depth int) {
6666
sq, ok := stmt.(*ast.SelectQuery)
6767
if !ok {
@@ -76,23 +76,17 @@ func explainSelectQueryWithInheritedWith(sb *strings.Builder, stmt ast.Statement
7676
return
7777
}
7878

79-
// Output SelectQuery with inherited WITH clause after columns
79+
// Output SelectQuery with inherited WITH clause at the end
8080
indent := strings.Repeat(" ", depth)
8181
children := countSelectQueryChildren(sq) + 1 // +1 for inherited WITH clause
8282
fmt.Fprintf(sb, "%sSelectQuery (children %d)\n", indent, children)
8383

84-
// Columns (ExpressionList) - output BEFORE inherited WITH
84+
// Columns (ExpressionList) - output first
8585
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(sq.Columns))
8686
for _, col := range sq.Columns {
8787
Node(sb, col, depth+2)
8888
}
8989

90-
// Inherited WITH clause (ExpressionList) - output AFTER columns
91-
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(inheritedWith))
92-
for _, w := range inheritedWith {
93-
Node(sb, w, depth+2)
94-
}
95-
9690
// FROM (including ARRAY JOIN as part of TablesInSelectQuery)
9791
if sq.From != nil || sq.ArrayJoin != nil {
9892
TablesWithArrayJoin(sb, sq.From, sq.ArrayJoin, depth+1)
@@ -180,6 +174,105 @@ func explainSelectQueryWithInheritedWith(sb *strings.Builder, stmt ast.Statement
180174
if sq.Top != nil {
181175
Node(sb, sq.Top, depth+1)
182176
}
177+
178+
// Inherited WITH clause (ExpressionList) - output at the END
179+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(inheritedWith))
180+
for _, w := range inheritedWith {
181+
Node(sb, w, depth+2)
182+
}
183+
}
184+
185+
// ExplainSelectWithInheritedWith recursively explains a select statement with inherited WITH clause
186+
// This is used for WITH ... INSERT ... SELECT where the WITH clause belongs to the INSERT
187+
// but needs to be output at the end of each SelectQuery in the tree
188+
func ExplainSelectWithInheritedWith(sb *strings.Builder, stmt ast.Statement, inheritedWith []ast.Expression, depth int) {
189+
switch s := stmt.(type) {
190+
case *ast.SelectWithUnionQuery:
191+
explainSelectWithUnionQueryWithInheritedWith(sb, s, inheritedWith, depth)
192+
case *ast.SelectIntersectExceptQuery:
193+
explainSelectIntersectExceptQueryWithInheritedWith(sb, s, inheritedWith, depth)
194+
case *ast.SelectQuery:
195+
explainSelectQueryWithInheritedWith(sb, s, inheritedWith, depth)
196+
default:
197+
Node(sb, stmt, depth)
198+
}
199+
}
200+
201+
// explainSelectWithUnionQueryWithInheritedWith explains a SelectWithUnionQuery with inherited WITH
202+
func explainSelectWithUnionQueryWithInheritedWith(sb *strings.Builder, n *ast.SelectWithUnionQuery, inheritedWith []ast.Expression, depth int) {
203+
if n == nil {
204+
return
205+
}
206+
indent := strings.Repeat(" ", depth)
207+
children := countSelectUnionChildren(n)
208+
fmt.Fprintf(sb, "%sSelectWithUnionQuery (children %d)\n", indent, children)
209+
210+
selects := simplifyUnionSelects(n.Selects)
211+
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(selects))
212+
for _, sel := range selects {
213+
ExplainSelectWithInheritedWith(sb, sel, inheritedWith, depth+2)
214+
}
215+
216+
// INTO OUTFILE clause
217+
for _, sel := range n.Selects {
218+
if sq, ok := sel.(*ast.SelectQuery); ok && sq.IntoOutfile != nil {
219+
fmt.Fprintf(sb, "%s Literal \\'%s\\'\n", indent, sq.IntoOutfile.Filename)
220+
break
221+
}
222+
}
223+
// SETTINGS before FORMAT
224+
if n.SettingsBeforeFormat && len(n.Settings) > 0 {
225+
fmt.Fprintf(sb, "%s Set\n", indent)
226+
}
227+
// FORMAT clause - check individual SelectQuery nodes
228+
for _, sel := range n.Selects {
229+
if sq, ok := sel.(*ast.SelectQuery); ok && sq.Format != nil {
230+
Node(sb, sq.Format, depth+1)
231+
break
232+
}
233+
}
234+
// SETTINGS after FORMAT
235+
if n.SettingsAfterFormat && len(n.Settings) > 0 {
236+
fmt.Fprintf(sb, "%s Set\n", indent)
237+
} else {
238+
for _, sel := range n.Selects {
239+
if sq, ok := sel.(*ast.SelectQuery); ok && sq.SettingsAfterFormat && len(sq.Settings) > 0 {
240+
fmt.Fprintf(sb, "%s Set\n", indent)
241+
break
242+
}
243+
}
244+
}
245+
}
246+
247+
// explainSelectIntersectExceptQueryWithInheritedWith explains a SelectIntersectExceptQuery with inherited WITH
248+
func explainSelectIntersectExceptQueryWithInheritedWith(sb *strings.Builder, n *ast.SelectIntersectExceptQuery, inheritedWith []ast.Expression, depth int) {
249+
indent := strings.Repeat(" ", depth)
250+
fmt.Fprintf(sb, "%sSelectIntersectExceptQuery (children %d)\n", indent, len(n.Selects))
251+
252+
// Check if EXCEPT is present - affects how first operand is wrapped
253+
hasExcept := false
254+
for _, op := range n.Operators {
255+
if strings.HasPrefix(op, "EXCEPT") {
256+
hasExcept = true
257+
break
258+
}
259+
}
260+
261+
for i, sel := range n.Selects {
262+
if hasExcept && i == 0 {
263+
// Wrap first operand in SelectWithUnionQuery format
264+
if _, isUnion := sel.(*ast.SelectWithUnionQuery); isUnion {
265+
ExplainSelectWithInheritedWith(sb, sel, inheritedWith, depth+1)
266+
} else {
267+
childIndent := strings.Repeat(" ", depth+1)
268+
fmt.Fprintf(sb, "%sSelectWithUnionQuery (children 1)\n", childIndent)
269+
fmt.Fprintf(sb, "%s ExpressionList (children 1)\n", childIndent)
270+
ExplainSelectWithInheritedWith(sb, sel, inheritedWith, depth+3)
271+
}
272+
} else {
273+
ExplainSelectWithInheritedWith(sb, sel, inheritedWith, depth+1)
274+
}
275+
}
183276
}
184277

185278
func explainSelectWithUnionQuery(sb *strings.Builder, n *ast.SelectWithUnionQuery, indent string, depth int) {

internal/explain/statements.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,13 @@ func explainInsertQuery(sb *strings.Builder, n *ast.InsertQuery, indent string,
9797
}
9898
}
9999
}
100-
Node(sb, n.Select, depth+1)
100+
// If this INSERT has an inherited WITH clause (from WITH ... INSERT syntax),
101+
// use the special explain function that outputs WITH at the end of each SelectQuery
102+
if len(n.With) > 0 {
103+
ExplainSelectWithInheritedWith(sb, n.Select, n.With, depth+1)
104+
} else {
105+
Node(sb, n.Select, depth+1)
106+
}
101107
}
102108

103109
if n.HasSettings {

0 commit comments

Comments
 (0)