Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions internal/explain/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ import (
"github.com/sqlc-dev/doubleclick/ast"
)

// escapeAlias escapes backslashes in alias names for EXPLAIN output
func escapeAlias(alias string) string {
return strings.ReplaceAll(alias, "\\", "\\\\")
}

func explainIdentifier(sb *strings.Builder, n *ast.Identifier, indent string) {
name := formatIdentifierName(n)
if n.Alias != "" {
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, name, n.Alias)
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, name, escapeAlias(n.Alias))
} else {
fmt.Fprintf(sb, "%sIdentifier %s\n", indent, name)
}
Expand Down Expand Up @@ -319,7 +324,7 @@ func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, dept
func explainSubquery(sb *strings.Builder, n *ast.Subquery, indent string, depth int) {
children := 1
if n.Alias != "" {
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, n.Alias, children)
fmt.Fprintf(sb, "%sSubquery (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), children)
} else {
fmt.Fprintf(sb, "%sSubquery (children %d)\n", indent, children)
}
Expand Down Expand Up @@ -349,7 +354,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
}
if hasComplexExpr {
// Render as Function tuple with alias
fmt.Fprintf(sb, "%sFunction tuple (alias %s) (children %d)\n", indent, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction tuple (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
for _, expr := range exprs {
Node(sb, expr, depth+2)
Expand Down Expand Up @@ -380,7 +385,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
}
if needsFunctionFormat {
// Render as Function array with alias
fmt.Fprintf(sb, "%sFunction array (alias %s) (children %d)\n", indent, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction array (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
if len(exprs) > 0 {
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs))
} else {
Expand All @@ -393,20 +398,20 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
}
}
}
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), n.Alias)
fmt.Fprintf(sb, "%sLiteral %s (alias %s)\n", indent, FormatLiteral(e), escapeAlias(n.Alias))
case *ast.BinaryExpr:
// Binary expressions become functions with alias
fnName := OperatorToFunction(e.Op)
// For || (concat) operator, flatten chained concatenations
if e.Op == "||" {
operands := collectConcatOperands(e)
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
for _, op := range operands {
Node(sb, op, depth+2)
}
} else {
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
Node(sb, e.Left, depth+2)
Node(sb, e.Right, depth+2)
Expand All @@ -423,39 +428,39 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
if inSubqueryContext {
switch val := lit.Value.(type) {
case int64:
fmt.Fprintf(sb, "%sLiteral Int64_%d (alias %s)\n", indent, -val, n.Alias)
fmt.Fprintf(sb, "%sLiteral Int64_%d (alias %s)\n", indent, -val, escapeAlias(n.Alias))
return
case uint64:
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, n.Alias)
fmt.Fprintf(sb, "%sLiteral Int64_-%d (alias %s)\n", indent, val, escapeAlias(n.Alias))
return
}
}
case ast.LiteralFloat:
// Always convert negated floats to literals (especially for -inf, -nan)
val := lit.Value.(float64)
s := FormatFloat(-val)
fmt.Fprintf(sb, "%sLiteral Float64_%s (alias %s)\n", indent, s, n.Alias)
fmt.Fprintf(sb, "%sLiteral Float64_%s (alias %s)\n", indent, s, escapeAlias(n.Alias))
return
}
}
}
// Unary expressions become functions with alias
fnName := UnaryOperatorToFunction(e.Op)
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1)
Node(sb, e.Operand, depth+2)
case *ast.FunctionCall:
// Function calls already handle aliases
explainFunctionCallWithAlias(sb, e, n.Alias, indent, depth)
case *ast.Identifier:
// Identifiers with alias
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, e.Name(), n.Alias)
fmt.Fprintf(sb, "%sIdentifier %s (alias %s)\n", indent, e.Name(), escapeAlias(n.Alias))
case *ast.IntervalExpr:
// Interval expressions with alias
explainIntervalExpr(sb, e, n.Alias, indent, depth)
case *ast.TernaryExpr:
// Ternary expressions become if functions with alias
fmt.Fprintf(sb, "%sFunction if (alias %s) (children %d)\n", indent, n.Alias, 1)
fmt.Fprintf(sb, "%sFunction if (alias %s) (children %d)\n", indent, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 3)
Node(sb, e.Condition, depth+2)
Node(sb, e.Then, depth+2)
Expand Down
19 changes: 9 additions & 10 deletions internal/explain/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ func explainSelectWithUnionQuery(sb *strings.Builder, n *ast.SelectWithUnionQuer
break
}
}
// When FORMAT is present, SETTINGS is output at SelectWithUnionQuery level
// When SETTINGS comes AFTER FORMAT, it's output at SelectWithUnionQuery level
for _, sel := range n.Selects {
if sq, ok := sel.(*ast.SelectQuery); ok && sq.Format != nil && len(sq.Settings) > 0 {
if sq, ok := sel.(*ast.SelectQuery); ok && sq.SettingsAfterFormat && len(sq.Settings) > 0 {
fmt.Fprintf(sb, "%s Set\n", indent)
break
}
Expand Down Expand Up @@ -122,9 +122,9 @@ func explainSelectQuery(sb *strings.Builder, n *ast.SelectQuery, indent string,
Node(sb, expr, depth+2)
}
}
// SETTINGS - output at SelectQuery level only if there's no FORMAT
// When FORMAT is present, SETTINGS is at SelectWithUnionQuery level instead
if len(n.Settings) > 0 && n.Format == nil {
// SETTINGS is output at SelectQuery level only when NOT after FORMAT
// When SettingsAfterFormat is true, it's output at SelectWithUnionQuery level instead
if len(n.Settings) > 0 && !n.SettingsAfterFormat {
fmt.Fprintf(sb, "%s Set\n", indent)
}
}
Expand Down Expand Up @@ -238,9 +238,9 @@ func countSelectUnionChildren(n *ast.SelectWithUnionQuery) int {
break
}
}
// When FORMAT is present, SETTINGS is counted at SelectWithUnionQuery level
// When SETTINGS comes AFTER FORMAT, it's counted at SelectWithUnionQuery level
for _, sel := range n.Selects {
if sq, ok := sel.(*ast.SelectQuery); ok && sq.Format != nil && len(sq.Settings) > 0 {
if sq, ok := sel.(*ast.SelectQuery); ok && sq.SettingsAfterFormat && len(sq.Settings) > 0 {
count++
break
}
Expand Down Expand Up @@ -294,9 +294,8 @@ func countSelectQueryChildren(n *ast.SelectQuery) int {
if n.Offset != nil {
count++
}
// SETTINGS is counted at SelectQuery level only if there's no FORMAT
// When FORMAT is present, SETTINGS is at SelectWithUnionQuery level instead
if len(n.Settings) > 0 && n.Format == nil {
// SETTINGS is counted at SelectQuery level only when NOT after FORMAT
if len(n.Settings) > 0 && !n.SettingsAfterFormat {
count++
}
return count
Expand Down
7 changes: 1 addition & 6 deletions parser/testdata/00307_format_xml/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
{
"explain_todo": {
"stmt3": true,
"stmt5": true
}
}
{}
23 changes: 1 addition & 22 deletions parser/testdata/00405_output_format_pretty_color/metadata.json
Original file line number Diff line number Diff line change
@@ -1,28 +1,7 @@
{
"explain_todo": {
"stmt10": true,
"stmt11": true,
"stmt13": true,
"stmt14": true,
"stmt15": true,
"stmt16": true,
"stmt17": true,
"stmt18": true,
"stmt19": true,
"stmt20": true,
"stmt22": true,
"stmt23": true,
"stmt24": true,
"stmt25": true,
"stmt26": true,
"stmt27": true,
"stmt28": true,
"stmt29": true,
"stmt4": true,
"stmt5": true,
"stmt6": true,
"stmt7": true,
"stmt8": true,
"stmt9": true
"stmt4": true
}
}
2 changes: 1 addition & 1 deletion parser/testdata/00405_pretty_formats/metadata.json
Original file line number Diff line number Diff line change
@@ -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}}
{}
2 changes: 1 addition & 1 deletion parser/testdata/00722_inner_join/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt8":true,"stmt9":true}}
{}
6 changes: 1 addition & 5 deletions parser/testdata/00730_unicode_terminal_format/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt21": true
}
}
{}
6 changes: 1 addition & 5 deletions parser/testdata/00963_achimbab/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt2": true
}
}
{}
2 changes: 0 additions & 2 deletions parser/testdata/01074_partial_revokes/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
"stmt34": true,
"stmt35": true,
"stmt36": true,
"stmt37": true,
"stmt39": true,
"stmt4": true,
"stmt40": true,
Expand All @@ -41,7 +40,6 @@
"stmt59": true,
"stmt6": true,
"stmt60": true,
"stmt61": true,
"stmt63": true,
"stmt64": true,
"stmt66": true,
Expand Down
6 changes: 1 addition & 5 deletions parser/testdata/01104_distributed_numbers_test/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt1": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt4": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt1": true
}
}
{}
2 changes: 1 addition & 1 deletion parser/testdata/01318_parallel_final_stuck/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt5":true}}
{}
6 changes: 1 addition & 5 deletions parser/testdata/01553_settings_early_apply/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt11": true
}
}
{}
1 change: 0 additions & 1 deletion parser/testdata/01591_window_functions/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"stmt39": true,
"stmt41": true,
"stmt45": true,
"stmt46": true,
"stmt50": true,
"stmt51": true,
"stmt52": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
{
"explain_todo": {
"stmt5": true,
"stmt6": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"explain_todo": {
"stmt10": true,
"stmt5": true,
"stmt8": true,
"stmt9": true
"stmt5": true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"stmt24": true,
"stmt26": true,
"stmt3": true,
"stmt4": true,
"stmt7": true,
"stmt9": true
}
Expand Down
3 changes: 1 addition & 2 deletions parser/testdata/01670_neighbor_lc_bug/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
{
"explain_todo": {
"stmt3": true,
"stmt7": true
"stmt3": true
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt8": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt4":true,"stmt5":true,"stmt6":true,"stmt7":true}}
{}
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt1": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt3":true}}
{}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"explain_todo": {
"stmt10": true,
"stmt14": true,
"stmt6": true
}
}
6 changes: 1 addition & 5 deletions parser/testdata/02180_group_by_lowcardinality/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt4": true
}
}
{}
6 changes: 1 addition & 5 deletions parser/testdata/02270_client_name/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt1": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
{
"explain_todo": {
"stmt4": true,
"stmt5": true,
"stmt6": true,
"stmt7": true
}
}
{}
5 changes: 1 addition & 4 deletions parser/testdata/02402_external_disk_metrics/metadata.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
{
"explain_todo": {
"stmt10": true,
"stmt14": true,
"stmt16": true,
"stmt17": true,
"stmt18": true,
"stmt4": true
"stmt18": true
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
{
"explain_todo": {
"stmt10": true,
"stmt11": true,
"stmt12": true,
"stmt14": true,
"stmt3": true,
"stmt5": true,
"stmt6": true,
"stmt7": true,
"stmt8": true,
"stmt9": true
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"stmt77": true,
"stmt80": true,
"stmt84": true,
"stmt86": true,
"stmt87": true,
"stmt9": true,
"stmt91": true,
Expand Down
Loading