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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 5 additions & 4 deletions ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,10 +1037,11 @@ const (

// BinaryExpr represents a binary expression.
type BinaryExpr struct {
Position token.Position `json:"-"`
Left Expression `json:"left"`
Op string `json:"op"`
Right Expression `json:"right"`
Position token.Position `json:"-"`
Left Expression `json:"left"`
Op string `json:"op"`
Right Expression `json:"right"`
Parenthesized bool `json:"parenthesized,omitempty"` // True if wrapped in explicit parentheses
}

func (b *BinaryExpr) Pos() token.Position { return b.Position }
Expand Down
52 changes: 52 additions & 0 deletions internal/explain/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,18 @@ func explainBinaryExpr(sb *strings.Builder, n *ast.BinaryExpr, indent string, de
return
}

// For OR and AND operators, flatten left-associative chains
// but preserve explicit parenthesization like "(a OR b) OR c"
if n.Op == "OR" || n.Op == "AND" {
operands := collectLogicalOperands(n)
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
for _, op := range operands {
Node(sb, op, depth+2)
}
return
}

fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
Node(sb, n.Left, depth+2)
Expand Down Expand Up @@ -291,6 +303,26 @@ func collectConcatOperands(n *ast.BinaryExpr) []ast.Expression {
return operands
}

// collectLogicalOperands flattens chained OR/AND operations into a list of operands,
// but respects explicit parenthesization. For example:
// - "a OR b OR c" → [a, b, c] (flattened)
// - "(a OR b) OR c" → [(a OR b), c] (preserved due to explicit parens)
func collectLogicalOperands(n *ast.BinaryExpr) []ast.Expression {
var operands []ast.Expression

// Recursively collect from left side if it's the same operator AND not parenthesized
if left, ok := n.Left.(*ast.BinaryExpr); ok && left.Op == n.Op && !left.Parenthesized {
operands = append(operands, collectLogicalOperands(left)...)
} else {
operands = append(operands, n.Left)
}

// Don't flatten right side - explicit parentheses would be on the left in left-associative parsing
operands = append(operands, n.Right)

return operands
}

func explainUnaryExpr(sb *strings.Builder, n *ast.UnaryExpr, indent string, depth int) {
// Handle negate of literal numbers - output as negative literal instead of function
if n.Op == "-" {
Expand Down Expand Up @@ -410,6 +442,14 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
for _, op := range operands {
Node(sb, op, depth+2)
}
} else if e.Op == "OR" || e.Op == "AND" {
// For OR and AND operators, flatten but respect explicit parenthesization
operands := collectLogicalOperands(e)
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, escapeAlias(n.Alias), 1)
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 2)
Expand Down Expand Up @@ -582,6 +622,18 @@ func explainWithElement(sb *strings.Builder, n *ast.WithElement, indent string,
for _, op := range operands {
Node(sb, op, depth+2)
}
} else if e.Op == "OR" || e.Op == "AND" {
// For OR and AND operators, flatten but respect explicit parenthesization
operands := collectLogicalOperands(e)
if n.Name != "" {
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Name, 1)
} else {
fmt.Fprintf(sb, "%sFunction %s (children %d)\n", indent, fnName, 1)
}
fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(operands))
for _, op := range operands {
Node(sb, op, depth+2)
}
} else {
if n.Name != "" {
fmt.Fprintf(sb, "%sFunction %s (alias %s) (children %d)\n", indent, fnName, n.Name, 1)
Expand Down
7 changes: 7 additions & 0 deletions parser/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,13 @@ func (p *Parser) parseGroupedOrTuple() ast.Expression {
}

p.expect(token.RPAREN)

// Mark binary expressions as parenthesized so we can preserve explicit
// grouping in EXPLAIN output (e.g., "(a OR b) OR c" vs "a OR b OR c")
if binExpr, ok := first.(*ast.BinaryExpr); ok {
binExpr.Parenthesized = true
}

return first
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt11":true,"stmt12":true,"stmt5":true,"stmt6":true,"stmt7":true}}
{}
6 changes: 1 addition & 5 deletions parser/testdata/00093_prewhere_array_join/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt2": true
}
}
{}
8 changes: 1 addition & 7 deletions parser/testdata/00110_external_sort/metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{
"explain_todo": {
"stmt6": true,
"stmt7": true,
"stmt8": true
}
}
{}
2 changes: 1 addition & 1 deletion parser/testdata/00167_read_bytes_from_fs/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt4":true}}
{}
13 changes: 1 addition & 12 deletions parser/testdata/00170_s3_cache/metadata.json
Original file line number Diff line number Diff line change
@@ -1,12 +1 @@
{
"explain_todo": {
"stmt28": true,
"stmt42": true,
"stmt43": true,
"stmt44": true,
"stmt45": true,
"stmt46": true,
"stmt47": true,
"stmt48": true
}
}
{}
2 changes: 1 addition & 1 deletion parser/testdata/00172_early_constant_folding/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt3":true,"stmt4":true}}
{}
7 changes: 1 addition & 6 deletions parser/testdata/00229_prewhere_column_missing/metadata.json
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
{
"explain_todo": {
"stmt20": true,
"stmt21": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt3":true}}
{}
8 changes: 1 addition & 7 deletions parser/testdata/00239_type_conversion_in_in/metadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{
"explain_todo": {
"stmt1": true,
"stmt2": true,
"stmt3": true
}
}
{}
2 changes: 1 addition & 1 deletion parser/testdata/00318_pk_tuple_order/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt17":true,"stmt18":true,"stmt20":true,"stmt21":true,"stmt22":true,"stmt23":true,"stmt25":true,"stmt27":true,"stmt29":true,"stmt31":true,"stmt32":true,"stmt33":true,"stmt40":true}}
{}
2 changes: 1 addition & 1 deletion parser/testdata/00321_pk_set/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt10":true,"stmt11":true,"stmt6":true,"stmt7":true,"stmt8":true,"stmt9":true}}
{}
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
{
"explain_todo": {
"stmt10": true,
"stmt11": true,
"stmt12": true,
"stmt21": true,
"stmt24": true,
"stmt27": true,
"stmt31": true,
"stmt34": true,
"stmt37": true,
"stmt38": true,
"stmt42": true,
"stmt43": true,
"stmt44": true,
"stmt46": true,
"stmt47": true,
"stmt48": true,
"stmt6": true,
"stmt8": true
}
}
9 changes: 0 additions & 9 deletions parser/testdata/00502_custom_partitioning_local/metadata.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
{
"explain_todo": {
"stmt10": true,
"stmt13": true,
"stmt17": true,
"stmt25": true,
"stmt26": true,
"stmt28": true,
"stmt41": true,
"stmt42": true,
"stmt43": true,
"stmt45": true,
"stmt48": true,
"stmt58": true,
"stmt59": true,
"stmt61": true,
"stmt7": true,
"stmt73": true,
"stmt8": true
}
}
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
{
"explain_todo": {
"stmt11": true,
"stmt12": true,
"stmt13": true,
"stmt14": true,
"stmt16": true,
"stmt19": true,
"stmt32": true,
"stmt33": true,
"stmt34": true,
"stmt35": true,
"stmt37": true,
"stmt53": true,
"stmt54": true,
"stmt55": true,
"stmt56": true,
"stmt57": true,
"stmt59": true,
"stmt62": true,
"stmt75": true,
"stmt76": true,
"stmt77": true,
"stmt78": true,
"stmt80": true,
"stmt94": true,
"stmt95": true,
"stmt96": true,
"stmt98": true
"stmt96": true
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
{
"explain_todo": {
"stmt28": true,
"stmt31": true,
"stmt42": true,
"stmt44": true,
"stmt46": true,
Expand Down
2 changes: 1 addition & 1 deletion parser/testdata/00633_func_or_in/metadata.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt4":true}}
{}
15 changes: 1 addition & 14 deletions parser/testdata/00700_decimal_compare/metadata.json
Original file line number Diff line number Diff line change
@@ -1,14 +1 @@
{
"explain_todo": {
"stmt13": true,
"stmt14": true,
"stmt15": true,
"stmt16": true,
"stmt17": true,
"stmt18": true,
"stmt19": true,
"stmt20": true,
"stmt21": true,
"stmt22": 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 +1 @@
{"explain_todo":{"stmt5":true}}
{}
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{"explain_todo":{"stmt12":true,"stmt15":true}}
{
"explain_todo": {
"stmt12": true
}
}
6 changes: 1 addition & 5 deletions parser/testdata/00735_or_expr_optimize_bug/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt4": true
}
}
{}
20 changes: 1 addition & 19 deletions parser/testdata/00736_disjunction_optimisation/metadata.json
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
{
"explain_todo": {
"stmt10": true,
"stmt11": true,
"stmt12": true,
"stmt13": true,
"stmt14": true,
"stmt15": true,
"stmt16": true,
"stmt17": true,
"stmt20": true,
"stmt21": true,
"stmt22": true,
"stmt23": true,
"stmt24": true,
"stmt25": true,
"stmt26": true,
"stmt27": true,
"stmt28": true,
"stmt29": true,
"stmt30": true,
"stmt31": true,
"stmt32": true,
"stmt33": true,
"stmt34": true,
"stmt6": true,
"stmt7": true,
"stmt8": true,
"stmt9": true
"stmt8": true
}
}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"explain_todo":{"stmt10":true,"stmt6":true}}
{}
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{
"explain_todo": {
"stmt14": true,
"stmt22": true,
"stmt6": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
{
"explain_todo": {
"stmt12": true,
"stmt13": true,
"stmt20": true,
"stmt21": true,
"stmt27": true,
"stmt41": true,
"stmt43": true,
"stmt8": true
"stmt41": true
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
{
"explain_todo": {
"stmt12": true,
"stmt20": true,
"stmt24": true,
"stmt36": true
}
}
{}
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
{
"explain_todo": {
"stmt5": true,
"stmt6": true,
"stmt7": true,
"stmt9": true
"stmt6": true
}
}
6 changes: 1 addition & 5 deletions parser/testdata/00824_filesystem/metadata.json
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
{
"explain_todo": {
"stmt1": true
}
}
{}
9 changes: 1 addition & 8 deletions parser/testdata/00826_cross_to_inner_join/metadata.json
Original file line number Diff line number Diff line change
@@ -1,8 +1 @@
{
"explain_todo": {
"stmt27": true,
"stmt29": true,
"stmt57": true,
"stmt59": true
}
}
{}
Loading