From 068ab52b0d6331f3ed23abdeaa35ed056b4a180b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 25 Dec 2025 04:19:07 +0000 Subject: [PATCH] Fix EXPLAIN AST output for single tuple in IN expression When an IN expression contains a single tuple literal, ClickHouse wraps it in a Function tuple node. This matches ClickHouse's behavior for queries like `SELECT (1, '') IN ((1, ''));`. --- internal/explain/functions.go | 25 ++++++++++++++++++- .../testdata/00131_set_hashed/metadata.json | 2 +- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/internal/explain/functions.go b/internal/explain/functions.go index 71a9e391b..9e5f83d87 100644 --- a/internal/explain/functions.go +++ b/internal/explain/functions.go @@ -184,7 +184,17 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) if n.Query != nil { argCount++ } else { - argCount += len(n.List) + // Check if we have a single tuple literal that should be wrapped in Function tuple + if len(n.List) == 1 { + if lit, ok := n.List[0].(*ast.Literal); ok && lit.Type == ast.LiteralTuple { + // Single tuple literal gets wrapped in Function tuple, so count as 1 + argCount++ + } else { + argCount += len(n.List) + } + } else { + argCount += len(n.List) + } } fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, argCount) Node(sb, n.Expr, depth+2) @@ -192,6 +202,19 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) // Subqueries in IN should be wrapped in Subquery node fmt.Fprintf(sb, "%s Subquery (children %d)\n", indent, 1) Node(sb, n.Query, depth+3) + } else if len(n.List) == 1 { + // Single element in the list + // If it's a tuple literal, wrap it in Function tuple + // Otherwise, output the element directly + if lit, ok := n.List[0].(*ast.Literal); ok && lit.Type == ast.LiteralTuple { + // Wrap tuple literal in Function tuple + fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1) + fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, 1) + Node(sb, n.List[0], depth+4) + } else { + // Single non-tuple element - output directly + Node(sb, n.List[0], depth+2) + } } else { for _, item := range n.List { Node(sb, item, depth+2) diff --git a/parser/testdata/00131_set_hashed/metadata.json b/parser/testdata/00131_set_hashed/metadata.json index ef120d978..0967ef424 100644 --- a/parser/testdata/00131_set_hashed/metadata.json +++ b/parser/testdata/00131_set_hashed/metadata.json @@ -1 +1 @@ -{"todo": true} +{}