Skip to content

Commit fec9a6b

Browse files
authored
Fix SETTINGS placement in EXPLAIN output based on SettingsAfterFormat (#84)
1 parent 0d6bbbe commit fec9a6b

File tree

61 files changed

+80
-256
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+80
-256
lines changed

internal/explain/expressions.go

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@ import (
77
"github.com/sqlc-dev/doubleclick/ast"
88
)
99

10+
// escapeAlias escapes backslashes in alias names for EXPLAIN output
11+
func escapeAlias(alias string) string {
12+
return strings.ReplaceAll(alias, "\\", "\\\\")
13+
}
14+
1015
func explainIdentifier(sb *strings.Builder, n *ast.Identifier, indent string) {
1116
name := formatIdentifierName(n)
1217
if n.Alias != "" {
13-
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, name, n.Alias)
18+
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, name, escapeAlias(n.Alias))
1419
} else {
1520
fmt.Fprintf(sb, "%sIdentifier %s\n", indent, name)
1621
}
@@ -319,7 +324,7 @@ func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, dept
319324
func explainSubquery(sb *strings.Builder, n *ast.Subquery, indent string, depth int) {
320325
children := 1
321326
if n.Alias != "" {
322-
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, n.Alias, children)
327+
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), children)
323328
} else {
324329
fmt.Fprintf(sb, "%sSubquery (children %d)\n", indent, children)
325330
}
@@ -349,7 +354,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
349354
}
350355
if hasComplexExpr {
351356
// Render as Function tuple with alias
352-
fmt.Fprintf(sb, "%sFunction tuple (alias %s) (children %d)\n", indent, n.Alias, 1)
357+
fmt.Fprintf(sb, "%sFunction tuple (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
353358
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
354359
for _, expr := range exprs {
355360
Node(sb, expr, depth+2)
@@ -380,7 +385,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
380385
}
381386
if needsFunctionFormat {
382387
// Render as Function array with alias
383-
fmt.Fprintf(sb, "%sFunction array (alias %s) (children %d)\n", indent, n.Alias, 1)
388+
fmt.Fprintf(sb, "%sFunction array (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
384389
if len(exprs) > 0 {
385390
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
386391
} else {
@@ -393,20 +398,20 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
393398
}
394399
}
395400
}
396-
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), n.Alias)
401+
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), escapeAlias(n.Alias))
397402
case *ast.BinaryExpr:
398403
// Binary expressions become functions with alias
399404
fnName := OperatorToFunction(e.Op)
400405
// For || (concat) operator, flatten chained concatenations
401406
if e.Op == "||" {
402407
operands := collectConcatOperands(e)
403-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
408+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
404409
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
405410
for _, op := range operands {
406411
Node(sb, op, depth+2)
407412
}
408413
} else {
409-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
414+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
410415
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
411416
Node(sb, e.Left, depth+2)
412417
Node(sb, e.Right, depth+2)
@@ -423,39 +428,39 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
423428
if inSubqueryContext {
424429
switch val := lit.Value.(type) {
425430
case int64:
426-
fmt.Fprintf(sb, "%sLiteral Int64_%d (alias %s)\n", indent, -val, n.Alias)
431+
fmt.Fprintf(sb, "%sLiteral Int64_%d (alias %s)\n", indent, -val, escapeAlias(n.Alias))
427432
return
428433
case uint64:
429-
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, n.Alias)
434+
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, escapeAlias(n.Alias))
430435
return
431436
}
432437
}
433438
case ast.LiteralFloat:
434439
// Always convert negated floats to literals (especially for -inf, -nan)
435440
val := lit.Value.(float64)
436441
s := FormatFloat(-val)
437-
fmt.Fprintf(sb, "%sLiteral Float64_%s (alias %s)\n", indent, s, n.Alias)
442+
fmt.Fprintf(sb, "%sLiteral Float64_%s (alias %s)\n", indent, s, escapeAlias(n.Alias))
438443
return
439444
}
440445
}
441446
}
442447
// Unary expressions become functions with alias
443448
fnName := UnaryOperatorToFunction(e.Op)
444-
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
449+
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
445450
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
446451
Node(sb, e.Operand, depth+2)
447452
case *ast.FunctionCall:
448453
// Function calls already handle aliases
449454
explainFunctionCallWithAlias(sb, e, n.Alias, indent, depth)
450455
case *ast.Identifier:
451456
// Identifiers with alias
452-
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, e.Name(), n.Alias)
457+
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, e.Name(), escapeAlias(n.Alias))
453458
case *ast.IntervalExpr:
454459
// Interval expressions with alias
455460
explainIntervalExpr(sb, e, n.Alias, indent, depth)
456461
case *ast.TernaryExpr:
457462
// Ternary expressions become if functions with alias
458-
fmt.Fprintf(sb, "%sFunction if (alias %s) (children %d)\n", indent, n.Alias, 1)
463+
fmt.Fprintf(sb, "%sFunction if (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
459464
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 3)
460465
Node(sb, e.Condition, depth+2)
461466
Node(sb, e.Then, depth+2)

internal/explain/select.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,9 @@ func explainSelectWithUnionQuery(sb *strings.Builder, n *ast.SelectWithUnionQuer
4242
break
4343
}
4444
}
45-
// When FORMAT is present, SETTINGS is output at SelectWithUnionQuery level
45+
// When SETTINGS comes AFTER FORMAT, it's output at SelectWithUnionQuery level
4646
for _, sel := range n.Selects {
47-
if sq, ok := sel.(*ast.SelectQuery); ok && sq.Format != nil && len(sq.Settings) > 0 {
47+
if sq, ok := sel.(*ast.SelectQuery); ok && sq.SettingsAfterFormat && len(sq.Settings) > 0 {
4848
fmt.Fprintf(sb, "%s Set\n", indent)
4949
break
5050
}
@@ -122,9 +122,9 @@ func explainSelectQuery(sb *strings.Builder, n *ast.SelectQuery, indent string,
122122
Node(sb, expr, depth+2)
123123
}
124124
}
125-
// SETTINGS - output at SelectQuery level only if there's no FORMAT
126-
// When FORMAT is present, SETTINGS is at SelectWithUnionQuery level instead
127-
if len(n.Settings) > 0 && n.Format == nil {
125+
// SETTINGS is output at SelectQuery level only when NOT after FORMAT
126+
// When SettingsAfterFormat is true, it's output at SelectWithUnionQuery level instead
127+
if len(n.Settings) > 0 && !n.SettingsAfterFormat {
128128
fmt.Fprintf(sb, "%s Set\n", indent)
129129
}
130130
}
@@ -238,9 +238,9 @@ func countSelectUnionChildren(n *ast.SelectWithUnionQuery) int {
238238
break
239239
}
240240
}
241-
// When FORMAT is present, SETTINGS is counted at SelectWithUnionQuery level
241+
// When SETTINGS comes AFTER FORMAT, it's counted at SelectWithUnionQuery level
242242
for _, sel := range n.Selects {
243-
if sq, ok := sel.(*ast.SelectQuery); ok && sq.Format != nil && len(sq.Settings) > 0 {
243+
if sq, ok := sel.(*ast.SelectQuery); ok && sq.SettingsAfterFormat && len(sq.Settings) > 0 {
244244
count++
245245
break
246246
}
@@ -294,9 +294,8 @@ func countSelectQueryChildren(n *ast.SelectQuery) int {
294294
if n.Offset != nil {
295295
count++
296296
}
297-
// SETTINGS is counted at SelectQuery level only if there's no FORMAT
298-
// When FORMAT is present, SETTINGS is at SelectWithUnionQuery level instead
299-
if len(n.Settings) > 0 && n.Format == nil {
297+
// SETTINGS is counted at SelectQuery level only when NOT after FORMAT
298+
if len(n.Settings) > 0 && !n.SettingsAfterFormat {
300299
count++
301300
}
302301
return count
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt3": true,
4-
"stmt5": true
5-
}
6-
}
1+
{}
Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,7 @@
11
{
22
"explain_todo": {
3-
"stmt10": true,
4-
"stmt11": true,
53
"stmt13": true,
6-
"stmt14": true,
7-
"stmt15": true,
8-
"stmt16": true,
9-
"stmt17": true,
10-
"stmt18": true,
11-
"stmt19": true,
12-
"stmt20": true,
134
"stmt22": true,
14-
"stmt23": true,
15-
"stmt24": true,
16-
"stmt25": true,
17-
"stmt26": true,
18-
"stmt27": true,
19-
"stmt28": true,
20-
"stmt29": true,
21-
"stmt4": true,
22-
"stmt5": true,
23-
"stmt6": true,
24-
"stmt7": true,
25-
"stmt8": true,
26-
"stmt9": true
5+
"stmt4": true
276
}
287
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"explain_todo":{"stmt10":true,"stmt12":true,"stmt13":true,"stmt14":true,"stmt15":true,"stmt16":true,"stmt17":true,"stmt18":true,"stmt20":true,"stmt21":true,"stmt22":true,"stmt23":true,"stmt24":true,"stmt25":true,"stmt26":true,"stmt4":true,"stmt5":true,"stmt6":true,"stmt7":true,"stmt8":true,"stmt9":true}}
1+
{}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"explain_todo":{"stmt8":true,"stmt9":true}}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt21": true
4-
}
5-
}
1+
{}
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt2": true
4-
}
5-
}
1+
{}

parser/testdata/01074_partial_revokes/metadata.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
"stmt34": true,
2121
"stmt35": true,
2222
"stmt36": true,
23-
"stmt37": true,
2423
"stmt39": true,
2524
"stmt4": true,
2625
"stmt40": true,
@@ -41,7 +40,6 @@
4140
"stmt59": true,
4241
"stmt6": true,
4342
"stmt60": true,
44-
"stmt61": true,
4543
"stmt63": true,
4644
"stmt64": true,
4745
"stmt66": true,
Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
{
2-
"explain_todo": {
3-
"stmt1": true
4-
}
5-
}
1+
{}

0 commit comments

Comments
 (0)