Skip to content

Commit b355de2

Browse files
kyleconroyclaude
andauthored
Fix failing tests in continuous loop (#112)
* Fix nested empty array handling in aliased expressions Add checks for empty nested arrays in explainAliasedExpr to properly render arrays containing empty subarrays as Function array instead of Literal. This matches the behavior in explainLiteral. The fix adds: - Check for nested arrays that are empty or contain empty arrays - containsEmptyArraysRecursive check for deeply nested empty arrays - containsTuplesRecursive check for deeply nested tuples This resolves 10+ failing explain tests across multiple test suites: - 00909_arrayEnumerateUniq (10 statements) - 00548_slice_of_nested - 02699_polygons_sym_difference_rollup - 02699_polygons_sym_difference_total_analyzer * Fix binary string literal parsing (b'...') Add readBinaryString() function to properly decode binary string literals. Binary strings like b'0' should be decoded as byte values where each bit (0 or 1) contributes to the resulting bytes. For example: - b'0' -> \0 (single bit 0, left-padded to 8 bits = 0x00) - b'00110000' -> '0' (8 bits = ASCII 0x30) - b'111001101011010110001011111010001010111110010101' -> '测试' This fixes 9 failing explain tests in 02494_parser_string_binary_literal. * Fix IS NOT DISTINCT FROM operator and boolean tuple handling Two fixes in this commit: 1. Fix IS NOT DISTINCT FROM operator parsing: - Use <=> operator which maps to isNotDistinctFrom function - Use NOT_PREC for right side to correctly include lower-precedence operators like IN - IS DISTINCT FROM wraps in NOT(IS NOT DISTINCT FROM) 2. Add boolean support for IN list tuple literals: - Boolean literals in IN lists can now be combined into Literal Tuple_ format - Added allBooleansOrNull check to both explainInExpr functions This fixes 9 statements in 02868_operator_is_not_distinct_from_priority and 1 statement in 03214_join_on_tuple_comparison_elimination_bug. * Handle non-literal expressions in aliased array formatting Add checks for CAST expressions, binary expressions, and other non-literal expressions when determining if an array should be rendered as Function array vs Literal Array_. This ensures arrays containing CAST, function calls, etc. are properly rendered while preserving correct behavior for: - Simple arrays with primitive literals - Arrays with negated numbers - Nested arrays (handled separately) This fixes 9 statements in 00502_sum_map and many other tests: - 02916_analyzer_set_in_join (1 statement) - 02708_dotProduct (2 statements) - 02423_json_quote_float64 (2 statements) - 02524_fuzz_and_fuss (1 statement) - 00597_push_down_predicate_long (2 statements) - 03727_concat_with_separator_subquery (3 statements) * Handle STRICT modifier for EXCEPT and REPLACE column transformers Add handling for the STRICT modifier in asterisk and columns matcher EXCEPT and REPLACE parsing functions. This ensures that queries like `* EXCEPT STRICT i` and `* REPLACE STRICT i + 1 AS i` parse correctly. This fixes 7 statements in 01470_columns_transformers: - stmt12-15 (EXCEPT STRICT) - stmt16-17, stmt22 (REPLACE STRICT) * Add support for RENAME DICTIONARY statements The parser now handles RENAME DICTIONARY syntax in addition to RENAME TABLE. This allows dictionary rename operations to be properly parsed and explained. This fixes: - 3 statements in 01155_rename_move_materialized_view - 6 statements in 01191_rename_dictionary - 3 statements in 02343_analyzer_column_transformers_strict (from STRICT fix) * Add DISTINCT ON (col1, col2, ...) syntax support Added parsing and EXPLAIN AST output for PostgreSQL-style DISTINCT ON clause which specifies columns to determine row uniqueness. Changes: - Added DistinctOn field to SelectQuery AST node - Added DISTINCT ON parsing in parseSelect() and parseFromSelectSyntax() - Added DISTINCT ON output in explainSelectQuery (outputs Literal UInt64_1 followed by ExpressionList of columns) Fixed tests: - 03363_hive_style_partition (3 statements) - 01244_optimize_distributed_group_by_sharding_key (1 statement) - 01952_optimize_distributed_group_by_sharding_key (8 statements) * Add support for tagged dollar-quoted strings ($tag$...$tag$) Extended the lexer to properly handle PostgreSQL-style dollar-quoted strings with custom tags like $doc$content$doc$. The lexer now: 1. Looks ahead to verify a matching closing tag exists before treating something as a dollar-quoted string 2. Falls back to identifier parsing for cases like $alias$name$ which are valid ClickHouse identifiers containing $ characters 3. Added peekCharN function to peek multiple characters ahead This fixes all 8 pending statements in test 01948_heredoc. * Add PARTITION ID syntax support for ALTER commands Added parsing and EXPLAIN AST output for PARTITION ID 'value' syntax in ALTER TABLE commands (ATTACH, DETACH, DROP, REPLACE, FETCH, FREEZE). Changes: - Added PartitionIsID field to AlterCommand AST node - Parse PARTITION ID 'value' in parseAlterCommand for multiple partition operations - Output Partition_ID format in explainAlterCommand when PartitionIsID is true Fixed 8 statements in test 01166_truncate_multiple_partitions and many other tests that use PARTITION ID syntax. * Add WITH RECURSIVE support for CTEs Skip the RECURSIVE keyword after WITH when parsing CTEs. The keyword is handled by silently consuming it since the recursive behavior is transparent at the AST/parsing level. Fixed 8 statements in 03033_recursive_cte_basic and many other tests using recursive CTEs. * Add REPLACE/EXCHANGE DICTIONARY and fix RENAME/dict source parsing Multiple dictionary-related fixes: 1. Added REPLACE DICTIONARY support (equivalent to CREATE OR REPLACE) 2. Added EXCHANGE DICTIONARIES support (similar to EXCHANGE TABLES) 3. Fixed key-value pair parsing for dictionary SOURCE clause - Values like TABLE test are now properly parsed 4. Fixed RENAME output to not include database names when not specified Fixed 8 statements in 03173_check_cyclic_dependencies_on_create_and_rename and many other dictionary-related tests. * Add support for MATERIALIZE TTL ALTER command - Add AlterMaterializeTTL constant to ast/ast.go - Add MATERIALIZE TTL parsing after MATERIALIZE keyword in parser.go - Fix explainAlterCommand to omit (children N) when count is 0 This fixes 01070_materialize_ttl and several other tests that use MATERIALIZE TTL. * Support dotted column names in INSERT column lists Handle column names like ip4Map.value for nested columns in INSERT statements. This allows parsing INSERT INTO table(id, column.subcolumn, ...) VALUES (...) without requiring backticks around the dotted names. Fixes many tests using nested column syntax. * Fix ALTER query FORMAT and SETTINGS clause ordering - Parse FORMAT before SETTINGS in ALTER TABLE statements - Output FORMAT identifier before Set in explain output This matches ClickHouse's FORMAT Null SETTINGS ... syntax. * Support trailing commas in expression lists ClickHouse allows trailing commas before clauses like FROM, WHERE, etc. For example: SELECT a, b, FROM table Add isClauseKeyword function to detect when a token is a clause keyword that should terminate an expression list, vs when it's being used as an identifier (which ClickHouse allows for many keywords). The detection is context-aware - keywords followed by (, [, or = are treated as expression continuations rather than clause terminators. * Include type in QueryParameter EXPLAIN output Format QueryParameter with type as Name:Type to match ClickHouse output. For example: QueryParameter filter:FixedString(2) Fixes many parameterized view tests. * Add PARALLEL WITH statement chaining and fix EXISTS TABLE explain format - Add PARALLEL token to lexer keywords - Add ParallelWithQuery AST node for chaining statements with PARALLEL WITH - Add parseParallelWith in parser to handle statement chaining - Fix table expression alias handling to not consume PARALLEL when followed by WITH - Fix ExistsTableQuery explain output to match ClickHouse format (space alignment for missing database) Tests fixed: - 03305_parallel_with (all statements) - 03604_parallel_with_query_lock (all statements) - 01048_exists_query (all statements) - 00101_materialized_views_and_insert_without_explicit_database (exists-related) - 01073_attach_if_not_exists * Fix Identifier handling in FormatDataType and escape strings in CAST explain - Add Identifier handling in FormatDataType to properly format AggregateFunction types that contain function name identifiers - Escape string literals in CAST explain output to properly handle null bytes and other control characters Tests fixed: - 02688_aggregate_states (all statements) - 02477_single_value_data_string_regression - 02689_meaningless_data_types - 02731_nothing_deserialization - 02885_arg_min_max_combinator - 03011_definitive_guide_to_cast - 03210_variant_with_aggregate_function_type - 03254_normalize_aggregate_states_with_named_tuple_args - 03411_iceberg_bucket * Add STALENESS support for WITH FILL and fix OrderByElement/InterpolateElement explain - Add FillStaleness field to OrderByElement AST - Parse STALENESS clause in ORDER BY WITH FILL - Simplify explainOrderByElement to always use direct children (no FillModifier) - Fix explainInterpolateElement to correctly output value OR column identifier Tests fixed: - 03266_with_fill_staleness (all statements) - 03266_with_fill_staleness_cases - 03266_with_fill_staleness_errors - 00995_order_by_with_fill - 02016_order_by_with_fill_monotonic_functions_removal - 02112_with_fill_interval - 02366_with_fill_date - 02560_with_fill_int256_int - 02561_with_fill_date_datetime_incompatible - 02861_interpolate_alias_precedence - 03043_group_array_result_is_expected - 03093_with_fill_support_constant_expression * Add MODIFY ORDER BY support in ALTER statements - Add AlterModifyOrderBy command type to AST - Add OrderByExpr field to AlterCommand struct - Parse MODIFY ORDER BY (expr, ...) syntax in ALTER - Explain output wraps multiple expressions in tuple function Tests fixed: - 00754_alter_modify_order_by (all statements) - 00754_alter_modify_order_by_replicated_zookeeper_long - 00910_crash_when_distributed_modify_order_by - 01526_alter_add_and_modify_order_zookeeper - 01532_primary_key_without_order_by_zookeeper - 02484_substitute_udf_storage_args - 02710_allow_suspicious_indices - 02863_interpolate_subquery - 03020_order_by_SimpleAggregateFunction - 03263_forbid_materialize_sort_key - 03578_ttl_column_in_order_by_validation * Handle +Inf and -Inf as infinity literals in parser Previously +Inf was being parsed as a unary plus function applied to the Inf identifier, causing array literals containing +Inf to be treated as function calls instead of literal arrays. Now +Inf and -Inf are recognized as special Float64 infinity literals. * Add ATTACH TABLE schema parsing and fix multi-column PRIMARY KEY - Extended AttachQuery AST to support columns, engine, order by, and primary key clauses - Added parsing for ATTACH TABLE with column definitions and ENGINE clause similar to CREATE TABLE - Fixed PRIMARY KEY with multiple columns in CREATE TABLE to wrap in Function tuple - Updated explain output for ATTACH TABLE to include columns and storage definitions * Add MOVE PARTITION and fix OPTIMIZE TABLE database name - Added MOVE PARTITION ... TO TABLE parsing in ALTER statements - Added ToDatabase and ToTable fields to AlterCommand for destination - Fixed OPTIMIZE TABLE to output database identifier for qualified names * Fix nested array literal format for arrays with negative numbers Updated containsNonLiteralExpressions to accept unary minus of literals (negative numbers) as literal-like expressions. This allows nested arrays containing negative numbers to be formatted as Literal Array_[Array_[...], ...] instead of Function array. * Add CONSTRAINT ASSUME support in CREATE TABLE parsing Previously only CONSTRAINT ... CHECK was supported. Now also supports CONSTRAINT ... ASSUME which is used for query optimization hints. * Fix SYSTEM distributed commands to output table name twice SYSTEM STOP/START DISTRIBUTED SENDS and SYSTEM FLUSH DISTRIBUTED commands now output the table name as both database and table in EXPLAIN output, matching ClickHouse behavior. * Simplify SQL standard TRIM with empty string to just the literal When using SQL standard syntax like trim(LEADING '' FROM 'foo'), ClickHouse simplifies this to just the literal 'foo' in EXPLAIN output. Added SQLStandard field to FunctionCall to distinguish SQL standard TRIM syntax from direct trimLeft/trimRight/trimBoth function calls. * Fix CAST expression handling for arrays containing CastExpr elements Add containsCastExpressions function to check if array/tuple literals contain CastExpr elements. This allows proper formatting of arrays like [1::UInt32, 2::UInt32] as Function array nodes while keeping arrays with just negative numbers (like [-1, -2, -3]) formatted as string literals. Fixes 01852_cast_operator_2 (6 statements) and related tests. * Add ON CLUSTER support and duplicate output for SYSTEM distributed commands - Add OnCluster and DuplicateTableOutput fields to SystemQuery AST - Parse ON CLUSTER clause for SYSTEM commands (FLUSH DISTRIBUTED, etc.) - For qualified table names (database.table), output identifiers twice in EXPLAIN to match ClickHouse's expected format Fixes 01294_system_distributed_on_cluster (6 statements). --------- Co-authored-by: Claude <[email protected]>
1 parent 797b449 commit b355de2

File tree

225 files changed

+1265
-1439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

225 files changed

+1265
-1439
lines changed

ast/ast.go

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ type SelectQuery struct {
5757
Position token.Position `json:"-"`
5858
With []Expression `json:"with,omitempty"`
5959
Distinct bool `json:"distinct,omitempty"`
60+
DistinctOn []Expression `json:"distinct_on,omitempty"` // DISTINCT ON (col1, col2, ...) syntax
6061
Top Expression `json:"top,omitempty"`
6162
Columns []Expression `json:"columns"`
6263
From *TablesInSelectQuery `json:"from,omitempty"`
@@ -199,15 +200,16 @@ const (
199200

200201
// OrderByElement represents an ORDER BY element.
201202
type OrderByElement struct {
202-
Position token.Position `json:"-"`
203-
Expression Expression `json:"expression"`
204-
Descending bool `json:"descending,omitempty"`
205-
NullsFirst *bool `json:"nulls_first,omitempty"`
206-
Collate string `json:"collate,omitempty"`
207-
WithFill bool `json:"with_fill,omitempty"`
208-
FillFrom Expression `json:"fill_from,omitempty"`
209-
FillTo Expression `json:"fill_to,omitempty"`
210-
FillStep Expression `json:"fill_step,omitempty"`
203+
Position token.Position `json:"-"`
204+
Expression Expression `json:"expression"`
205+
Descending bool `json:"descending,omitempty"`
206+
NullsFirst *bool `json:"nulls_first,omitempty"`
207+
Collate string `json:"collate,omitempty"`
208+
WithFill bool `json:"with_fill,omitempty"`
209+
FillFrom Expression `json:"fill_from,omitempty"`
210+
FillTo Expression `json:"fill_to,omitempty"`
211+
FillStep Expression `json:"fill_step,omitempty"`
212+
FillStaleness Expression `json:"fill_staleness,omitempty"`
211213
}
212214

213215
func (o *OrderByElement) Pos() token.Position { return o.Position }
@@ -574,8 +576,11 @@ type AlterCommand struct {
574576
Constraint *Constraint `json:"constraint,omitempty"`
575577
ConstraintName string `json:"constraint_name,omitempty"`
576578
Partition Expression `json:"partition,omitempty"`
579+
PartitionIsID bool `json:"partition_is_id,omitempty"` // True when using PARTITION ID 'value' syntax
577580
FromTable string `json:"from_table,omitempty"`
578-
FromPath string `json:"from_path,omitempty"` // For FETCH PARTITION FROM
581+
ToDatabase string `json:"to_database,omitempty"` // For MOVE PARTITION TO TABLE
582+
ToTable string `json:"to_table,omitempty"` // For MOVE PARTITION TO TABLE
583+
FromPath string `json:"from_path,omitempty"` // For FETCH PARTITION FROM
579584
TTL *TTLClause `json:"ttl,omitempty"`
580585
Settings []*SettingExpr `json:"settings,omitempty"`
581586
Where Expression `json:"where,omitempty"` // For DELETE WHERE
@@ -585,6 +590,7 @@ type AlterCommand struct {
585590
StatisticsColumns []string `json:"statistics_columns,omitempty"` // For ADD/DROP/CLEAR/MATERIALIZE STATISTICS
586591
StatisticsTypes []*FunctionCall `json:"statistics_types,omitempty"` // For ADD/MODIFY STATISTICS TYPE
587592
Comment string `json:"comment,omitempty"` // For COMMENT COLUMN
593+
OrderByExpr []Expression `json:"order_by_expr,omitempty"` // For MODIFY ORDER BY
588594
}
589595

590596
// Projection represents a projection definition.
@@ -635,6 +641,7 @@ const (
635641
AlterAddConstraint AlterCommandType = "ADD_CONSTRAINT"
636642
AlterDropConstraint AlterCommandType = "DROP_CONSTRAINT"
637643
AlterModifyTTL AlterCommandType = "MODIFY_TTL"
644+
AlterMaterializeTTL AlterCommandType = "MATERIALIZE_TTL"
638645
AlterModifySetting AlterCommandType = "MODIFY_SETTING"
639646
AlterDropPartition AlterCommandType = "DROP_PARTITION"
640647
AlterDetachPartition AlterCommandType = "DETACH_PARTITION"
@@ -653,10 +660,11 @@ const (
653660
AlterClearProjection AlterCommandType = "CLEAR_PROJECTION"
654661
AlterAddStatistics AlterCommandType = "ADD_STATISTICS"
655662
AlterModifyStatistics AlterCommandType = "MODIFY_STATISTICS"
656-
AlterDropStatistics AlterCommandType = "DROP_STATISTICS"
657-
AlterClearStatistics AlterCommandType = "CLEAR_STATISTICS"
663+
AlterDropStatistics AlterCommandType = "DROP_STATISTICS"
664+
AlterClearStatistics AlterCommandType = "CLEAR_STATISTICS"
658665
AlterMaterializeStatistics AlterCommandType = "MATERIALIZE_STATISTICS"
659666
AlterModifyComment AlterCommandType = "MODIFY_COMMENT"
667+
AlterModifyOrderBy AlterCommandType = "MODIFY_ORDER_BY"
660668
)
661669

662670
// TruncateQuery represents a TRUNCATE statement.
@@ -707,9 +715,14 @@ func (d *DetachQuery) statementNode() {}
707715

708716
// AttachQuery represents an ATTACH statement.
709717
type AttachQuery struct {
710-
Position token.Position `json:"-"`
711-
Database string `json:"database,omitempty"`
712-
Table string `json:"table,omitempty"`
718+
Position token.Position `json:"-"`
719+
Database string `json:"database,omitempty"`
720+
Table string `json:"table,omitempty"`
721+
Columns []*ColumnDeclaration `json:"columns,omitempty"`
722+
ColumnsPrimaryKey []Expression `json:"columns_primary_key,omitempty"` // PRIMARY KEY in column list
723+
Engine *EngineClause `json:"engine,omitempty"`
724+
OrderBy []Expression `json:"order_by,omitempty"`
725+
PrimaryKey []Expression `json:"primary_key,omitempty"`
713726
}
714727

715728
func (a *AttachQuery) Pos() token.Position { return a.Position }
@@ -840,10 +853,12 @@ func (c *CheckQuery) statementNode() {}
840853

841854
// SystemQuery represents a SYSTEM statement.
842855
type SystemQuery struct {
843-
Position token.Position `json:"-"`
844-
Command string `json:"command"`
845-
Database string `json:"database,omitempty"`
846-
Table string `json:"table,omitempty"`
856+
Position token.Position `json:"-"`
857+
Command string `json:"command"`
858+
Database string `json:"database,omitempty"`
859+
Table string `json:"table,omitempty"`
860+
OnCluster string `json:"on_cluster,omitempty"`
861+
DuplicateTableOutput bool `json:"duplicate_table_output,omitempty"` // True for commands that need database/table output twice
847862
}
848863

849864
func (s *SystemQuery) Pos() token.Position { return s.Position }
@@ -1290,14 +1305,15 @@ func (c *ColumnsMatcher) expressionNode() {}
12901305

12911306
// FunctionCall represents a function call.
12921307
type FunctionCall struct {
1293-
Position token.Position `json:"-"`
1294-
Name string `json:"name"`
1295-
Parameters []Expression `json:"parameters,omitempty"` // For parametric functions like quantile(0.9)(x)
1296-
Arguments []Expression `json:"arguments,omitempty"`
1297-
Settings []*SettingExpr `json:"settings,omitempty"` // For table functions with SETTINGS
1298-
Distinct bool `json:"distinct,omitempty"`
1299-
Over *WindowSpec `json:"over,omitempty"`
1300-
Alias string `json:"alias,omitempty"`
1308+
Position token.Position `json:"-"`
1309+
Name string `json:"name"`
1310+
Parameters []Expression `json:"parameters,omitempty"` // For parametric functions like quantile(0.9)(x)
1311+
Arguments []Expression `json:"arguments,omitempty"`
1312+
Settings []*SettingExpr `json:"settings,omitempty"` // For table functions with SETTINGS
1313+
Distinct bool `json:"distinct,omitempty"`
1314+
Over *WindowSpec `json:"over,omitempty"`
1315+
Alias string `json:"alias,omitempty"`
1316+
SQLStandard bool `json:"sql_standard,omitempty"` // True for SQL standard syntax like TRIM(... FROM ...)
13011317
}
13021318

13031319
func (f *FunctionCall) Pos() token.Position { return f.Position }
@@ -1594,3 +1610,13 @@ type ExistsExpr struct {
15941610
func (e *ExistsExpr) Pos() token.Position { return e.Position }
15951611
func (e *ExistsExpr) End() token.Position { return e.Position }
15961612
func (e *ExistsExpr) expressionNode() {}
1613+
1614+
// ParallelWithQuery represents multiple statements executed in parallel with PARALLEL WITH.
1615+
type ParallelWithQuery struct {
1616+
Position token.Position `json:"-"`
1617+
Statements []Statement `json:"statements"`
1618+
}
1619+
1620+
func (p *ParallelWithQuery) Pos() token.Position { return p.Position }
1621+
func (p *ParallelWithQuery) End() token.Position { return p.Position }
1622+
func (p *ParallelWithQuery) statementNode() {}

internal/explain/explain.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
231231
case *ast.DetachQuery:
232232
explainDetachQuery(sb, n, indent)
233233
case *ast.AttachQuery:
234-
explainAttachQuery(sb, n, indent)
234+
explainAttachQuery(sb, n, indent, depth)
235235
case *ast.AlterQuery:
236236
explainAlterQuery(sb, n, indent, depth)
237237
case *ast.OptimizeQuery:
@@ -246,6 +246,8 @@ func Node(sb *strings.Builder, node interface{}, depth int) {
246246
explainCreateIndexQuery(sb, n, indent, depth)
247247
case *ast.UpdateQuery:
248248
explainUpdateQuery(sb, n, indent, depth)
249+
case *ast.ParallelWithQuery:
250+
explainParallelWithQuery(sb, n, indent, depth)
249251

250252
// Types
251253
case *ast.DataType:

internal/explain/expressions.go

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,16 @@ func containsOnlyArraysOrTuples(exprs []ast.Expression) bool {
228228
// any non-literal expressions (identifiers, function calls, etc.)
229229
func containsNonLiteralExpressions(exprs []ast.Expression) bool {
230230
for _, e := range exprs {
231-
if _, ok := e.(*ast.Literal); !ok {
232-
return true
231+
if _, ok := e.(*ast.Literal); ok {
232+
continue
233+
}
234+
// Unary minus of a literal (negative number) is also acceptable
235+
if unary, ok := e.(*ast.UnaryExpr); ok && unary.Op == "-" {
236+
if _, ok := unary.Operand.(*ast.Literal); ok {
237+
continue
238+
}
233239
}
240+
return true
234241
}
235242
return false
236243
}
@@ -498,6 +505,7 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
498505
if e.Type == ast.LiteralArray {
499506
if exprs, ok := e.Value.([]ast.Expression); ok {
500507
needsFunctionFormat := false
508+
hasNestedArrays := false
501509
// Empty arrays always use Function array format
502510
if len(exprs) == 0 {
503511
needsFunctionFormat = true
@@ -508,6 +516,17 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
508516
needsFunctionFormat = true
509517
break
510518
}
519+
// Check for nested arrays
520+
if lit, ok := expr.(*ast.Literal); ok && lit.Type == ast.LiteralArray {
521+
hasNestedArrays = true
522+
// Check if inner array is empty or contains empty arrays
523+
if innerExprs, ok := lit.Value.([]ast.Expression); ok {
524+
if len(innerExprs) == 0 || containsEmptyArrays(innerExprs) {
525+
needsFunctionFormat = true
526+
break
527+
}
528+
}
529+
}
511530
// Check for identifiers - use Function array
512531
if _, ok := expr.(*ast.Identifier); ok {
513532
needsFunctionFormat = true
@@ -518,6 +537,41 @@ func explainAliasedExpr(sb *strings.Builder, n *ast.AliasedExpr, depth int) {
518537
needsFunctionFormat = true
519538
break
520539
}
540+
// Check for CAST expressions - use Function array
541+
if _, ok := expr.(*ast.CastExpr); ok {
542+
needsFunctionFormat = true
543+
break
544+
}
545+
// Check for binary expressions - use Function array
546+
if _, ok := expr.(*ast.BinaryExpr); ok {
547+
needsFunctionFormat = true
548+
break
549+
}
550+
// Check for other non-literal expressions (skip arrays/tuples which are handled separately)
551+
if lit, ok := expr.(*ast.Literal); !ok {
552+
// Not a literal - check if it's a unary negation of a number (which is OK)
553+
if unary, ok := expr.(*ast.UnaryExpr); ok && unary.Op == "-" {
554+
if innerLit, ok := unary.Operand.(*ast.Literal); ok {
555+
if innerLit.Type == ast.LiteralInteger || innerLit.Type == ast.LiteralFloat {
556+
continue // Negated number is OK
557+
}
558+
}
559+
}
560+
needsFunctionFormat = true
561+
break
562+
} else if lit.Type != ast.LiteralArray && lit.Type != ast.LiteralTuple {
563+
// Simple literal (not array/tuple) - OK
564+
continue
565+
}
566+
// Arrays and tuples are handled by the earlier checks for nested arrays
567+
}
568+
// Also check for empty arrays at any depth within nested arrays
569+
if hasNestedArrays && containsEmptyArraysRecursive(exprs) {
570+
needsFunctionFormat = true
571+
}
572+
// Also check for tuples at any depth within nested arrays
573+
if hasNestedArrays && containsTuplesRecursive(exprs) {
574+
needsFunctionFormat = true
521575
}
522576
if needsFunctionFormat {
523577
// Render as Function array with alias

internal/explain/format.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,9 @@ func FormatDataType(dt *ast.DataType) string {
286286
} else {
287287
params = append(params, fmt.Sprintf("%v", p))
288288
}
289+
} else if ident, ok := p.(*ast.Identifier); ok {
290+
// Identifier (e.g., function name in AggregateFunction types)
291+
params = append(params, ident.Name())
289292
} else {
290293
params = append(params, fmt.Sprintf("%v", p))
291294
}

0 commit comments

Comments
 (0)