Skip to content

Commit 1a73be6

Browse files
committed
Escape backslashes in aliases for EXPLAIN output
1 parent 84a07ff commit 1a73be6

2 files changed

Lines changed: 19 additions & 18 deletions

File tree

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)
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+
{}

0 commit comments

Comments
 (0)