diff --git a/internal/explain/expressions.go b/internal/explain/expressions.go index 13bf0587e3..dfa57f35ce 100644 --- a/internal/explain/expressions.go +++ b/internal/explain/expressions.go @@ -47,6 +47,15 @@ func explainLiteral(sb *strings.Builder, n *ast.Literal, indent string, depth in fmt.Fprintf(sb, "%s ExpressionList\n", indent) return } + // Single-element tuples (from trailing comma syntax like (1,)) always render as Function tuple + if len(exprs) == 1 { + fmt.Fprintf(sb, "%sFunction tuple (children %d)\n", indent, 1) + fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(exprs)) + for _, e := range exprs { + Node(sb, e, depth+2) + } + return + } hasComplexExpr := false for _, e := range exprs { // Simple literals (numbers, strings, etc.) are OK @@ -454,11 +463,16 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) { explainCastExpr(sb, e, indent, depth) } case *ast.ArrayAccess: - // Array access - ClickHouse doesn't show aliases on arrayElement in EXPLAIN AST - explainArrayAccess(sb, e, indent, depth) + // Array access - show alias only when array is not a literal + // ClickHouse hides alias when array access is on a literal + if _, isLit := e.Array.(*ast.Literal); isLit { + explainArrayAccess(sb, e, indent, depth) + } else { + explainArrayAccessWithAlias(sb, e, n.Alias, indent, depth) + } case *ast.TupleAccess: - // Tuple access - ClickHouse doesn't show aliases on tupleElement in EXPLAIN AST - explainTupleAccess(sb, e, indent, depth) + // Tuple access with alias + explainTupleAccessWithAlias(sb, e, n.Alias, indent, depth) case *ast.InExpr: // IN expressions with alias explainInExprWithAlias(sb, e, n.Alias, indent, depth) diff --git a/internal/explain/functions.go b/internal/explain/functions.go index cd5aa63641..5b47208337 100644 --- a/internal/explain/functions.go +++ b/internal/explain/functions.go @@ -395,19 +395,20 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) argCount += len(n.List) } } else { - // Check if all items are tuples - allTuples := true + // Check if all items are string literals (large list case - no wrapper) + allStringLiterals := true for _, item := range n.List { - if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralTuple { - allTuples = false + if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralString { + allStringLiterals = false break } } - if allTuples { - // All tuples get wrapped in a single Function tuple - argCount++ - } else { + if allStringLiterals { + // Large string list - separate children argCount += len(n.List) + } else { + // Non-string items get wrapped in a single Function tuple + argCount++ } } } @@ -455,8 +456,26 @@ func explainInExpr(sb *strings.Builder, n *ast.InExpr, indent string, depth int) explainTupleInInList(sb, item.(*ast.Literal), indent+" ", depth+4) } } else { + // Check if all items are string literals (large list case) + allStringLiterals := true for _, item := range n.List { - Node(sb, item, depth+2) + if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralString { + allStringLiterals = false + break + } + } + if allStringLiterals { + // Large string list - output as separate children (no tuple wrapper) + for _, item := range n.List { + Node(sb, item, depth+2) + } + } else { + // Wrap non-literal/non-tuple list items in Function tuple + fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1) + fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.List)) + for _, item := range n.List { + Node(sb, item, depth+4) + } } } } @@ -548,18 +567,20 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in argCount += len(n.List) } } else { - // Check if all items are tuples - allTuples := true + // Check if all items are string literals (large list case - no wrapper) + allStringLiterals := true for _, item := range n.List { - if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralTuple { - allTuples = false + if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralString { + allStringLiterals = false break } } - if allTuples { - argCount++ - } else { + if allStringLiterals { + // Large string list - separate children argCount += len(n.List) + } else { + // Non-string items get wrapped in a single Function tuple + argCount++ } } } @@ -600,8 +621,26 @@ func explainInExprWithAlias(sb *strings.Builder, n *ast.InExpr, alias string, in explainTupleInInList(sb, item.(*ast.Literal), indent+" ", depth+4) } } else { + // Check if all items are string literals (large list case) + allStringLiterals := true for _, item := range n.List { - Node(sb, item, depth+2) + if lit, ok := item.(*ast.Literal); !ok || lit.Type != ast.LiteralString { + allStringLiterals = false + break + } + } + if allStringLiterals { + // Large string list - output as separate children (no tuple wrapper) + for _, item := range n.List { + Node(sb, item, depth+2) + } + } else { + // Wrap non-literal/non-tuple list items in Function tuple + fmt.Fprintf(sb, "%s Function tuple (children %d)\n", indent, 1) + fmt.Fprintf(sb, "%s ExpressionList (children %d)\n", indent, len(n.List)) + for _, item := range n.List { + Node(sb, item, depth+4) + } } } } diff --git a/parser/expression.go b/parser/expression.go index 3ce0af0fbb..3e202a453a 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -895,6 +895,10 @@ func (p *Parser) parseGroupedOrTuple() ast.Expression { elements := []ast.Expression{first} for p.currentIs(token.COMMA) { p.nextToken() + // Handle trailing comma: (1,) should create tuple with single element + if p.currentIs(token.RPAREN) { + break + } elements = append(elements, p.parseExpression(LOWEST)) } p.expect(token.RPAREN) diff --git a/parser/testdata/00017_aggregation_uninitialized_memory/metadata.json b/parser/testdata/00017_aggregation_uninitialized_memory/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/00017_aggregation_uninitialized_memory/metadata.json +++ b/parser/testdata/00017_aggregation_uninitialized_memory/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/00172_constexprs_in_set/metadata.json b/parser/testdata/00172_constexprs_in_set/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/00172_constexprs_in_set/metadata.json +++ b/parser/testdata/00172_constexprs_in_set/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/00604_show_create_database/metadata.json b/parser/testdata/00604_show_create_database/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/00604_show_create_database/metadata.json +++ b/parser/testdata/00604_show_create_database/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/00606_quantiles_and_nans/metadata.json b/parser/testdata/00606_quantiles_and_nans/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/00606_quantiles_and_nans/metadata.json +++ b/parser/testdata/00606_quantiles_and_nans/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/00840_top_k_weighted/metadata.json b/parser/testdata/00840_top_k_weighted/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/00840_top_k_weighted/metadata.json +++ b/parser/testdata/00840_top_k_weighted/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/01021_tuple_parser/metadata.json b/parser/testdata/01021_tuple_parser/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/01021_tuple_parser/metadata.json +++ b/parser/testdata/01021_tuple_parser/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/01277_large_tuples/metadata.json b/parser/testdata/01277_large_tuples/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/01277_large_tuples/metadata.json +++ b/parser/testdata/01277_large_tuples/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/01407_lambda_arrayJoin/metadata.json b/parser/testdata/01407_lambda_arrayJoin/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/01407_lambda_arrayJoin/metadata.json +++ b/parser/testdata/01407_lambda_arrayJoin/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/01813_quantileBfloat16_nans/metadata.json b/parser/testdata/01813_quantileBfloat16_nans/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/01813_quantileBfloat16_nans/metadata.json +++ b/parser/testdata/01813_quantileBfloat16_nans/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/02163_operators/metadata.json b/parser/testdata/02163_operators/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/02163_operators/metadata.json +++ b/parser/testdata/02163_operators/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/02265_limit_push_down_over_window_functions_bug/metadata.json b/parser/testdata/02265_limit_push_down_over_window_functions_bug/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/02265_limit_push_down_over_window_functions_bug/metadata.json +++ b/parser/testdata/02265_limit_push_down_over_window_functions_bug/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/02336_sort_optimization_with_fill/metadata.json b/parser/testdata/02336_sort_optimization_with_fill/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/02336_sort_optimization_with_fill/metadata.json +++ b/parser/testdata/02336_sort_optimization_with_fill/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/02513_date_string_comparison/metadata.json b/parser/testdata/02513_date_string_comparison/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/02513_date_string_comparison/metadata.json +++ b/parser/testdata/02513_date_string_comparison/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/02810_row_binary_with_defaults/metadata.json b/parser/testdata/02810_row_binary_with_defaults/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/02810_row_binary_with_defaults/metadata.json +++ b/parser/testdata/02810_row_binary_with_defaults/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true} diff --git a/parser/testdata/02922_respect_nulls_Nullable/metadata.json b/parser/testdata/02922_respect_nulls_Nullable/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/02922_respect_nulls_Nullable/metadata.json +++ b/parser/testdata/02922_respect_nulls_Nullable/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/03203_system_numbers_limit_and_offset_complex/metadata.json b/parser/testdata/03203_system_numbers_limit_and_offset_complex/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/03203_system_numbers_limit_and_offset_complex/metadata.json +++ b/parser/testdata/03203_system_numbers_limit_and_offset_complex/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/03221_refreshable_matview_progress/metadata.json b/parser/testdata/03221_refreshable_matview_progress/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/03221_refreshable_matview_progress/metadata.json +++ b/parser/testdata/03221_refreshable_matview_progress/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/03258_old_analyzer_const_expr_bug/metadata.json b/parser/testdata/03258_old_analyzer_const_expr_bug/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/03258_old_analyzer_const_expr_bug/metadata.json +++ b/parser/testdata/03258_old_analyzer_const_expr_bug/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/03630_join_blocks_with_different_constness/metadata.json b/parser/testdata/03630_join_blocks_with_different_constness/metadata.json index 40f6fb6cc5..7b727e7605 100644 --- a/parser/testdata/03630_join_blocks_with_different_constness/metadata.json +++ b/parser/testdata/03630_join_blocks_with_different_constness/metadata.json @@ -1 +1 @@ -{"explain":false,"todo":true,"todo_format":true} +{"todo_format":true,"explain":false} diff --git a/parser/testdata/03713_data_types_binary_deserialization_stack_overflow/metadata.json b/parser/testdata/03713_data_types_binary_deserialization_stack_overflow/metadata.json index b78075a468..55f5cc6775 100644 --- a/parser/testdata/03713_data_types_binary_deserialization_stack_overflow/metadata.json +++ b/parser/testdata/03713_data_types_binary_deserialization_stack_overflow/metadata.json @@ -1 +1 @@ -{"todo":true,"todo_format":true} +{"todo_format":true}