Skip to main content

polyglot_sql/
transforms.rs

1//! SQL AST Transforms
2//!
3//! This module provides functions to transform SQL ASTs for dialect compatibility.
4//! These transforms are used during transpilation to convert dialect-specific features
5//! to forms that are supported by the target dialect.
6//!
7//! Based on the Python implementation in `sqlglot/transforms.py`.
8
9use crate::error::Result;
10use crate::dialects::{Dialect, DialectType};
11use crate::dialects::transform_recursive;
12use crate::expressions::{
13    Alias, BinaryOp, BooleanLiteral, Cast, DataType, Exists, Expression, From, Identifier, Join,
14    Function, JoinKind, Lateral, LateralView, Literal, NamedArgSeparator, NamedArgument, Over, Select, StructField,
15    Subquery, UnaryFunc, UnnestFunc, Where,
16};
17use std::cell::RefCell;
18
19/// Apply a chain of transforms to an expression
20///
21/// # Arguments
22/// * `expr` - The expression to transform
23/// * `transforms` - A list of transform functions to apply in order
24///
25/// # Returns
26/// The transformed expression
27pub fn preprocess<F>(expr: Expression, transforms: &[F]) -> Result<Expression>
28where
29    F: Fn(Expression) -> Result<Expression>,
30{
31    let mut result = expr;
32    for transform in transforms {
33        result = transform(result)?;
34    }
35    Ok(result)
36}
37
38/// Convert UNNEST to EXPLODE (for Spark/Hive compatibility)
39///
40/// UNNEST is standard SQL but Spark uses EXPLODE instead.
41pub fn unnest_to_explode(expr: Expression) -> Result<Expression> {
42    match expr {
43        Expression::Unnest(unnest) => {
44            Ok(Expression::Explode(Box::new(UnaryFunc::new(unnest.this))))
45        }
46        _ => Ok(expr),
47    }
48}
49
50/// Convert CROSS JOIN UNNEST to LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks.
51///
52/// This is a SELECT-level structural transformation that:
53/// 1. Converts UNNEST in FROM clause to INLINE/EXPLODE
54/// 2. Converts CROSS JOIN (LATERAL) UNNEST to LATERAL VIEW entries
55/// 3. For single-arg UNNEST: uses EXPLODE
56/// 4. For multi-arg UNNEST: uses INLINE(ARRAYS_ZIP(...))
57///
58/// Based on Python sqlglot's `unnest_to_explode` transform in transforms.py (lines 290-391).
59pub fn unnest_to_explode_select(expr: Expression) -> Result<Expression> {
60    transform_recursive(expr, &unnest_to_explode_select_inner)
61}
62
63/// Helper to determine the UDTF function for an UNNEST expression.
64/// Single-arg UNNEST → EXPLODE, multi-arg → INLINE
65fn make_udtf_expr(unnest: &UnnestFunc) -> Expression {
66    let has_multi_expr = !unnest.expressions.is_empty();
67    if has_multi_expr {
68        // Multi-arg: INLINE(ARRAYS_ZIP(arg1, arg2, ...))
69        let mut all_args = vec![unnest.this.clone()];
70        all_args.extend(unnest.expressions.iter().cloned());
71        let arrays_zip = Expression::Function(Box::new(Function::new(
72            "ARRAYS_ZIP".to_string(),
73            all_args,
74        )));
75        Expression::Function(Box::new(Function::new(
76            "INLINE".to_string(),
77            vec![arrays_zip],
78        )))
79    } else {
80        // Single-arg: EXPLODE(arg)
81        Expression::Explode(Box::new(UnaryFunc::new(unnest.this.clone())))
82    }
83}
84
85fn unnest_to_explode_select_inner(expr: Expression) -> Result<Expression> {
86    let Expression::Select(mut select) = expr else { return Ok(expr); };
87
88    // Process FROM clause: UNNEST items need conversion
89    if let Some(ref mut from) = select.from {
90        if from.expressions.len() >= 1 {
91            let mut new_from_exprs = Vec::new();
92            let mut new_lateral_views = Vec::new();
93            let first_is_unnest = is_unnest_expr(&from.expressions[0]);
94
95            for (idx, from_item) in from.expressions.drain(..).enumerate() {
96                if idx == 0 && first_is_unnest {
97                    // UNNEST is the first (and possibly only) item in FROM
98                    // Replace it with INLINE/EXPLODE, keeping alias
99                    let replaced = replace_from_unnest(from_item);
100                    new_from_exprs.push(replaced);
101                } else if idx > 0 && is_unnest_expr(&from_item) {
102                    // Additional UNNEST items in FROM (comma-joined) → LATERAL VIEW
103                    let (alias_name, column_aliases, unnest_func) = extract_unnest_info(from_item);
104                    let udtf = make_udtf_expr(&unnest_func);
105                    new_lateral_views.push(LateralView {
106                        this: udtf,
107                        table_alias: alias_name,
108                        column_aliases,
109                        outer: false,
110                    });
111                } else {
112                    new_from_exprs.push(from_item);
113                }
114            }
115
116            from.expressions = new_from_exprs;
117            // Append lateral views for comma-joined UNNESTs
118            select.lateral_views.extend(new_lateral_views);
119        }
120    }
121
122    // Process joins: CROSS JOIN (LATERAL) UNNEST → LATERAL VIEW
123    let mut remaining_joins = Vec::new();
124    for join in select.joins.drain(..) {
125        if matches!(join.kind, JoinKind::Cross | JoinKind::Inner) {
126            let (is_unnest, is_lateral) = check_join_unnest(&join.this);
127            if is_unnest {
128                // Extract UNNEST info from join, handling Lateral wrapper
129                let (lateral_alias, lateral_col_aliases, join_expr) = if is_lateral {
130                    if let Expression::Lateral(lat) = join.this {
131                        // Extract alias from Lateral struct
132                        let alias = lat.alias.map(|s| Identifier::new(&s));
133                        let col_aliases: Vec<Identifier> = lat.column_aliases.iter()
134                            .map(|s| Identifier::new(s))
135                            .collect();
136                        (alias, col_aliases, *lat.this)
137                    } else {
138                        (None, Vec::new(), join.this)
139                    }
140                } else {
141                    (None, Vec::new(), join.this)
142                };
143
144                let (alias_name, column_aliases, unnest_func) = extract_unnest_info(join_expr);
145
146                // Prefer Lateral's alias over UNNEST's alias
147                let final_alias = lateral_alias.or(alias_name);
148                let final_col_aliases = if !lateral_col_aliases.is_empty() {
149                    lateral_col_aliases
150                } else {
151                    column_aliases
152                };
153
154                // Use "unnest" as default alias if none provided (for single-arg case)
155                let table_alias = final_alias.or_else(|| {
156                    Some(Identifier::new("unnest"))
157                });
158                let col_aliases = if final_col_aliases.is_empty() {
159                    vec![Identifier::new("unnest")]
160                } else {
161                    final_col_aliases
162                };
163
164                let udtf = make_udtf_expr(&unnest_func);
165                select.lateral_views.push(LateralView {
166                    this: udtf,
167                    table_alias,
168                    column_aliases: col_aliases,
169                    outer: false,
170                });
171            } else {
172                remaining_joins.push(join);
173            }
174        } else {
175            remaining_joins.push(join);
176        }
177    }
178    select.joins = remaining_joins;
179
180    Ok(Expression::Select(select))
181}
182
183/// Check if an expression is or wraps an UNNEST
184fn is_unnest_expr(expr: &Expression) -> bool {
185    match expr {
186        Expression::Unnest(_) => true,
187        Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
188        _ => false,
189    }
190}
191
192/// Check if a join's expression is an UNNEST (possibly wrapped in Lateral)
193fn check_join_unnest(expr: &Expression) -> (bool, bool) {
194    match expr {
195        Expression::Unnest(_) => (true, false),
196        Expression::Alias(a) => {
197            if matches!(a.this, Expression::Unnest(_)) {
198                (true, false)
199            } else {
200                (false, false)
201            }
202        }
203        Expression::Lateral(lat) => {
204            match &*lat.this {
205                Expression::Unnest(_) => (true, true),
206                Expression::Alias(a) => {
207                    if matches!(a.this, Expression::Unnest(_)) {
208                        (true, true)
209                    } else {
210                        (false, true)
211                    }
212                }
213                _ => (false, true),
214            }
215        }
216        _ => (false, false),
217    }
218}
219
220/// Replace an UNNEST in FROM with INLINE/EXPLODE, preserving alias structure
221fn replace_from_unnest(from_item: Expression) -> Expression {
222    match from_item {
223        Expression::Alias(mut a) => {
224            if let Expression::Unnest(unnest) = a.this {
225                a.this = make_udtf_expr(&unnest);
226            }
227            Expression::Alias(a)
228        }
229        Expression::Unnest(unnest) => {
230            make_udtf_expr(&unnest)
231        }
232        other => other,
233    }
234}
235
236/// Extract alias info and UnnestFunc from an expression (possibly wrapped in Alias)
237fn extract_unnest_info(expr: Expression) -> (Option<Identifier>, Vec<Identifier>, UnnestFunc) {
238    match expr {
239        Expression::Alias(a) => {
240            if let Expression::Unnest(unnest) = a.this {
241                (Some(a.alias), a.column_aliases, *unnest)
242            } else {
243                // Should not happen if we already checked is_unnest_expr
244                (Some(a.alias), a.column_aliases, UnnestFunc {
245                    this: a.this,
246                    expressions: Vec::new(),
247                    with_ordinality: false,
248                    alias: None,
249                    offset_alias: None,
250                })
251            }
252        }
253        Expression::Unnest(unnest) => {
254            let alias = unnest.alias.clone();
255            (alias, Vec::new(), *unnest)
256        }
257        _ => {
258            (None, Vec::new(), UnnestFunc {
259                this: expr,
260                expressions: Vec::new(),
261                with_ordinality: false,
262                alias: None,
263                offset_alias: None,
264            })
265        }
266    }
267}
268
269/// Convert EXPLODE to UNNEST (for standard SQL compatibility)
270pub fn explode_to_unnest(expr: Expression) -> Result<Expression> {
271    match expr {
272        Expression::Explode(explode) => {
273            Ok(Expression::Unnest(Box::new(UnnestFunc {
274                this: explode.this,
275                expressions: Vec::new(),
276                with_ordinality: false,
277                alias: None,
278                offset_alias: None,
279            })))
280        }
281        _ => Ok(expr),
282    }
283}
284
285/// Replace boolean literals for dialects that don't support them
286///
287/// Converts TRUE/FALSE to 1/0 for dialects like older MySQL versions
288pub fn replace_bool_with_int(expr: Expression) -> Result<Expression> {
289    match expr {
290        Expression::Boolean(b) => {
291            let value = if b.value { "1" } else { "0" };
292            Ok(Expression::Literal(Literal::Number(value.to_string())))
293        }
294        _ => Ok(expr),
295    }
296}
297
298/// Replace integer literals for dialects that prefer boolean
299///
300/// Converts 1/0 to TRUE/FALSE
301pub fn replace_int_with_bool(expr: Expression) -> Result<Expression> {
302    match expr {
303        Expression::Literal(Literal::Number(n)) if n == "1" || n == "0" => {
304            Ok(Expression::Boolean(BooleanLiteral { value: n == "1" }))
305        }
306        _ => Ok(expr),
307    }
308}
309
310
311/// Remove precision from parameterized types
312///
313/// Some dialects don't support precision parameters on certain types.
314/// This transform removes them, e.g., VARCHAR(255) → VARCHAR, DECIMAL(10,2) → DECIMAL
315pub fn remove_precision_parameterized_types(expr: Expression) -> Result<Expression> {
316    Ok(strip_type_params_recursive(expr))
317}
318
319/// Recursively strip type parameters from DataType values in an expression
320fn strip_type_params_recursive(expr: Expression) -> Expression {
321    match expr {
322        // Handle Cast expressions - strip precision from target type
323        Expression::Cast(mut cast) => {
324            cast.to = strip_data_type_params(cast.to);
325            // Also recursively process the expression being cast
326            cast.this = strip_type_params_recursive(cast.this);
327            Expression::Cast(cast)
328        }
329        // Handle TryCast expressions (uses same Cast struct)
330        Expression::TryCast(mut try_cast) => {
331            try_cast.to = strip_data_type_params(try_cast.to);
332            try_cast.this = strip_type_params_recursive(try_cast.this);
333            Expression::TryCast(try_cast)
334        }
335        // Handle SafeCast expressions (uses same Cast struct)
336        Expression::SafeCast(mut safe_cast) => {
337            safe_cast.to = strip_data_type_params(safe_cast.to);
338            safe_cast.this = strip_type_params_recursive(safe_cast.this);
339            Expression::SafeCast(safe_cast)
340        }
341        // For now, pass through other expressions
342        // A full implementation would recursively visit all nodes
343        _ => expr,
344    }
345}
346
347/// Strip precision/scale/length parameters from a DataType
348fn strip_data_type_params(dt: DataType) -> DataType {
349    match dt {
350        // Numeric types with precision/scale
351        DataType::Decimal { .. } => DataType::Decimal { precision: None, scale: None },
352        DataType::TinyInt { .. } => DataType::TinyInt { length: None },
353        DataType::SmallInt { .. } => DataType::SmallInt { length: None },
354        DataType::Int { .. } => DataType::Int { length: None, integer_spelling: false },
355        DataType::BigInt { .. } => DataType::BigInt { length: None },
356
357        // String types with length
358        DataType::Char { .. } => DataType::Char { length: None },
359        DataType::VarChar { .. } => DataType::VarChar { length: None, parenthesized_length: false },
360
361        // Binary types with length
362        DataType::Binary { .. } => DataType::Binary { length: None },
363        DataType::VarBinary { .. } => DataType::VarBinary { length: None },
364
365        // Bit types with length
366        DataType::Bit { .. } => DataType::Bit { length: None },
367        DataType::VarBit { .. } => DataType::VarBit { length: None },
368
369        // Time types with precision
370        DataType::Time { .. } => DataType::Time { precision: None, timezone: false },
371        DataType::Timestamp { timezone, .. } => DataType::Timestamp { precision: None, timezone },
372
373        // Array - recursively strip element type
374        DataType::Array { element_type, dimension } => DataType::Array {
375            element_type: Box::new(strip_data_type_params(*element_type)),
376            dimension,
377        },
378
379        // Map - recursively strip key and value types
380        DataType::Map { key_type, value_type } => DataType::Map {
381            key_type: Box::new(strip_data_type_params(*key_type)),
382            value_type: Box::new(strip_data_type_params(*value_type)),
383        },
384
385        // Struct - recursively strip field types
386        DataType::Struct { fields, nested } => DataType::Struct {
387            fields: fields
388                .into_iter()
389                .map(|f| StructField::with_options(f.name, strip_data_type_params(f.data_type), f.options))
390                .collect(),
391            nested,
392        },
393
394        // Vector - strip dimension
395        DataType::Vector { element_type, .. } => DataType::Vector {
396            element_type: element_type.map(|et| Box::new(strip_data_type_params(*et))),
397            dimension: None,
398        },
399
400        // Object - recursively strip field types
401        DataType::Object { fields, modifier } => DataType::Object {
402            fields: fields
403                .into_iter()
404                .map(|(name, ty, not_null)| (name, strip_data_type_params(ty), not_null))
405                .collect(),
406            modifier,
407        },
408
409        // Other types pass through unchanged
410        other => other,
411    }
412}
413
414
415/// Eliminate QUALIFY clause by converting to a subquery with WHERE filter
416///
417/// QUALIFY is supported by Snowflake, BigQuery, and DuckDB but not by most other dialects.
418///
419/// Converts:
420/// ```sql
421/// SELECT * FROM t QUALIFY ROW_NUMBER() OVER (...) = 1
422/// ```
423/// To:
424/// ```sql
425/// SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS _w FROM t) _t WHERE _w = 1
426/// ```
427///
428/// Reference: `transforms.py:194-255`
429pub fn eliminate_qualify(expr: Expression) -> Result<Expression> {
430    match expr {
431        Expression::Select(mut select) => {
432            if let Some(qualify) = select.qualify.take() {
433                // Python sqlglot approach:
434                // 1. Extract the window function from the qualify condition
435                // 2. Add it as _w alias to the inner select
436                // 3. Replace the window function reference with _w in the outer WHERE
437                // 4. Keep original select expressions in the outer query
438
439                let qualify_filter = qualify.this;
440                let window_alias_name = "_w".to_string();
441                let window_alias_ident = Identifier::new(window_alias_name.clone());
442
443                // Try to extract window function from comparison
444                // Pattern: WINDOW_FUNC = value -> inner adds WINDOW_FUNC AS _w, outer WHERE _w = value
445                let (window_expr, outer_where) = extract_window_from_condition(
446                    qualify_filter.clone(),
447                    &window_alias_ident,
448                );
449
450                if let Some(win_expr) = window_expr {
451                    // Add window function as _w alias to inner select
452                    let window_alias_expr = Expression::Alias(Box::new(crate::expressions::Alias {
453                        this: win_expr,
454                        alias: window_alias_ident.clone(),
455                        column_aliases: vec![],
456                        pre_alias_comments: vec![],
457                        trailing_comments: vec![],
458                    }));
459
460                    // Save original expressions for the outer SELECT
461                    let original_exprs = select.expressions.clone();
462                    select.expressions.push(window_alias_expr);
463
464                    // Create the inner subquery
465                    let inner_select = Expression::Select(select);
466                    let subquery = Subquery {
467                        this: inner_select,
468                        alias: Some(Identifier::new("_t".to_string())),
469                        column_aliases: vec![],
470                        order_by: None,
471                        limit: None,
472                        offset: None,
473                        distribute_by: None,
474                        sort_by: None,
475                        cluster_by: None,
476                        lateral: false,
477                        modifiers_inside: false,
478                        trailing_comments: vec![],
479                    };
480
481                    // Create the outer SELECT with original expressions and WHERE _w <op> value
482                    let outer_select = Select {
483                        expressions: original_exprs,
484                        from: Some(From {
485                            expressions: vec![Expression::Subquery(Box::new(subquery))],
486                        }),
487                        where_clause: Some(Where {
488                            this: outer_where,
489                        }),
490                        ..Select::new()
491                    };
492
493                    return Ok(Expression::Select(Box::new(outer_select)));
494                } else {
495                    // Fallback: if we can't extract a window function, use old approach
496                    let qualify_alias = Expression::Alias(Box::new(crate::expressions::Alias {
497                        this: qualify_filter.clone(),
498                        alias: window_alias_ident.clone(),
499                        column_aliases: vec![],
500                        pre_alias_comments: vec![],
501                        trailing_comments: vec![],
502                    }));
503
504                    let original_exprs = select.expressions.clone();
505                    select.expressions.push(qualify_alias);
506
507                    let inner_select = Expression::Select(select);
508                    let subquery = Subquery {
509                        this: inner_select,
510                        alias: Some(Identifier::new("_t".to_string())),
511                        column_aliases: vec![],
512                        order_by: None,
513                        limit: None,
514                        offset: None,
515                        distribute_by: None,
516                        sort_by: None,
517                        cluster_by: None,
518                        lateral: false,
519                        modifiers_inside: false,
520                        trailing_comments: vec![],
521                    };
522
523                    let outer_select = Select {
524                        expressions: original_exprs,
525                        from: Some(From {
526                            expressions: vec![Expression::Subquery(Box::new(subquery))],
527                        }),
528                        where_clause: Some(Where {
529                            this: Expression::Column(crate::expressions::Column {
530                                name: window_alias_ident,
531                                table: None,
532                                join_mark: false,
533                                trailing_comments: vec![],
534                            }),
535                        }),
536                        ..Select::new()
537                    };
538
539                    return Ok(Expression::Select(Box::new(outer_select)));
540                }
541            }
542            Ok(Expression::Select(select))
543        }
544        other => Ok(other),
545    }
546}
547
548/// Extract a window function from a qualify condition.
549/// Returns (window_expression, rewritten_condition) if found.
550/// The rewritten condition replaces the window function with a column reference to the alias.
551fn extract_window_from_condition(
552    condition: Expression,
553    alias: &Identifier,
554) -> (Option<Expression>, Expression) {
555    let alias_col = Expression::Column(crate::expressions::Column {
556        name: alias.clone(),
557        table: None,
558        join_mark: false,
559        trailing_comments: vec![],
560    });
561
562    // Check if condition is a simple comparison with a window function on one side
563    match condition {
564        // WINDOW_FUNC = value
565        Expression::Eq(ref op) => {
566            if is_window_expr(&op.left) {
567                (Some(op.left.clone()), Expression::Eq(Box::new(BinaryOp {
568                    left: alias_col,
569                    right: op.right.clone(),
570                    ..(**op).clone()
571                })))
572            } else if is_window_expr(&op.right) {
573                (Some(op.right.clone()), Expression::Eq(Box::new(BinaryOp {
574                    left: op.left.clone(),
575                    right: alias_col,
576                    ..(**op).clone()
577                })))
578            } else {
579                (None, condition)
580            }
581        }
582        Expression::Neq(ref op) => {
583            if is_window_expr(&op.left) {
584                (Some(op.left.clone()), Expression::Neq(Box::new(BinaryOp {
585                    left: alias_col,
586                    right: op.right.clone(),
587                    ..(**op).clone()
588                })))
589            } else if is_window_expr(&op.right) {
590                (Some(op.right.clone()), Expression::Neq(Box::new(BinaryOp {
591                    left: op.left.clone(),
592                    right: alias_col,
593                    ..(**op).clone()
594                })))
595            } else {
596                (None, condition)
597            }
598        }
599        Expression::Lt(ref op) => {
600            if is_window_expr(&op.left) {
601                (Some(op.left.clone()), Expression::Lt(Box::new(BinaryOp {
602                    left: alias_col,
603                    right: op.right.clone(),
604                    ..(**op).clone()
605                })))
606            } else if is_window_expr(&op.right) {
607                (Some(op.right.clone()), Expression::Lt(Box::new(BinaryOp {
608                    left: op.left.clone(),
609                    right: alias_col,
610                    ..(**op).clone()
611                })))
612            } else {
613                (None, condition)
614            }
615        }
616        Expression::Lte(ref op) => {
617            if is_window_expr(&op.left) {
618                (Some(op.left.clone()), Expression::Lte(Box::new(BinaryOp {
619                    left: alias_col,
620                    right: op.right.clone(),
621                    ..(**op).clone()
622                })))
623            } else if is_window_expr(&op.right) {
624                (Some(op.right.clone()), Expression::Lte(Box::new(BinaryOp {
625                    left: op.left.clone(),
626                    right: alias_col,
627                    ..(**op).clone()
628                })))
629            } else {
630                (None, condition)
631            }
632        }
633        Expression::Gt(ref op) => {
634            if is_window_expr(&op.left) {
635                (Some(op.left.clone()), Expression::Gt(Box::new(BinaryOp {
636                    left: alias_col,
637                    right: op.right.clone(),
638                    ..(**op).clone()
639                })))
640            } else if is_window_expr(&op.right) {
641                (Some(op.right.clone()), Expression::Gt(Box::new(BinaryOp {
642                    left: op.left.clone(),
643                    right: alias_col,
644                    ..(**op).clone()
645                })))
646            } else {
647                (None, condition)
648            }
649        }
650        Expression::Gte(ref op) => {
651            if is_window_expr(&op.left) {
652                (Some(op.left.clone()), Expression::Gte(Box::new(BinaryOp {
653                    left: alias_col,
654                    right: op.right.clone(),
655                    ..(**op).clone()
656                })))
657            } else if is_window_expr(&op.right) {
658                (Some(op.right.clone()), Expression::Gte(Box::new(BinaryOp {
659                    left: op.left.clone(),
660                    right: alias_col,
661                    ..(**op).clone()
662                })))
663            } else {
664                (None, condition)
665            }
666        }
667        // If the condition is just a window function (bare QUALIFY expression)
668        _ if is_window_expr(&condition) => {
669            (Some(condition), alias_col)
670        }
671        // Can't extract window function
672        _ => (None, condition),
673    }
674}
675
676/// Check if an expression is a window function
677fn is_window_expr(expr: &Expression) -> bool {
678    matches!(expr, Expression::Window(_) | Expression::WindowFunction(_))
679}
680
681/// Eliminate DISTINCT ON clause by converting to a subquery with ROW_NUMBER
682///
683/// DISTINCT ON is PostgreSQL-specific. For dialects that don't support it,
684/// this converts it to a subquery with a ROW_NUMBER() window function.
685///
686/// Converts:
687/// ```sql
688/// SELECT DISTINCT ON (a) a, b FROM t ORDER BY a, b
689/// ```
690/// To:
691/// ```sql
692/// SELECT a, b FROM (
693///     SELECT a, b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY a, b) AS _row_number
694///     FROM t
695/// ) _t WHERE _row_number = 1
696/// ```
697///
698/// Reference: `transforms.py:138-191`
699pub fn eliminate_distinct_on(expr: Expression) -> Result<Expression> {
700    eliminate_distinct_on_for_dialect(expr, None)
701}
702
703/// Eliminate DISTINCT ON with dialect-specific NULL ordering behavior.
704///
705/// For dialects where NULLs don't sort first by default in DESC ordering,
706/// we need to add explicit NULL ordering to preserve DISTINCT ON semantics.
707pub fn eliminate_distinct_on_for_dialect(expr: Expression, target: Option<DialectType>) -> Result<Expression> {
708    use crate::expressions::Case;
709
710    // PostgreSQL and DuckDB support DISTINCT ON natively - skip elimination
711    if matches!(target, Some(DialectType::PostgreSQL) | Some(DialectType::DuckDB)) {
712        return Ok(expr);
713    }
714
715    // Determine NULL ordering mode based on target dialect
716    // Oracle/Redshift/Snowflake: NULLS FIRST is default for DESC -> no change needed
717    // BigQuery/Spark/Presto/Hive/etc: need explicit NULLS FIRST
718    // MySQL/StarRocks/TSQL: no NULLS FIRST syntax -> use CASE WHEN IS NULL
719    enum NullsMode {
720        None,         // Default NULLS FIRST behavior (Oracle, Redshift, Snowflake)
721        NullsFirst,   // Add explicit NULLS FIRST (BigQuery, Spark, Presto, Hive, etc.)
722        CaseExpr,     // Use CASE WHEN IS NULL for NULLS FIRST simulation (MySQL, StarRocks, TSQL)
723    }
724
725    let nulls_mode = match target {
726        Some(DialectType::MySQL) | Some(DialectType::StarRocks) | Some(DialectType::SingleStore)
727        | Some(DialectType::TSQL) | Some(DialectType::Fabric) => NullsMode::CaseExpr,
728        Some(DialectType::Oracle) | Some(DialectType::Redshift) | Some(DialectType::Snowflake) => NullsMode::None,
729        // All other dialects that don't support DISTINCT ON: use NULLS FIRST
730        _ => NullsMode::NullsFirst,
731    };
732
733    match expr {
734        Expression::Select(mut select) => {
735            if let Some(distinct_cols) = select.distinct_on.take() {
736                if !distinct_cols.is_empty() {
737                    // Create ROW_NUMBER() OVER (PARTITION BY distinct_cols ORDER BY ...)
738                    let row_number_alias = Identifier::new("_row_number".to_string());
739
740                    // Get order_by expressions, or use distinct_cols as default order
741                    let order_exprs = if let Some(ref order_by) = select.order_by {
742                        let mut exprs = order_by.expressions.clone();
743                        // Add NULL ordering based on target dialect
744                        match nulls_mode {
745                            NullsMode::NullsFirst => {
746                                for ord in &mut exprs {
747                                    if ord.desc && ord.nulls_first.is_none() {
748                                        ord.nulls_first = Some(true);
749                                    }
750                                }
751                            }
752                            NullsMode::CaseExpr => {
753                                // For each DESC column without explicit nulls ordering,
754                                // prepend: CASE WHEN col IS NULL THEN 1 ELSE 0 END DESC
755                                let mut new_exprs = Vec::new();
756                                for ord in &exprs {
757                                    if ord.desc && ord.nulls_first.is_none() {
758                                        // Add CASE WHEN col IS NULL THEN 1 ELSE 0 END DESC
759                                        let null_check = Expression::Case(Box::new(Case {
760                                            operand: None,
761                                            whens: vec![(
762                                                Expression::IsNull(Box::new(crate::expressions::IsNull {
763                                                    this: ord.this.clone(),
764                                                    not: false,
765                                                    postfix_form: false,
766                                                })),
767                                                Expression::Literal(Literal::Number("1".to_string())),
768                                            )],
769                                            else_: Some(Expression::Literal(Literal::Number("0".to_string()))),
770                                        }));
771                                        new_exprs.push(crate::expressions::Ordered {
772                                            this: null_check,
773                                            desc: true,
774                                            nulls_first: None,
775                                            explicit_asc: false,
776                                            with_fill: None,
777                                        });
778                                    }
779                                    new_exprs.push(ord.clone());
780                                }
781                                exprs = new_exprs;
782                            }
783                            NullsMode::None => {}
784                        }
785                        exprs
786                    } else {
787                        distinct_cols
788                            .iter()
789                            .map(|e| crate::expressions::Ordered {
790                                this: e.clone(),
791                                desc: false,
792                                nulls_first: None,
793                                explicit_asc: false,
794                                with_fill: None,
795                            })
796                            .collect()
797                    };
798
799                    // Create window function: ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
800                    let row_number_func = Expression::WindowFunction(Box::new(
801                        crate::expressions::WindowFunction {
802                            this: Expression::RowNumber(crate::expressions::RowNumber),
803                            over: Over {
804                                partition_by: distinct_cols,
805                                order_by: order_exprs,
806                                frame: None,
807                                window_name: None,
808                                alias: None,
809                            },
810                            keep: None,
811                        },
812                    ));
813
814                    // Build aliased inner expressions and outer column references
815                    // Inner: SELECT a AS a, b AS b, ROW_NUMBER() OVER (...) AS _row_number
816                    // Outer: SELECT a, b FROM (...)
817                    let mut inner_aliased_exprs = Vec::new();
818                    let mut outer_select_exprs = Vec::new();
819                    for orig_expr in &select.expressions {
820                        match orig_expr {
821                            Expression::Alias(alias) => {
822                                // Already aliased - keep as-is in inner, reference alias in outer
823                                inner_aliased_exprs.push(orig_expr.clone());
824                                outer_select_exprs.push(Expression::Column(
825                                    crate::expressions::Column {
826                                        name: alias.alias.clone(),
827                                        table: None,
828                                        join_mark: false,
829                                        trailing_comments: vec![],
830                                    },
831                                ));
832                            }
833                            Expression::Column(col) => {
834                                // Wrap in alias: a AS a in inner, just a in outer
835                                inner_aliased_exprs.push(Expression::Alias(Box::new(
836                                    crate::expressions::Alias {
837                                        this: orig_expr.clone(),
838                                        alias: col.name.clone(),
839                                        column_aliases: vec![],
840                                        pre_alias_comments: vec![],
841                                        trailing_comments: vec![],
842                                    },
843                                )));
844                                outer_select_exprs.push(Expression::Column(
845                                    crate::expressions::Column {
846                                        name: col.name.clone(),
847                                        table: None,
848                                        join_mark: false,
849                                        trailing_comments: vec![],
850                                    },
851                                ));
852                            }
853                            _ => {
854                                // Complex expression without alias - include as-is in both
855                                inner_aliased_exprs.push(orig_expr.clone());
856                                outer_select_exprs.push(orig_expr.clone());
857                            }
858                        }
859                    }
860
861                    // Add ROW_NUMBER as aliased expression to inner select list
862                    let row_number_alias_expr =
863                        Expression::Alias(Box::new(crate::expressions::Alias {
864                            this: row_number_func,
865                            alias: row_number_alias.clone(),
866                            column_aliases: vec![],
867                            pre_alias_comments: vec![],
868                            trailing_comments: vec![],
869                        }));
870                    inner_aliased_exprs.push(row_number_alias_expr);
871
872                    // Replace inner select's expressions with aliased versions
873                    select.expressions = inner_aliased_exprs;
874
875                    // Remove ORDER BY from inner query (it's now in the window function)
876                    let _inner_order_by = select.order_by.take();
877
878                    // Clear DISTINCT from inner select (DISTINCT ON is replaced by ROW_NUMBER)
879                    select.distinct = false;
880
881                    // Create inner subquery
882                    let inner_select = Expression::Select(select);
883                    let subquery = Subquery {
884                        this: inner_select,
885                        alias: Some(Identifier::new("_t".to_string())),
886                        column_aliases: vec![],
887                        order_by: None,
888                        limit: None,
889                        offset: None,
890                        distribute_by: None,
891                        sort_by: None,
892                        cluster_by: None,
893                        lateral: false,
894                        modifiers_inside: false,
895                        trailing_comments: vec![],
896                    };
897
898                    // Create outer SELECT with WHERE _row_number = 1
899                    // No ORDER BY on outer query
900                    let outer_select = Select {
901                        expressions: outer_select_exprs,
902                        from: Some(From {
903                            expressions: vec![Expression::Subquery(Box::new(subquery))],
904                        }),
905                        where_clause: Some(Where {
906                            this: Expression::Eq(Box::new(BinaryOp {
907                                left: Expression::Column(crate::expressions::Column {
908                                    name: row_number_alias,
909                                    table: None,
910                                    join_mark: false,
911                                    trailing_comments: vec![],
912                                }),
913                                right: Expression::Literal(Literal::Number("1".to_string())),
914                                left_comments: vec![],
915                                operator_comments: vec![],
916                                trailing_comments: vec![],
917                            })),
918                        }),
919                        ..Select::new()
920                    };
921
922                    return Ok(Expression::Select(Box::new(outer_select)));
923                }
924            }
925            Ok(Expression::Select(select))
926        }
927        other => Ok(other),
928    }
929}
930
931/// Convert SEMI and ANTI joins into equivalent forms that use EXISTS instead.
932///
933/// For dialects that don't support SEMI/ANTI join syntax, this converts:
934/// - `SELECT * FROM a SEMI JOIN b ON a.x = b.x` → `SELECT * FROM a WHERE EXISTS (SELECT 1 FROM b WHERE a.x = b.x)`
935/// - `SELECT * FROM a ANTI JOIN b ON a.x = b.x` → `SELECT * FROM a WHERE NOT EXISTS (SELECT 1 FROM b WHERE a.x = b.x)`
936///
937/// Reference: `transforms.py:607-621`
938pub fn eliminate_semi_and_anti_joins(expr: Expression) -> Result<Expression> {
939    match expr {
940        Expression::Select(mut select) => {
941            let mut new_joins = Vec::new();
942            let mut extra_where_conditions = Vec::new();
943
944            for join in select.joins.drain(..) {
945                match join.kind {
946                    JoinKind::Semi | JoinKind::LeftSemi => {
947                        if let Some(on_condition) = join.on {
948                            // Create: EXISTS (SELECT 1 FROM join_table WHERE on_condition)
949                            let subquery_select = Select {
950                                expressions: vec![Expression::Literal(Literal::Number(
951                                    "1".to_string(),
952                                ))],
953                                from: Some(From {
954                                    expressions: vec![join.this],
955                                }),
956                                where_clause: Some(Where {
957                                    this: on_condition,
958                                }),
959                                ..Select::new()
960                            };
961
962                            let exists = Expression::Exists(Box::new(Exists {
963                                this: Expression::Subquery(Box::new(Subquery {
964                                    this: Expression::Select(Box::new(subquery_select)),
965                                    alias: None,
966                                    column_aliases: vec![],
967                                    order_by: None,
968                                    limit: None,
969                                    offset: None,
970                                    distribute_by: None,
971                                    sort_by: None,
972                                    cluster_by: None,
973                                    lateral: false,
974                                    modifiers_inside: false,
975                                    trailing_comments: vec![],
976                                })),
977                                not: false,
978                            }));
979
980                            extra_where_conditions.push(exists);
981                        }
982                    }
983                    JoinKind::Anti | JoinKind::LeftAnti => {
984                        if let Some(on_condition) = join.on {
985                            // Create: NOT EXISTS (SELECT 1 FROM join_table WHERE on_condition)
986                            let subquery_select = Select {
987                                expressions: vec![Expression::Literal(Literal::Number(
988                                    "1".to_string(),
989                                ))],
990                                from: Some(From {
991                                    expressions: vec![join.this],
992                                }),
993                                where_clause: Some(Where {
994                                    this: on_condition,
995                                }),
996                                ..Select::new()
997                            };
998
999                            // Use Exists with not: true for NOT EXISTS
1000                            let not_exists = Expression::Exists(Box::new(Exists {
1001                                this: Expression::Subquery(Box::new(Subquery {
1002                                    this: Expression::Select(Box::new(subquery_select)),
1003                                    alias: None,
1004                                    column_aliases: vec![],
1005                                    order_by: None,
1006                                    limit: None,
1007                                    offset: None,
1008                                    distribute_by: None,
1009                                    sort_by: None,
1010                                    cluster_by: None,
1011                                    lateral: false,
1012                                    modifiers_inside: false,
1013                                    trailing_comments: vec![],
1014                                })),
1015                                not: true,
1016                            }));
1017
1018                            extra_where_conditions.push(not_exists);
1019                        }
1020                    }
1021                    _ => {
1022                        // Keep other join types as-is
1023                        new_joins.push(join);
1024                    }
1025                }
1026            }
1027
1028            select.joins = new_joins;
1029
1030            // Add EXISTS conditions to WHERE clause
1031            if !extra_where_conditions.is_empty() {
1032                let combined = extra_where_conditions
1033                    .into_iter()
1034                    .reduce(|acc, cond| {
1035                        Expression::And(Box::new(BinaryOp {
1036                            left: acc,
1037                            right: cond,
1038                            left_comments: vec![],
1039                            operator_comments: vec![],
1040                            trailing_comments: vec![],
1041                        }))
1042                    })
1043                    .unwrap();
1044
1045                select.where_clause = match select.where_clause {
1046                    Some(Where { this: existing }) => Some(Where {
1047                        this: Expression::And(Box::new(BinaryOp {
1048                            left: existing,
1049                            right: combined,
1050                            left_comments: vec![],
1051                            operator_comments: vec![],
1052                            trailing_comments: vec![],
1053                        })),
1054                    }),
1055                    None => Some(Where { this: combined }),
1056                };
1057            }
1058
1059            Ok(Expression::Select(select))
1060        }
1061        other => Ok(other),
1062    }
1063}
1064
1065/// Convert FULL OUTER JOIN to a UNION of LEFT and RIGHT OUTER joins.
1066///
1067/// For dialects that don't support FULL OUTER JOIN, this converts:
1068/// ```sql
1069/// SELECT * FROM a FULL OUTER JOIN b ON a.x = b.x
1070/// ```
1071/// To:
1072/// ```sql
1073/// SELECT * FROM a LEFT OUTER JOIN b ON a.x = b.x
1074/// UNION ALL
1075/// SELECT * FROM a RIGHT OUTER JOIN b ON a.x = b.x
1076/// WHERE NOT EXISTS (SELECT 1 FROM a WHERE a.x = b.x)
1077/// ```
1078///
1079/// Note: This transformation currently only works for queries with a single FULL OUTER join.
1080///
1081/// Reference: `transforms.py:624-661`
1082pub fn eliminate_full_outer_join(expr: Expression) -> Result<Expression> {
1083    match expr {
1084        Expression::Select(mut select) => {
1085            // Find FULL OUTER joins
1086            let full_outer_join_idx = select.joins.iter().position(|j| j.kind == JoinKind::Full);
1087
1088            if let Some(idx) = full_outer_join_idx {
1089                // We only handle queries with a single FULL OUTER join
1090                let full_join_count = select.joins.iter().filter(|j| j.kind == JoinKind::Full).count();
1091                if full_join_count != 1 {
1092                    return Ok(Expression::Select(select));
1093                }
1094
1095                // Clone the query for the right side of the UNION
1096                let mut right_select = select.clone();
1097
1098                // Get the join condition from the FULL OUTER join
1099                let full_join = &select.joins[idx];
1100                let join_condition = full_join.on.clone();
1101
1102                // Left side: convert FULL to LEFT
1103                select.joins[idx].kind = JoinKind::Left;
1104
1105                // Right side: convert FULL to RIGHT and add NOT EXISTS condition
1106                right_select.joins[idx].kind = JoinKind::Right;
1107
1108                // Build NOT EXISTS for the right side to exclude rows that matched
1109                if let (Some(ref from), Some(ref join_cond)) = (&select.from, &join_condition) {
1110                    if !from.expressions.is_empty() {
1111                        let anti_subquery = Expression::Select(Box::new(Select {
1112                            expressions: vec![Expression::Literal(Literal::Number("1".to_string()))],
1113                            from: Some(from.clone()),
1114                            where_clause: Some(Where { this: join_cond.clone() }),
1115                            ..Select::new()
1116                        }));
1117
1118                        let not_exists = Expression::Not(Box::new(crate::expressions::UnaryOp {
1119                            this: Expression::Exists(Box::new(Exists {
1120                                this: Expression::Subquery(Box::new(Subquery {
1121                                    this: anti_subquery,
1122                                    alias: None,
1123                                    column_aliases: vec![],
1124                                    order_by: None,
1125                                    limit: None,
1126                                    offset: None,
1127                                    distribute_by: None,
1128                                    sort_by: None,
1129                                    cluster_by: None,
1130                                    lateral: false,
1131                                    modifiers_inside: false,
1132                                    trailing_comments: vec![],
1133                                })),
1134                                not: false,
1135                            })),
1136                        }));
1137
1138                        // Add NOT EXISTS to the WHERE clause
1139                        right_select.where_clause = Some(Where {
1140                            this: match right_select.where_clause {
1141                                Some(w) => Expression::And(Box::new(BinaryOp {
1142                                    left: w.this,
1143                                    right: not_exists,
1144                                    left_comments: vec![],
1145                                    operator_comments: vec![],
1146                                    trailing_comments: vec![],
1147                                })),
1148                                None => not_exists,
1149                            },
1150                        });
1151                    }
1152                }
1153
1154                // Remove WITH clause from right side (CTEs should only be on left)
1155                right_select.with = None;
1156
1157                // Remove ORDER BY from left side (will be applied after UNION)
1158                let order_by = select.order_by.take();
1159
1160                // Create UNION ALL of left and right
1161                let union = crate::expressions::Union {
1162                    left: Expression::Select(select),
1163                    right: Expression::Select(right_select),
1164                    all: true, // UNION ALL
1165                    distinct: false,
1166                    with: None,
1167                    order_by,
1168                    limit: None,
1169                    offset: None,
1170                    distribute_by: None,
1171                    sort_by: None,
1172                    cluster_by: None,
1173                    by_name: false,
1174                    side: None,
1175                    kind: None,
1176                    corresponding: false,
1177                    strict: false,
1178                    on_columns: Vec::new(),
1179                };
1180
1181                return Ok(Expression::Union(Box::new(union)));
1182            }
1183
1184            Ok(Expression::Select(select))
1185        }
1186        other => Ok(other),
1187    }
1188}
1189
1190/// Move CTEs to the top level of the query.
1191///
1192/// Some dialects (e.g., Hive, T-SQL, Spark prior to version 3) only allow CTEs to be
1193/// defined at the top-level, so for example queries like:
1194///
1195/// ```sql
1196/// SELECT * FROM (WITH t(c) AS (SELECT 1) SELECT * FROM t) AS subq
1197/// ```
1198///
1199/// are invalid in those dialects. This transformation moves all CTEs to the top level.
1200///
1201/// Reference: `transforms.py:664-700`
1202pub fn move_ctes_to_top_level(expr: Expression) -> Result<Expression> {
1203    match expr {
1204        Expression::Select(mut select) => {
1205            // Collect all CTEs from nested subqueries
1206            let mut collected_ctes: Vec<crate::expressions::Cte> = Vec::new();
1207            let mut has_recursive = false;
1208
1209            // Recursively find all WITH clauses in subqueries
1210            collect_nested_ctes(&Expression::Select(select.clone()), &mut collected_ctes, &mut has_recursive, true);
1211
1212            // If we collected any CTEs, add them to the top-level WITH
1213            if !collected_ctes.is_empty() {
1214                let top_with = select.with.get_or_insert_with(|| crate::expressions::With {
1215                    ctes: Vec::new(),
1216                    recursive: false,
1217                    leading_comments: vec![],
1218                    search: None,
1219                });
1220
1221                // Set recursive flag if any nested CTE was recursive
1222                if has_recursive {
1223                    top_with.recursive = true;
1224                }
1225
1226                // Prepend collected CTEs (they should come before existing ones)
1227                let mut new_ctes = collected_ctes;
1228                new_ctes.append(&mut top_with.ctes);
1229                top_with.ctes = new_ctes;
1230            }
1231
1232            Ok(Expression::Select(select))
1233        }
1234        other => Ok(other),
1235    }
1236}
1237
1238/// Helper to recursively collect CTEs from nested subqueries
1239fn collect_nested_ctes(
1240    expr: &Expression,
1241    collected: &mut Vec<crate::expressions::Cte>,
1242    has_recursive: &mut bool,
1243    is_top_level: bool,
1244) {
1245    match expr {
1246        Expression::Select(select) => {
1247            // If this is not the top level and has a WITH clause, collect its CTEs
1248            if !is_top_level {
1249                if let Some(ref with) = select.with {
1250                    if with.recursive {
1251                        *has_recursive = true;
1252                    }
1253                    collected.extend(with.ctes.clone());
1254                }
1255            }
1256
1257            // Recurse into FROM clause
1258            if let Some(ref from) = select.from {
1259                for expr in &from.expressions {
1260                    collect_nested_ctes(expr, collected, has_recursive, false);
1261                }
1262            }
1263
1264            // Recurse into JOINs
1265            for join in &select.joins {
1266                collect_nested_ctes(&join.this, collected, has_recursive, false);
1267            }
1268
1269            // Recurse into select expressions (for subqueries in SELECT)
1270            for sel_expr in &select.expressions {
1271                collect_nested_ctes(sel_expr, collected, has_recursive, false);
1272            }
1273
1274            // Recurse into WHERE
1275            if let Some(ref where_clause) = select.where_clause {
1276                collect_nested_ctes(&where_clause.this, collected, has_recursive, false);
1277            }
1278        }
1279        Expression::Subquery(subquery) => {
1280            // Process the inner query
1281            collect_nested_ctes(&subquery.this, collected, has_recursive, false);
1282        }
1283        Expression::Alias(alias) => {
1284            collect_nested_ctes(&alias.this, collected, has_recursive, false);
1285        }
1286        // Add more expression types as needed
1287        _ => {}
1288    }
1289}
1290
1291/// Inline window definitions from WINDOW clause.
1292///
1293/// Some dialects don't support named windows. This transform inlines them:
1294///
1295/// ```sql
1296/// SELECT SUM(a) OVER w FROM t WINDOW w AS (PARTITION BY b)
1297/// ```
1298///
1299/// To:
1300///
1301/// ```sql
1302/// SELECT SUM(a) OVER (PARTITION BY b) FROM t
1303/// ```
1304///
1305/// Reference: `transforms.py:975-1003`
1306pub fn eliminate_window_clause(expr: Expression) -> Result<Expression> {
1307    match expr {
1308        Expression::Select(mut select) => {
1309            if let Some(named_windows) = select.windows.take() {
1310                // Build a map of window name -> window spec
1311                let window_map: std::collections::HashMap<String, &Over> = named_windows
1312                    .iter()
1313                    .map(|nw| (nw.name.name.to_lowercase(), &nw.spec))
1314                    .collect();
1315
1316                // Inline window references in the select expressions
1317                select.expressions = select
1318                    .expressions
1319                    .into_iter()
1320                    .map(|e| inline_window_refs(e, &window_map))
1321                    .collect();
1322            }
1323            Ok(Expression::Select(select))
1324        }
1325        other => Ok(other),
1326    }
1327}
1328
1329/// Helper function to inline window references in an expression
1330fn inline_window_refs(
1331    expr: Expression,
1332    window_map: &std::collections::HashMap<String, &Over>,
1333) -> Expression {
1334    match expr {
1335        Expression::WindowFunction(mut wf) => {
1336            // Check if this window references a named window
1337            if let Some(ref name) = wf.over.window_name {
1338                let key = name.name.to_lowercase();
1339                if let Some(named_spec) = window_map.get(&key) {
1340                    // Inherit properties from the named window
1341                    if wf.over.partition_by.is_empty() && !named_spec.partition_by.is_empty() {
1342                        wf.over.partition_by = named_spec.partition_by.clone();
1343                    }
1344                    if wf.over.order_by.is_empty() && !named_spec.order_by.is_empty() {
1345                        wf.over.order_by = named_spec.order_by.clone();
1346                    }
1347                    if wf.over.frame.is_none() && named_spec.frame.is_some() {
1348                        wf.over.frame = named_spec.frame.clone();
1349                    }
1350                    // Clear the window name reference
1351                    wf.over.window_name = None;
1352                }
1353            }
1354            Expression::WindowFunction(wf)
1355        }
1356        Expression::Alias(mut alias) => {
1357            // Recurse into aliased expressions
1358            alias.this = inline_window_refs(alias.this, window_map);
1359            Expression::Alias(alias)
1360        }
1361        // For a complete implementation, we would need to recursively visit all expressions
1362        // that can contain window functions (CASE, subqueries, etc.)
1363        other => other,
1364    }
1365}
1366
1367/// Eliminate Oracle-style (+) join marks by converting to standard JOINs.
1368///
1369/// Oracle uses (+) syntax for outer joins:
1370/// ```sql
1371/// SELECT * FROM a, b WHERE a.x = b.x(+)
1372/// ```
1373///
1374/// This is converted to standard LEFT OUTER JOIN:
1375/// ```sql
1376/// SELECT * FROM a LEFT OUTER JOIN b ON a.x = b.x
1377/// ```
1378///
1379/// Reference: `transforms.py:828-945`
1380pub fn eliminate_join_marks(expr: Expression) -> Result<Expression> {
1381    match expr {
1382        Expression::Select(mut select) => {
1383            // Check if there are any join marks in the WHERE clause
1384            let has_join_marks = select.where_clause.as_ref().map_or(false, |w| {
1385                contains_join_mark(&w.this)
1386            });
1387
1388            if !has_join_marks {
1389                return Ok(Expression::Select(select));
1390            }
1391
1392            // Collect tables from FROM clause
1393            let from_tables: Vec<String> = select
1394                .from
1395                .as_ref()
1396                .map(|f| {
1397                    f.expressions
1398                        .iter()
1399                        .filter_map(|e| get_table_name(e))
1400                        .collect()
1401                })
1402                .unwrap_or_default();
1403
1404            // Extract join conditions and their marked tables from WHERE
1405            let mut join_conditions: std::collections::HashMap<String, Vec<Expression>> =
1406                std::collections::HashMap::new();
1407            let mut remaining_conditions: Vec<Expression> = Vec::new();
1408
1409            if let Some(ref where_clause) = select.where_clause {
1410                extract_join_mark_conditions(
1411                    &where_clause.this,
1412                    &mut join_conditions,
1413                    &mut remaining_conditions,
1414                );
1415            }
1416
1417            // Build new JOINs for each marked table
1418            let mut new_joins = select.joins.clone();
1419            for (table_name, conditions) in join_conditions {
1420                // Find if this table is in FROM or existing JOINs
1421                let table_in_from = from_tables.contains(&table_name);
1422
1423                if table_in_from && !conditions.is_empty() {
1424                    // Create LEFT JOIN with combined conditions
1425                    let combined_condition = conditions.into_iter().reduce(|a, b| {
1426                        Expression::And(Box::new(BinaryOp {
1427                            left: a,
1428                            right: b,
1429                            left_comments: vec![],
1430                            operator_comments: vec![],
1431                            trailing_comments: vec![],
1432                        }))
1433                    });
1434
1435                    // Find the table in FROM and move it to a JOIN
1436                    if let Some(ref mut from) = select.from {
1437                        if let Some(pos) = from.expressions.iter().position(|e| {
1438                            get_table_name(e).map_or(false, |n| n == table_name)
1439                        }) {
1440                            if from.expressions.len() > 1 {
1441                                let join_table = from.expressions.remove(pos);
1442                                new_joins.push(crate::expressions::Join {
1443                                    this: join_table,
1444                                    kind: JoinKind::Left,
1445                                    on: combined_condition,
1446                                    using: vec![],
1447                                    use_inner_keyword: false,
1448                                    use_outer_keyword: true,
1449                                    deferred_condition: false,
1450                                    join_hint: None,
1451                                    match_condition: None,
1452                                    pivots: Vec::new(),
1453                                });
1454                            }
1455                        }
1456                    }
1457                }
1458            }
1459
1460            select.joins = new_joins;
1461
1462            // Update WHERE with remaining conditions
1463            if remaining_conditions.is_empty() {
1464                select.where_clause = None;
1465            } else {
1466                let combined = remaining_conditions.into_iter().reduce(|a, b| {
1467                    Expression::And(Box::new(BinaryOp {
1468                        left: a,
1469                        right: b,
1470                        left_comments: vec![],
1471                        operator_comments: vec![],
1472                        trailing_comments: vec![],
1473                    }))
1474                });
1475                select.where_clause = combined.map(|c| Where { this: c });
1476            }
1477
1478            // Clear join marks from all columns
1479            clear_join_marks(&mut Expression::Select(select.clone()));
1480
1481            Ok(Expression::Select(select))
1482        }
1483        other => Ok(other),
1484    }
1485}
1486
1487/// Check if an expression contains any columns with join marks
1488fn contains_join_mark(expr: &Expression) -> bool {
1489    match expr {
1490        Expression::Column(col) => col.join_mark,
1491        Expression::And(op) | Expression::Or(op) => {
1492            contains_join_mark(&op.left) || contains_join_mark(&op.right)
1493        }
1494        Expression::Eq(op) | Expression::Neq(op) | Expression::Lt(op)
1495        | Expression::Lte(op) | Expression::Gt(op) | Expression::Gte(op) => {
1496            contains_join_mark(&op.left) || contains_join_mark(&op.right)
1497        }
1498        Expression::Not(op) => contains_join_mark(&op.this),
1499        _ => false,
1500    }
1501}
1502
1503/// Get table name from a table expression
1504fn get_table_name(expr: &Expression) -> Option<String> {
1505    match expr {
1506        Expression::Table(t) => Some(t.name.name.clone()),
1507        Expression::Alias(a) => Some(a.alias.name.clone()),
1508        _ => None,
1509    }
1510}
1511
1512/// Extract join mark conditions from WHERE clause
1513fn extract_join_mark_conditions(
1514    expr: &Expression,
1515    join_conditions: &mut std::collections::HashMap<String, Vec<Expression>>,
1516    remaining: &mut Vec<Expression>,
1517) {
1518    match expr {
1519        Expression::And(op) => {
1520            extract_join_mark_conditions(&op.left, join_conditions, remaining);
1521            extract_join_mark_conditions(&op.right, join_conditions, remaining);
1522        }
1523        _ => {
1524            if let Some(table) = get_join_mark_table(expr) {
1525                join_conditions
1526                    .entry(table)
1527                    .or_insert_with(Vec::new)
1528                    .push(expr.clone());
1529            } else {
1530                remaining.push(expr.clone());
1531            }
1532        }
1533    }
1534}
1535
1536/// Get the table name of a column with join mark in an expression
1537fn get_join_mark_table(expr: &Expression) -> Option<String> {
1538    match expr {
1539        Expression::Eq(op) | Expression::Neq(op) | Expression::Lt(op)
1540        | Expression::Lte(op) | Expression::Gt(op) | Expression::Gte(op) => {
1541            // Check both sides for join mark columns
1542            if let Expression::Column(col) = &op.left {
1543                if col.join_mark {
1544                    return col.table.as_ref().map(|t| t.name.clone());
1545                }
1546            }
1547            if let Expression::Column(col) = &op.right {
1548                if col.join_mark {
1549                    return col.table.as_ref().map(|t| t.name.clone());
1550                }
1551            }
1552            None
1553        }
1554        _ => None,
1555    }
1556}
1557
1558/// Clear join marks from all columns in an expression
1559fn clear_join_marks(expr: &mut Expression) {
1560    match expr {
1561        Expression::Column(col) => col.join_mark = false,
1562        Expression::Select(select) => {
1563            if let Some(ref mut w) = select.where_clause {
1564                clear_join_marks(&mut w.this);
1565            }
1566            for sel_expr in &mut select.expressions {
1567                clear_join_marks(sel_expr);
1568            }
1569        }
1570        Expression::And(op) | Expression::Or(op) => {
1571            clear_join_marks(&mut op.left);
1572            clear_join_marks(&mut op.right);
1573        }
1574        Expression::Eq(op) | Expression::Neq(op) | Expression::Lt(op)
1575        | Expression::Lte(op) | Expression::Gt(op) | Expression::Gte(op) => {
1576            clear_join_marks(&mut op.left);
1577            clear_join_marks(&mut op.right);
1578        }
1579        _ => {}
1580    }
1581}
1582
1583/// Add column names to recursive CTE definitions.
1584///
1585/// Uses projection output names in recursive CTE definitions to define the CTEs' columns.
1586/// This is required by some dialects that need explicit column names in recursive CTEs.
1587///
1588/// Reference: `transforms.py:576-592`
1589pub fn add_recursive_cte_column_names(expr: Expression) -> Result<Expression> {
1590    match expr {
1591        Expression::Select(mut select) => {
1592            if let Some(ref mut with) = select.with {
1593                if with.recursive {
1594                    let mut counter = 0;
1595                    for cte in &mut with.ctes {
1596                        if cte.columns.is_empty() {
1597                            // Try to get column names from the CTE's SELECT
1598                            if let Expression::Select(ref cte_select) = cte.this {
1599                                let names: Vec<Identifier> = cte_select
1600                                    .expressions
1601                                    .iter()
1602                                    .map(|e| match e {
1603                                        Expression::Alias(a) => a.alias.clone(),
1604                                        Expression::Column(c) => c.name.clone(),
1605                                        _ => {
1606                                            counter += 1;
1607                                            Identifier::new(format!("_c_{}", counter))
1608                                        }
1609                                    })
1610                                    .collect();
1611                                cte.columns = names;
1612                            }
1613                        }
1614                    }
1615                }
1616            }
1617            Ok(Expression::Select(select))
1618        }
1619        other => Ok(other),
1620    }
1621}
1622
1623/// Convert epoch string in CAST to timestamp literal.
1624///
1625/// Replaces `CAST('epoch' AS TIMESTAMP)` with `CAST('1970-01-01 00:00:00' AS TIMESTAMP)`
1626/// for dialects that don't support the 'epoch' keyword.
1627///
1628/// Reference: `transforms.py:595-604`
1629pub fn epoch_cast_to_ts(expr: Expression) -> Result<Expression> {
1630    match expr {
1631        Expression::Cast(mut cast) => {
1632            if let Expression::Literal(Literal::String(ref s)) = cast.this {
1633                if s.to_lowercase() == "epoch" {
1634                    if is_temporal_type(&cast.to) {
1635                        cast.this =
1636                            Expression::Literal(Literal::String("1970-01-01 00:00:00".to_string()));
1637                    }
1638                }
1639            }
1640            Ok(Expression::Cast(cast))
1641        }
1642        Expression::TryCast(mut try_cast) => {
1643            if let Expression::Literal(Literal::String(ref s)) = try_cast.this {
1644                if s.to_lowercase() == "epoch" {
1645                    if is_temporal_type(&try_cast.to) {
1646                        try_cast.this =
1647                            Expression::Literal(Literal::String("1970-01-01 00:00:00".to_string()));
1648                    }
1649                }
1650            }
1651            Ok(Expression::TryCast(try_cast))
1652        }
1653        other => Ok(other),
1654    }
1655}
1656
1657/// Check if a DataType is a temporal type (DATE, TIMESTAMP, etc.)
1658fn is_temporal_type(dt: &DataType) -> bool {
1659    matches!(
1660        dt,
1661        DataType::Date | DataType::Timestamp { .. } | DataType::Time { .. }
1662    )
1663}
1664
1665/// Ensure boolean values in conditions.
1666///
1667/// Converts numeric values used in conditions into explicit boolean expressions.
1668/// For dialects that require explicit booleans in WHERE clauses.
1669///
1670/// Converts:
1671/// ```sql
1672/// WHERE column
1673/// ```
1674/// To:
1675/// ```sql
1676/// WHERE column <> 0
1677/// ```
1678///
1679/// And:
1680/// ```sql
1681/// WHERE 1
1682/// ```
1683/// To:
1684/// ```sql
1685/// WHERE 1 <> 0
1686/// ```
1687///
1688/// Reference: `transforms.py:703-721`
1689pub fn ensure_bools(expr: Expression) -> Result<Expression> {
1690    // First, recursively process Case WHEN conditions throughout the expression tree
1691    let expr = ensure_bools_in_case(expr);
1692    match expr {
1693        Expression::Select(mut select) => {
1694            // Transform WHERE clause condition
1695            if let Some(ref mut where_clause) = select.where_clause {
1696                where_clause.this = ensure_bool_condition(where_clause.this.clone());
1697            }
1698            // Transform HAVING clause condition
1699            if let Some(ref mut having) = select.having {
1700                having.this = ensure_bool_condition(having.this.clone());
1701            }
1702            Ok(Expression::Select(select))
1703        }
1704        // Top-level AND/OR/NOT expressions also need ensure_bools processing
1705        Expression::And(_) | Expression::Or(_) | Expression::Not(_) => {
1706            Ok(ensure_bool_condition(expr))
1707        }
1708        other => Ok(other),
1709    }
1710}
1711
1712/// Recursively walk the expression tree to find Case expressions and apply
1713/// ensure_bool_condition to their WHEN conditions. This ensures that
1714/// `CASE WHEN TRUE` becomes `CASE WHEN (1 = 1)` etc.
1715fn ensure_bools_in_case(expr: Expression) -> Expression {
1716    match expr {
1717        Expression::Case(mut case) => {
1718            case.whens = case.whens.into_iter().map(|(condition, result)| {
1719                let new_condition = ensure_bool_condition(ensure_bools_in_case(condition));
1720                let new_result = ensure_bools_in_case(result);
1721                (new_condition, new_result)
1722            }).collect();
1723            if let Some(else_expr) = case.else_ {
1724                case.else_ = Some(ensure_bools_in_case(else_expr));
1725            }
1726            Expression::Case(Box::new(*case))
1727        }
1728        Expression::Select(mut select) => {
1729            // Recursively process expressions in the SELECT list
1730            select.expressions = select.expressions.into_iter().map(ensure_bools_in_case).collect();
1731            // Process WHERE/HAVING are handled by ensure_bools main function
1732            Expression::Select(select)
1733        }
1734        Expression::Alias(mut alias) => {
1735            alias.this = ensure_bools_in_case(alias.this);
1736            Expression::Alias(alias)
1737        }
1738        Expression::Paren(mut paren) => {
1739            paren.this = ensure_bools_in_case(paren.this);
1740            Expression::Paren(paren)
1741        }
1742        other => other,
1743    }
1744}
1745
1746/// Helper to check if an expression is inherently boolean (returns a boolean value).
1747/// Inherently boolean expressions include comparisons, predicates, logical operators, etc.
1748fn is_boolean_expression(expr: &Expression) -> bool {
1749    matches!(
1750        expr,
1751        Expression::Eq(_)
1752            | Expression::Neq(_)
1753            | Expression::Lt(_)
1754            | Expression::Lte(_)
1755            | Expression::Gt(_)
1756            | Expression::Gte(_)
1757            | Expression::Is(_)
1758            | Expression::IsNull(_)
1759            | Expression::IsTrue(_)
1760            | Expression::IsFalse(_)
1761            | Expression::Like(_)
1762            | Expression::ILike(_)
1763            | Expression::SimilarTo(_)
1764            | Expression::Glob(_)
1765            | Expression::RegexpLike(_)
1766            | Expression::In(_)
1767            | Expression::Between(_)
1768            | Expression::Exists(_)
1769            | Expression::And(_)
1770            | Expression::Or(_)
1771            | Expression::Not(_)
1772            | Expression::Any(_)
1773            | Expression::All(_)
1774            | Expression::EqualNull(_)
1775    )
1776}
1777
1778/// Helper to wrap a non-boolean expression with `<> 0`
1779fn wrap_neq_zero(expr: Expression) -> Expression {
1780    Expression::Neq(Box::new(BinaryOp {
1781        left: expr,
1782        right: Expression::Literal(Literal::Number("0".to_string())),
1783        left_comments: vec![],
1784        operator_comments: vec![],
1785        trailing_comments: vec![],
1786    }))
1787}
1788
1789/// Helper to convert a condition expression to ensure it's boolean.
1790///
1791/// In TSQL, conditions in WHERE/HAVING must be boolean expressions.
1792/// Non-boolean expressions (columns, literals, casts, function calls, etc.)
1793/// are wrapped with `<> 0`. Boolean literals are converted to `(1 = 1)` or `(1 = 0)`.
1794fn ensure_bool_condition(expr: Expression) -> Expression {
1795    match expr {
1796        // For AND/OR, recursively process children
1797        Expression::And(op) => {
1798            let new_op = BinaryOp {
1799                left: ensure_bool_condition(op.left.clone()),
1800                right: ensure_bool_condition(op.right.clone()),
1801                left_comments: op.left_comments.clone(),
1802                operator_comments: op.operator_comments.clone(),
1803                trailing_comments: op.trailing_comments.clone(),
1804            };
1805            Expression::And(Box::new(new_op))
1806        }
1807        Expression::Or(op) => {
1808            let new_op = BinaryOp {
1809                left: ensure_bool_condition(op.left.clone()),
1810                right: ensure_bool_condition(op.right.clone()),
1811                left_comments: op.left_comments.clone(),
1812                operator_comments: op.operator_comments.clone(),
1813                trailing_comments: op.trailing_comments.clone(),
1814            };
1815            Expression::Or(Box::new(new_op))
1816        }
1817        // For NOT, recursively process the inner expression
1818        Expression::Not(op) => Expression::Not(Box::new(crate::expressions::UnaryOp {
1819            this: ensure_bool_condition(op.this.clone()),
1820        })),
1821        // For Paren, recurse into inner expression
1822        Expression::Paren(paren) => Expression::Paren(Box::new(crate::expressions::Paren {
1823            this: ensure_bool_condition(paren.this.clone()),
1824            trailing_comments: paren.trailing_comments.clone(),
1825        })),
1826        // Boolean literals: true -> (1 = 1), false -> (1 = 0)
1827        Expression::Boolean(BooleanLiteral { value: true }) => {
1828            Expression::Paren(Box::new(crate::expressions::Paren {
1829                this: Expression::Eq(Box::new(BinaryOp {
1830                    left: Expression::Literal(Literal::Number("1".to_string())),
1831                    right: Expression::Literal(Literal::Number("1".to_string())),
1832                    left_comments: vec![],
1833                    operator_comments: vec![],
1834                    trailing_comments: vec![],
1835                })),
1836                trailing_comments: vec![],
1837            }))
1838        }
1839        Expression::Boolean(BooleanLiteral { value: false }) => {
1840            Expression::Paren(Box::new(crate::expressions::Paren {
1841                this: Expression::Eq(Box::new(BinaryOp {
1842                    left: Expression::Literal(Literal::Number("1".to_string())),
1843                    right: Expression::Literal(Literal::Number("0".to_string())),
1844                    left_comments: vec![],
1845                    operator_comments: vec![],
1846                    trailing_comments: vec![],
1847                })),
1848                trailing_comments: vec![],
1849            }))
1850        }
1851        // Already boolean expressions pass through unchanged
1852        ref e if is_boolean_expression(e) => expr,
1853        // Everything else (Column, Identifier, Cast, Literal::Number, function calls, etc.)
1854        // gets wrapped with <> 0
1855        _ => wrap_neq_zero(expr),
1856    }
1857}
1858
1859/// Remove table qualifiers from column references.
1860///
1861/// Converts `table.column` to just `column` throughout the expression tree.
1862///
1863/// Reference: `transforms.py:724-730`
1864pub fn unqualify_columns(expr: Expression) -> Result<Expression> {
1865    Ok(unqualify_columns_recursive(expr))
1866}
1867
1868/// Recursively remove table qualifiers from column references
1869fn unqualify_columns_recursive(expr: Expression) -> Expression {
1870    match expr {
1871        Expression::Column(mut col) => {
1872            col.table = None;
1873            Expression::Column(col)
1874        }
1875        Expression::Select(mut select) => {
1876            select.expressions = select
1877                .expressions
1878                .into_iter()
1879                .map(unqualify_columns_recursive)
1880                .collect();
1881            if let Some(ref mut where_clause) = select.where_clause {
1882                where_clause.this = unqualify_columns_recursive(where_clause.this.clone());
1883            }
1884            if let Some(ref mut having) = select.having {
1885                having.this = unqualify_columns_recursive(having.this.clone());
1886            }
1887            if let Some(ref mut group_by) = select.group_by {
1888                group_by.expressions = group_by
1889                    .expressions
1890                    .iter()
1891                    .cloned()
1892                    .map(unqualify_columns_recursive)
1893                    .collect();
1894            }
1895            if let Some(ref mut order_by) = select.order_by {
1896                order_by.expressions = order_by
1897                    .expressions
1898                    .iter()
1899                    .map(|o| crate::expressions::Ordered {
1900                        this: unqualify_columns_recursive(o.this.clone()),
1901                        desc: o.desc,
1902                        nulls_first: o.nulls_first,
1903                        explicit_asc: o.explicit_asc,
1904                        with_fill: o.with_fill.clone(),
1905                    })
1906                    .collect();
1907            }
1908            for join in &mut select.joins {
1909                if let Some(ref mut on) = join.on {
1910                    *on = unqualify_columns_recursive(on.clone());
1911                }
1912            }
1913            Expression::Select(select)
1914        }
1915        Expression::Alias(mut alias) => {
1916            alias.this = unqualify_columns_recursive(alias.this);
1917            Expression::Alias(alias)
1918        }
1919        // Binary operations
1920        Expression::And(op) => Expression::And(Box::new(unqualify_binary_op(*op))),
1921        Expression::Or(op) => Expression::Or(Box::new(unqualify_binary_op(*op))),
1922        Expression::Eq(op) => Expression::Eq(Box::new(unqualify_binary_op(*op))),
1923        Expression::Neq(op) => Expression::Neq(Box::new(unqualify_binary_op(*op))),
1924        Expression::Lt(op) => Expression::Lt(Box::new(unqualify_binary_op(*op))),
1925        Expression::Lte(op) => Expression::Lte(Box::new(unqualify_binary_op(*op))),
1926        Expression::Gt(op) => Expression::Gt(Box::new(unqualify_binary_op(*op))),
1927        Expression::Gte(op) => Expression::Gte(Box::new(unqualify_binary_op(*op))),
1928        Expression::Add(op) => Expression::Add(Box::new(unqualify_binary_op(*op))),
1929        Expression::Sub(op) => Expression::Sub(Box::new(unqualify_binary_op(*op))),
1930        Expression::Mul(op) => Expression::Mul(Box::new(unqualify_binary_op(*op))),
1931        Expression::Div(op) => Expression::Div(Box::new(unqualify_binary_op(*op))),
1932        // Functions
1933        Expression::Function(mut func) => {
1934            func.args = func.args.into_iter().map(unqualify_columns_recursive).collect();
1935            Expression::Function(func)
1936        }
1937        Expression::AggregateFunction(mut func) => {
1938            func.args = func.args.into_iter().map(unqualify_columns_recursive).collect();
1939            Expression::AggregateFunction(func)
1940        }
1941        Expression::Case(mut case) => {
1942            case.whens = case
1943                .whens
1944                .into_iter()
1945                .map(|(cond, result)| {
1946                    (
1947                        unqualify_columns_recursive(cond),
1948                        unqualify_columns_recursive(result),
1949                    )
1950                })
1951                .collect();
1952            if let Some(else_expr) = case.else_ {
1953                case.else_ = Some(unqualify_columns_recursive(else_expr));
1954            }
1955            Expression::Case(case)
1956        }
1957        // Other expressions pass through unchanged
1958        other => other,
1959    }
1960}
1961
1962/// Helper to unqualify columns in a binary operation
1963fn unqualify_binary_op(mut op: BinaryOp) -> BinaryOp {
1964    op.left = unqualify_columns_recursive(op.left);
1965    op.right = unqualify_columns_recursive(op.right);
1966    op
1967}
1968
1969/// Convert UNNEST(GENERATE_DATE_ARRAY(...)) to recursive CTE.
1970///
1971/// For dialects that don't support GENERATE_DATE_ARRAY, this converts:
1972/// ```sql
1973/// SELECT * FROM UNNEST(GENERATE_DATE_ARRAY('2024-01-01', '2024-01-31', INTERVAL 1 DAY)) AS d(date_value)
1974/// ```
1975/// To a recursive CTE:
1976/// ```sql
1977/// WITH RECURSIVE _generated_dates(date_value) AS (
1978///     SELECT CAST('2024-01-01' AS DATE) AS date_value
1979///     UNION ALL
1980///     SELECT CAST(DATE_ADD(date_value, 1, DAY) AS DATE)
1981///     FROM _generated_dates
1982///     WHERE CAST(DATE_ADD(date_value, 1, DAY) AS DATE) <= CAST('2024-01-31' AS DATE)
1983/// )
1984/// SELECT date_value FROM _generated_dates
1985/// ```
1986///
1987/// Reference: `transforms.py:68-122`
1988pub fn unnest_generate_date_array_using_recursive_cte(expr: Expression) -> Result<Expression> {
1989    match expr {
1990        Expression::Select(mut select) => {
1991            let mut cte_count = 0;
1992            let mut new_ctes: Vec<crate::expressions::Cte> = Vec::new();
1993
1994            // Process FROM clause
1995            if let Some(ref mut from) = select.from {
1996                for table_expr in &mut from.expressions {
1997                    if let Some((cte, replacement)) = try_convert_generate_date_array(table_expr, &mut cte_count) {
1998                        new_ctes.push(cte);
1999                        *table_expr = replacement;
2000                    }
2001                }
2002            }
2003
2004            // Process JOINs
2005            for join in &mut select.joins {
2006                if let Some((cte, replacement)) = try_convert_generate_date_array(&join.this, &mut cte_count) {
2007                    new_ctes.push(cte);
2008                    join.this = replacement;
2009                }
2010            }
2011
2012            // Add collected CTEs to the WITH clause
2013            if !new_ctes.is_empty() {
2014                let with_clause = select.with.get_or_insert_with(|| crate::expressions::With {
2015                    ctes: Vec::new(),
2016                    recursive: true, // Recursive CTEs
2017                    leading_comments: vec![],
2018                    search: None,
2019                });
2020                with_clause.recursive = true;
2021
2022                // Prepend new CTEs
2023                let mut all_ctes = new_ctes;
2024                all_ctes.append(&mut with_clause.ctes);
2025                with_clause.ctes = all_ctes;
2026            }
2027
2028            Ok(Expression::Select(select))
2029        }
2030        other => Ok(other),
2031    }
2032}
2033
2034/// Try to convert an UNNEST(GENERATE_DATE_ARRAY(...)) to a recursive CTE reference
2035fn try_convert_generate_date_array(
2036    expr: &Expression,
2037    cte_count: &mut usize,
2038) -> Option<(crate::expressions::Cte, Expression)> {
2039    // Look for UNNEST containing GENERATE_DATE_ARRAY
2040    if let Expression::Unnest(unnest) = expr {
2041        if let Expression::GenerateDateArray(gda) = &unnest.this {
2042            // Extract start, end, step
2043            let start = gda.start.as_ref()?;
2044            let end = gda.end.as_ref()?;
2045            let step = gda.step.as_ref();
2046
2047            // Generate CTE name
2048            let cte_name = if *cte_count == 0 {
2049                "_generated_dates".to_string()
2050            } else {
2051                format!("_generated_dates_{}", cte_count)
2052            };
2053            *cte_count += 1;
2054
2055            let column_name = Identifier::new("date_value".to_string());
2056
2057            // Build base case: SELECT CAST(start AS DATE) AS date_value
2058            let base_select = Select {
2059                expressions: vec![Expression::Alias(Box::new(crate::expressions::Alias {
2060                    this: Expression::Cast(Box::new(Cast {
2061                        this: (**start).clone(),
2062                        to: DataType::Date,
2063                        trailing_comments: vec![],
2064                        double_colon_syntax: false,
2065                        format: None,
2066                        default: None,
2067                    })),
2068                    alias: column_name.clone(),
2069                    column_aliases: vec![],
2070                    pre_alias_comments: vec![],
2071                    trailing_comments: vec![],
2072                }))],
2073                ..Select::new()
2074            };
2075
2076            // Build recursive case: DATE_ADD(date_value, step) from CTE where result <= end
2077            let date_add_expr = Expression::Function(Box::new(crate::expressions::Function {
2078                name: "DATE_ADD".to_string(),
2079                args: vec![
2080                    Expression::Column(crate::expressions::Column {
2081                        name: column_name.clone(),
2082                        table: None,
2083                        join_mark: false,
2084                        trailing_comments: vec![],
2085                    }),
2086                    step.map(|s| (**s).clone()).unwrap_or_else(||
2087                        Expression::Interval(Box::new(crate::expressions::Interval {
2088                            this: Some(Expression::Literal(Literal::Number("1".to_string()))),
2089                            unit: Some(crate::expressions::IntervalUnitSpec::Simple {
2090                                unit: crate::expressions::IntervalUnit::Day,
2091                                use_plural: false,
2092                            }),
2093                        }))
2094                    ),
2095                ],
2096                distinct: false,
2097                trailing_comments: vec![],
2098                use_bracket_syntax: false,
2099                no_parens: false,
2100                quoted: false,
2101            }));
2102
2103            let cast_date_add = Expression::Cast(Box::new(Cast {
2104                this: date_add_expr.clone(),
2105                to: DataType::Date,
2106                trailing_comments: vec![],
2107                double_colon_syntax: false,
2108                format: None,
2109                default: None,
2110            }));
2111
2112            let recursive_select = Select {
2113                expressions: vec![cast_date_add.clone()],
2114                from: Some(From {
2115                    expressions: vec![Expression::Table(crate::expressions::TableRef::new(&cte_name))],
2116                }),
2117                where_clause: Some(Where {
2118                    this: Expression::Lte(Box::new(BinaryOp {
2119                        left: cast_date_add,
2120                        right: Expression::Cast(Box::new(Cast {
2121                            this: (**end).clone(),
2122                            to: DataType::Date,
2123                            trailing_comments: vec![],
2124                            double_colon_syntax: false,
2125                            format: None,
2126                            default: None,
2127                        })),
2128                        left_comments: vec![],
2129                        operator_comments: vec![],
2130                        trailing_comments: vec![],
2131                    })),
2132                }),
2133                ..Select::new()
2134            };
2135
2136            // Build UNION ALL of base and recursive
2137            let union = crate::expressions::Union {
2138                left: Expression::Select(Box::new(base_select)),
2139                right: Expression::Select(Box::new(recursive_select)),
2140                all: true, // UNION ALL
2141                distinct: false,
2142                with: None,
2143                order_by: None,
2144                limit: None,
2145                offset: None,
2146                distribute_by: None,
2147                sort_by: None,
2148                cluster_by: None,
2149                by_name: false,
2150                side: None,
2151                kind: None,
2152                corresponding: false,
2153                strict: false,
2154                on_columns: Vec::new(),
2155            };
2156
2157            // Create CTE
2158            let cte = crate::expressions::Cte {
2159                this: Expression::Union(Box::new(union)),
2160                alias: Identifier::new(cte_name.clone()),
2161                columns: vec![column_name.clone()],
2162                materialized: None,
2163                key_expressions: Vec::new(),
2164                alias_first: true,
2165            };
2166
2167            // Create replacement: SELECT date_value FROM cte_name
2168            let replacement_select = Select {
2169                expressions: vec![Expression::Column(crate::expressions::Column {
2170                    name: column_name,
2171                    table: None,
2172                    join_mark: false,
2173                    trailing_comments: vec![],
2174                })],
2175                from: Some(From {
2176                    expressions: vec![Expression::Table(crate::expressions::TableRef::new(&cte_name))],
2177                }),
2178                ..Select::new()
2179            };
2180
2181            let replacement = Expression::Subquery(Box::new(Subquery {
2182                this: Expression::Select(Box::new(replacement_select)),
2183                alias: Some(Identifier::new(cte_name)),
2184                column_aliases: vec![],
2185                order_by: None,
2186                limit: None,
2187                offset: None,
2188                distribute_by: None,
2189                sort_by: None,
2190                cluster_by: None,
2191                lateral: false,
2192                modifiers_inside: false,
2193                trailing_comments: vec![],
2194            }));
2195
2196            return Some((cte, replacement));
2197        }
2198    }
2199
2200    // Also check for aliased UNNEST
2201    if let Expression::Alias(alias) = expr {
2202        if let Some((cte, replacement)) = try_convert_generate_date_array(&alias.this, cte_count) {
2203            let new_alias = Expression::Alias(Box::new(crate::expressions::Alias {
2204                this: replacement,
2205                alias: alias.alias.clone(),
2206                column_aliases: alias.column_aliases.clone(),
2207                pre_alias_comments: alias.pre_alias_comments.clone(),
2208                trailing_comments: alias.trailing_comments.clone(),
2209            }));
2210            return Some((cte, new_alias));
2211        }
2212    }
2213
2214    None
2215}
2216
2217/// Convert ILIKE to LOWER(x) LIKE LOWER(y).
2218///
2219/// For dialects that don't support ILIKE (case-insensitive LIKE), this converts:
2220/// ```sql
2221/// SELECT * FROM t WHERE x ILIKE '%pattern%'
2222/// ```
2223/// To:
2224/// ```sql
2225/// SELECT * FROM t WHERE LOWER(x) LIKE LOWER('%pattern%')
2226/// ```
2227///
2228/// Reference: `generator.py:no_ilike_sql()`
2229pub fn no_ilike_sql(expr: Expression) -> Result<Expression> {
2230    match expr {
2231        Expression::ILike(ilike) => {
2232            // Create LOWER(left) LIKE LOWER(right)
2233            let lower_left = Expression::Function(Box::new(crate::expressions::Function {
2234                name: "LOWER".to_string(),
2235                args: vec![ilike.left],
2236                distinct: false,
2237                trailing_comments: vec![],
2238                use_bracket_syntax: false,
2239                no_parens: false,
2240                quoted: false,
2241            }));
2242
2243            let lower_right = Expression::Function(Box::new(crate::expressions::Function {
2244                name: "LOWER".to_string(),
2245                args: vec![ilike.right],
2246                distinct: false,
2247                trailing_comments: vec![],
2248                use_bracket_syntax: false,
2249                no_parens: false,
2250                quoted: false,
2251            }));
2252
2253            Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
2254                left: lower_left,
2255                right: lower_right,
2256                escape: ilike.escape,
2257                quantifier: ilike.quantifier,
2258            })))
2259        }
2260        other => Ok(other),
2261    }
2262}
2263
2264/// Convert TryCast to Cast.
2265///
2266/// For dialects that don't support TRY_CAST (safe cast that returns NULL on error),
2267/// this converts TRY_CAST to regular CAST. Note: This may cause runtime errors
2268/// for invalid casts that TRY_CAST would handle gracefully.
2269///
2270/// Reference: `generator.py:no_trycast_sql()`
2271pub fn no_trycast_sql(expr: Expression) -> Result<Expression> {
2272    match expr {
2273        Expression::TryCast(try_cast) => {
2274            Ok(Expression::Cast(try_cast))
2275        }
2276        other => Ok(other),
2277    }
2278}
2279
2280/// Convert SafeCast to Cast.
2281///
2282/// For dialects that don't support SAFE_CAST (BigQuery's safe cast syntax),
2283/// this converts SAFE_CAST to regular CAST.
2284pub fn no_safe_cast_sql(expr: Expression) -> Result<Expression> {
2285    match expr {
2286        Expression::SafeCast(safe_cast) => {
2287            Ok(Expression::Cast(safe_cast))
2288        }
2289        other => Ok(other),
2290    }
2291}
2292
2293/// Convert COMMENT ON statements to inline comments.
2294///
2295/// For dialects that don't support COMMENT ON syntax, this can transform
2296/// comment statements into inline comments or skip them entirely.
2297///
2298/// Reference: `generator.py:no_comment_column_constraint_sql()`
2299pub fn no_comment_column_constraint(expr: Expression) -> Result<Expression> {
2300    // For now, just pass through - comment handling is done in generator
2301    Ok(expr)
2302}
2303
2304/// Convert TABLE GENERATE_SERIES to UNNEST(GENERATE_SERIES(...)).
2305///
2306/// Some dialects use GENERATE_SERIES as a table-valued function, while others
2307/// prefer the UNNEST syntax. This converts:
2308/// ```sql
2309/// SELECT * FROM GENERATE_SERIES(1, 10) AS t(n)
2310/// ```
2311/// To:
2312/// ```sql
2313/// SELECT * FROM UNNEST(GENERATE_SERIES(1, 10)) AS _u(n)
2314/// ```
2315///
2316/// Reference: `transforms.py:125-135`
2317pub fn unnest_generate_series(expr: Expression) -> Result<Expression> {
2318    // Convert TABLE GENERATE_SERIES to UNNEST(GENERATE_SERIES(...))
2319    // This handles the case where GENERATE_SERIES is used as a table-valued function
2320    match expr {
2321        Expression::Table(ref table) => {
2322            // Check if the table name matches GENERATE_SERIES pattern
2323            // In practice, this would be Expression::GenerateSeries wrapped in a Table context
2324            if table.name.name.to_uppercase() == "GENERATE_SERIES" {
2325                // Create UNNEST wrapper
2326                let unnest = Expression::Unnest(Box::new(UnnestFunc {
2327                    this: expr.clone(),
2328                    expressions: Vec::new(),
2329                    with_ordinality: false,
2330                    alias: None,
2331                    offset_alias: None,
2332                }));
2333
2334                // If there's an alias, wrap in alias
2335                return Ok(Expression::Alias(Box::new(crate::expressions::Alias {
2336                    this: unnest,
2337                    alias: Identifier::new("_u".to_string()),
2338                    column_aliases: vec![],
2339                    pre_alias_comments: vec![],
2340                    trailing_comments: vec![],
2341                })));
2342            }
2343            Ok(expr)
2344        }
2345        Expression::GenerateSeries(gs) => {
2346            // Wrap GenerateSeries directly in UNNEST
2347            let unnest = Expression::Unnest(Box::new(UnnestFunc {
2348                this: Expression::GenerateSeries(gs),
2349                expressions: Vec::new(),
2350                with_ordinality: false,
2351                alias: None,
2352                offset_alias: None,
2353            }));
2354            Ok(unnest)
2355        }
2356        other => Ok(other),
2357    }
2358}
2359
2360/// Expand BETWEEN expressions in DELETE statements to >= AND <=
2361///
2362/// Some dialects (like StarRocks) don't support BETWEEN in DELETE statements
2363/// or prefer the expanded form. This transforms:
2364///   `DELETE FROM t WHERE a BETWEEN b AND c`
2365/// to:
2366///   `DELETE FROM t WHERE a >= b AND a <= c`
2367pub fn expand_between_in_delete(expr: Expression) -> Result<Expression> {
2368    match expr {
2369        Expression::Delete(mut delete) => {
2370            // If there's a WHERE clause, expand any BETWEEN expressions in it
2371            if let Some(ref mut where_clause) = delete.where_clause {
2372                where_clause.this = expand_between_recursive(where_clause.this.clone());
2373            }
2374            Ok(Expression::Delete(delete))
2375        }
2376        other => Ok(other),
2377    }
2378}
2379
2380/// Recursively expand BETWEEN expressions to >= AND <=
2381fn expand_between_recursive(expr: Expression) -> Expression {
2382    match expr {
2383        // Expand: a BETWEEN b AND c -> a >= b AND a <= c
2384        // Expand: a NOT BETWEEN b AND c -> a < b OR a > c
2385        Expression::Between(between) => {
2386            let this = expand_between_recursive(between.this.clone());
2387            let low = expand_between_recursive(between.low);
2388            let high = expand_between_recursive(between.high);
2389
2390            if between.not {
2391                // NOT BETWEEN: a < b OR a > c
2392                Expression::Or(Box::new(BinaryOp::new(
2393                    Expression::Lt(Box::new(BinaryOp::new(this.clone(), low))),
2394                    Expression::Gt(Box::new(BinaryOp::new(this, high))),
2395                )))
2396            } else {
2397                // BETWEEN: a >= b AND a <= c
2398                Expression::And(Box::new(BinaryOp::new(
2399                    Expression::Gte(Box::new(BinaryOp::new(this.clone(), low))),
2400                    Expression::Lte(Box::new(BinaryOp::new(this, high))),
2401                )))
2402            }
2403        }
2404
2405        // Recursively process AND/OR expressions
2406        Expression::And(mut op) => {
2407            op.left = expand_between_recursive(op.left);
2408            op.right = expand_between_recursive(op.right);
2409            Expression::And(op)
2410        }
2411        Expression::Or(mut op) => {
2412            op.left = expand_between_recursive(op.left);
2413            op.right = expand_between_recursive(op.right);
2414            Expression::Or(op)
2415        }
2416        Expression::Not(mut op) => {
2417            op.this = expand_between_recursive(op.this);
2418            Expression::Not(op)
2419        }
2420
2421        // Recursively process parenthesized expressions
2422        Expression::Paren(mut paren) => {
2423            paren.this = expand_between_recursive(paren.this);
2424            Expression::Paren(paren)
2425        }
2426
2427        // Pass through everything else unchanged
2428        other => other,
2429    }
2430}
2431
2432/// Push down CTE column names into SELECT expressions.
2433///
2434/// BigQuery doesn't support column names when defining a CTE, e.g.:
2435/// `WITH vartab(v) AS (SELECT ...)` is not valid.
2436/// Instead, it expects: `WITH vartab AS (SELECT ... AS v)`.
2437///
2438/// This transform removes the CTE column aliases and adds them as
2439/// aliases on the SELECT expressions.
2440pub fn pushdown_cte_column_names(expr: Expression) -> Result<Expression> {
2441    match expr {
2442        Expression::Select(mut select) => {
2443            if let Some(ref mut with) = select.with {
2444                for cte in &mut with.ctes {
2445                    if !cte.columns.is_empty() {
2446                        // Check if the CTE body is a star query - if so, just strip column names
2447                        let is_star = matches!(&cte.this, Expression::Select(s) if
2448                            s.expressions.len() == 1 && matches!(&s.expressions[0], Expression::Star(_)));
2449
2450                        if is_star {
2451                            // Can't push down column names for star queries, just remove them
2452                            cte.columns.clear();
2453                            continue;
2454                        }
2455
2456                        // Extract column names
2457                        let column_names: Vec<Identifier> = cte.columns.drain(..).collect();
2458
2459                        // Push column names down into the SELECT expressions
2460                        if let Expression::Select(ref mut inner_select) = cte.this {
2461                            let new_exprs: Vec<Expression> = inner_select
2462                                .expressions
2463                                .drain(..)
2464                                .zip(column_names.into_iter().chain(std::iter::repeat_with(|| Identifier::new(""))))
2465                                .map(|(expr, col_name)| {
2466                                    if col_name.name.is_empty() {
2467                                        return expr;
2468                                    }
2469                                    // If already aliased, replace the alias
2470                                    match expr {
2471                                        Expression::Alias(mut a) => {
2472                                            a.alias = col_name;
2473                                            Expression::Alias(a)
2474                                        }
2475                                        other => {
2476                                            Expression::Alias(Box::new(crate::expressions::Alias {
2477                                                this: other,
2478                                                alias: col_name,
2479                                                column_aliases: Vec::new(),
2480                                                pre_alias_comments: Vec::new(),
2481                                                trailing_comments: Vec::new(),
2482                                            }))
2483                                        }
2484                                    }
2485                                })
2486                                .collect();
2487                            inner_select.expressions = new_exprs;
2488                        }
2489                    }
2490                }
2491            }
2492            Ok(Expression::Select(select))
2493        }
2494        other => Ok(other),
2495    }
2496}
2497
2498/// Simplify nested parentheses around VALUES in FROM clause.
2499/// Converts `FROM ((VALUES (1)))` to `FROM (VALUES (1))` by stripping redundant wrapping.
2500/// Handles various nesting patterns: Subquery(Paren(Values)), Paren(Paren(Values)), etc.
2501pub fn simplify_nested_paren_values(expr: Expression) -> Result<Expression> {
2502    match expr {
2503        Expression::Select(mut select) => {
2504            if let Some(ref mut from) = select.from {
2505                for from_item in from.expressions.iter_mut() {
2506                    simplify_paren_values_in_from(from_item);
2507                }
2508            }
2509            Ok(Expression::Select(select))
2510        }
2511        other => Ok(other),
2512    }
2513}
2514
2515fn simplify_paren_values_in_from(expr: &mut Expression) {
2516    // Check various patterns and build replacement if needed
2517    let replacement = match expr {
2518        // Subquery(Paren(Values)) -> Subquery with Values directly
2519        Expression::Subquery(ref subquery) => {
2520            if let Expression::Paren(ref paren) = subquery.this {
2521                if matches!(&paren.this, Expression::Values(_)) {
2522                    let mut new_sub = subquery.as_ref().clone();
2523                    new_sub.this = paren.this.clone();
2524                    Some(Expression::Subquery(Box::new(new_sub)))
2525                } else {
2526                    None
2527                }
2528            } else {
2529                None
2530            }
2531        }
2532        // Paren(Subquery(Values)) -> Subquery(Values) - strip the Paren wrapper
2533        // Paren(Paren(Values)) -> Paren(Values) - strip one layer
2534        Expression::Paren(ref outer_paren) => {
2535            if let Expression::Subquery(ref subquery) = outer_paren.this {
2536                // Paren(Subquery(Values)) -> Subquery(Values) - strip outer Paren
2537                if matches!(&subquery.this, Expression::Values(_)) {
2538                    Some(outer_paren.this.clone())
2539                }
2540                // Paren(Subquery(Paren(Values))) -> Subquery(Values)
2541                else if let Expression::Paren(ref paren) = subquery.this {
2542                    if matches!(&paren.this, Expression::Values(_)) {
2543                        let mut new_sub = subquery.as_ref().clone();
2544                        new_sub.this = paren.this.clone();
2545                        Some(Expression::Subquery(Box::new(new_sub)))
2546                    } else {
2547                        None
2548                    }
2549                } else {
2550                    None
2551                }
2552            } else if let Expression::Paren(ref inner_paren) = outer_paren.this {
2553                if matches!(&inner_paren.this, Expression::Values(_)) {
2554                    Some(outer_paren.this.clone())
2555                } else {
2556                    None
2557                }
2558            } else {
2559                None
2560            }
2561        }
2562        _ => None,
2563    };
2564    if let Some(new_expr) = replacement {
2565        *expr = new_expr;
2566    }
2567}
2568
2569/// Add auto-generated table aliases (like `_t0`) for POSEXPLODE/EXPLODE in FROM clause
2570/// when the alias has column_aliases but no alias name.
2571/// This is needed for Spark target: `FROM POSEXPLODE(x) AS (a, b)` -> `FROM POSEXPLODE(x) AS _t0(a, b)`
2572pub fn add_auto_table_alias(expr: Expression) -> Result<Expression> {
2573    match expr {
2574        Expression::Select(mut select) => {
2575            // Process FROM expressions
2576            if let Some(ref mut from) = select.from {
2577                let mut counter = 0usize;
2578                for from_item in from.expressions.iter_mut() {
2579                    add_auto_alias_to_from_item(from_item, &mut counter);
2580                }
2581            }
2582            Ok(Expression::Select(select))
2583        }
2584        other => Ok(other),
2585    }
2586}
2587
2588fn add_auto_alias_to_from_item(expr: &mut Expression, counter: &mut usize) {
2589    use crate::expressions::Identifier;
2590
2591    match expr {
2592        Expression::Alias(ref mut alias) => {
2593            // If the alias name is empty and there are column_aliases, add auto-generated name
2594            if alias.alias.name.is_empty() && !alias.column_aliases.is_empty() {
2595                alias.alias = Identifier::new(format!("_t{}", counter));
2596                *counter += 1;
2597            }
2598        }
2599        _ => {}
2600    }
2601}
2602
2603/// Convert BigQuery-style UNNEST aliases to column-alias format for DuckDB/Presto/Spark.
2604///
2605/// BigQuery uses: `UNNEST(arr) AS x` where x is a column alias.
2606/// DuckDB/Presto/Spark need: `UNNEST(arr) AS _t0(x)` where _t0 is a table alias and x is the column alias.
2607///
2608/// Propagate struct field names from the first named struct in an array to subsequent unnamed structs.
2609///
2610/// In BigQuery, `[STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92)]` means the second struct
2611/// should inherit field names from the first: `[STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score)]`.
2612pub fn propagate_struct_field_names(expr: Expression) -> Result<Expression> {
2613    use crate::dialects::transform_recursive;
2614    transform_recursive(expr, &propagate_struct_names_in_expr)
2615}
2616
2617fn propagate_struct_names_in_expr(expr: Expression) -> Result<Expression> {
2618    use crate::expressions::{Alias, Function, Identifier, ArrayConstructor};
2619
2620    /// Helper to propagate struct field names within an array of expressions
2621    fn propagate_in_elements(elements: &[Expression]) -> Option<Vec<Expression>> {
2622        if elements.len() <= 1 {
2623            return None;
2624        }
2625        // Check if first element is a named STRUCT function
2626        if let Some(Expression::Function(ref first_struct)) = elements.first() {
2627            if first_struct.name.eq_ignore_ascii_case("STRUCT") {
2628                // Extract field names from first struct
2629                let field_names: Vec<Option<String>> = first_struct.args.iter().map(|arg| {
2630                    if let Expression::Alias(a) = arg {
2631                        Some(a.alias.name.clone())
2632                    } else {
2633                        None
2634                    }
2635                }).collect();
2636
2637                // Only propagate if first struct has at least one named field
2638                if field_names.iter().any(|n| n.is_some()) {
2639                    let mut new_elements = Vec::with_capacity(elements.len());
2640                    new_elements.push(elements[0].clone());
2641
2642                    for elem in &elements[1..] {
2643                        if let Expression::Function(ref s) = elem {
2644                            if s.name.eq_ignore_ascii_case("STRUCT") && s.args.len() == field_names.len() {
2645                                // Check if this struct has NO names (all unnamed)
2646                                let all_unnamed = s.args.iter().all(|a| !matches!(a, Expression::Alias(_)));
2647                                if all_unnamed {
2648                                    // Apply names from first struct
2649                                    let new_args: Vec<Expression> = s.args.iter().zip(field_names.iter()).map(|(val, name)| {
2650                                        if let Some(n) = name {
2651                                            Expression::Alias(Box::new(Alias::new(
2652                                                val.clone(),
2653                                                Identifier::new(n.clone()),
2654                                            )))
2655                                        } else {
2656                                            val.clone()
2657                                        }
2658                                    }).collect();
2659                                    new_elements.push(Expression::Function(Box::new(Function::new(
2660                                        "STRUCT".to_string(), new_args,
2661                                    ))));
2662                                    continue;
2663                                }
2664                            }
2665                        }
2666                        new_elements.push(elem.clone());
2667                    }
2668
2669                    return Some(new_elements);
2670                }
2671            }
2672        }
2673        None
2674    }
2675
2676    // Look for Array expressions containing STRUCT function calls
2677    if let Expression::Array(ref arr) = expr {
2678        if let Some(new_elements) = propagate_in_elements(&arr.expressions) {
2679            return Ok(Expression::Array(Box::new(crate::expressions::Array {
2680                expressions: new_elements,
2681            })));
2682        }
2683    }
2684
2685    // Also handle ArrayFunc (ArrayConstructor) - bracket notation [STRUCT(...), ...]
2686    if let Expression::ArrayFunc(ref arr) = expr {
2687        if let Some(new_elements) = propagate_in_elements(&arr.expressions) {
2688            return Ok(Expression::ArrayFunc(Box::new(ArrayConstructor {
2689                expressions: new_elements,
2690                bracket_notation: arr.bracket_notation,
2691                use_list_keyword: arr.use_list_keyword,
2692            })));
2693        }
2694    }
2695
2696    Ok(expr)
2697}
2698
2699/// This walks the entire expression tree to find SELECT statements and converts UNNEST aliases
2700/// in their FROM clauses and JOINs.
2701pub fn unnest_alias_to_column_alias(expr: Expression) -> Result<Expression> {
2702    use crate::dialects::transform_recursive;
2703    transform_recursive(expr, &unnest_alias_transform_single_select)
2704}
2705
2706/// Move UNNEST items from FROM clause to CROSS JOINs without changing alias format.
2707/// Used for BigQuery -> BigQuery/Redshift where we want CROSS JOIN but not _t0(col) aliases.
2708pub fn unnest_from_to_cross_join(expr: Expression) -> Result<Expression> {
2709    use crate::dialects::transform_recursive;
2710    transform_recursive(expr, &unnest_from_to_cross_join_single_select)
2711}
2712
2713fn unnest_from_to_cross_join_single_select(expr: Expression) -> Result<Expression> {
2714    if let Expression::Select(mut select) = expr {
2715        if let Some(ref mut from) = select.from {
2716            if from.expressions.len() > 1 {
2717                let mut new_from_exprs = Vec::new();
2718                let mut new_cross_joins = Vec::new();
2719
2720                for (idx, from_item) in from.expressions.drain(..).enumerate() {
2721                    if idx == 0 {
2722                        new_from_exprs.push(from_item);
2723                    } else {
2724                        let is_unnest = match &from_item {
2725                            Expression::Unnest(_) => true,
2726                            Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
2727                            _ => false,
2728                        };
2729
2730                        if is_unnest {
2731                            new_cross_joins.push(crate::expressions::Join {
2732                                this: from_item,
2733                                on: None,
2734                                using: Vec::new(),
2735                                kind: JoinKind::Cross,
2736                                use_inner_keyword: false,
2737                                use_outer_keyword: false,
2738                                deferred_condition: false,
2739                                join_hint: None,
2740                                match_condition: None,
2741                                pivots: Vec::new(),
2742                            });
2743                        } else {
2744                            new_from_exprs.push(from_item);
2745                        }
2746                    }
2747                }
2748
2749                from.expressions = new_from_exprs;
2750                new_cross_joins.append(&mut select.joins);
2751                select.joins = new_cross_joins;
2752            }
2753        }
2754
2755        Ok(Expression::Select(select))
2756    } else {
2757        Ok(expr)
2758    }
2759}
2760
2761/// Wrap UNNEST function aliases in JOIN items from `AS name` to `AS _u(name)`
2762/// Used for PostgreSQL → Presto/Trino transpilation where GENERATE_SERIES is
2763/// converted to UNNEST(SEQUENCE) and the alias needs the column-alias format.
2764pub fn wrap_unnest_join_aliases(expr: Expression) -> Result<Expression> {
2765    use crate::dialects::transform_recursive;
2766    transform_recursive(expr, &wrap_unnest_join_aliases_single)
2767}
2768
2769fn wrap_unnest_join_aliases_single(expr: Expression) -> Result<Expression> {
2770    if let Expression::Select(mut select) = expr {
2771        // Process JOIN items
2772        for join in &mut select.joins {
2773            wrap_unnest_alias_in_join_item(&mut join.this);
2774        }
2775        Ok(Expression::Select(select))
2776    } else {
2777        Ok(expr)
2778    }
2779}
2780
2781/// If a join item is an Alias wrapping an UNNEST function, convert alias to _u(alias_name) format
2782fn wrap_unnest_alias_in_join_item(expr: &mut Expression) {
2783    use crate::expressions::Identifier;
2784    if let Expression::Alias(alias) = expr {
2785        // Check if the inner expression is a function call to UNNEST
2786        let is_unnest = match &alias.this {
2787            Expression::Function(f) => f.name.eq_ignore_ascii_case("UNNEST"),
2788            _ => false,
2789        };
2790
2791        if is_unnest && alias.column_aliases.is_empty() {
2792            // Simple alias like `AS s` -> wrap to `AS _u(s)`
2793            let original_alias_name = alias.alias.name.clone();
2794            alias.alias = Identifier {
2795                name: "_u".to_string(),
2796                quoted: false,
2797                trailing_comments: Vec::new(),
2798            };
2799            alias.column_aliases = vec![Identifier {
2800                name: original_alias_name,
2801                quoted: false,
2802                trailing_comments: Vec::new(),
2803            }];
2804        }
2805    }
2806}
2807
2808fn unnest_alias_transform_single_select(expr: Expression) -> Result<Expression> {
2809    if let Expression::Select(mut select) = expr {
2810        let mut counter = 0usize;
2811
2812        // Process FROM expressions: convert aliases AND move UNNEST items to CROSS JOIN
2813        if let Some(ref mut from) = select.from {
2814            // First pass: convert aliases in-place
2815            for from_item in from.expressions.iter_mut() {
2816                convert_unnest_alias_in_from(from_item, &mut counter);
2817            }
2818
2819            // Second pass: move UNNEST items from FROM to CROSS JOINs
2820            if from.expressions.len() > 1 {
2821                let mut new_from_exprs = Vec::new();
2822                let mut new_cross_joins = Vec::new();
2823
2824                for (idx, from_item) in from.expressions.drain(..).enumerate() {
2825                    if idx == 0 {
2826                        // First expression always stays in FROM
2827                        new_from_exprs.push(from_item);
2828                    } else {
2829                        // Check if this is UNNEST or Alias(UNNEST)
2830                        let is_unnest = match &from_item {
2831                            Expression::Unnest(_) => true,
2832                            Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
2833                            _ => false,
2834                        };
2835
2836                        if is_unnest {
2837                            // Convert to CROSS JOIN
2838                            new_cross_joins.push(crate::expressions::Join {
2839                                this: from_item,
2840                                on: None,
2841                                using: Vec::new(),
2842                                kind: JoinKind::Cross,
2843                                use_inner_keyword: false,
2844                                use_outer_keyword: false,
2845                                deferred_condition: false,
2846                                join_hint: None,
2847                                match_condition: None,
2848                                pivots: Vec::new(),
2849                            });
2850                        } else {
2851                            // Keep non-UNNEST items in FROM
2852                            new_from_exprs.push(from_item);
2853                        }
2854                    }
2855                }
2856
2857                from.expressions = new_from_exprs;
2858                // Prepend cross joins before existing joins
2859                new_cross_joins.append(&mut select.joins);
2860                select.joins = new_cross_joins;
2861            }
2862        }
2863
2864        // Process JOINs (existing joins that may have UNNEST aliases)
2865        for join in select.joins.iter_mut() {
2866            convert_unnest_alias_in_from(&mut join.this, &mut counter);
2867        }
2868
2869        Ok(Expression::Select(select))
2870    } else {
2871        Ok(expr)
2872    }
2873}
2874
2875fn convert_unnest_alias_in_from(expr: &mut Expression, counter: &mut usize) {
2876    use crate::expressions::Identifier;
2877
2878    if let Expression::Alias(ref mut alias) = expr {
2879        // Check if the inner expression is UNNEST (or EXPLODE)
2880        let is_unnest = matches!(&alias.this, Expression::Unnest(_))
2881            || matches!(&alias.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("EXPLODE"));
2882
2883        if is_unnest && alias.column_aliases.is_empty() {
2884            // Convert: UNNEST(arr) AS x -> UNNEST(arr) AS _tN(x)
2885            let col_alias = alias.alias.clone();
2886            alias.column_aliases = vec![col_alias];
2887            alias.alias = Identifier::new(format!("_t{}", counter));
2888            *counter += 1;
2889        }
2890    }
2891}
2892
2893/// Expand POSEXPLODE in SELECT expressions for DuckDB.
2894///
2895/// Converts `SELECT POSEXPLODE(x)` to `SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos, UNNEST(x) AS col`
2896/// Handles both aliased and unaliased forms:
2897/// - `SELECT POSEXPLODE(x) AS (a, b)` -> `SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b`
2898/// - `SELECT * FROM POSEXPLODE(x) AS (a, b)` -> `SELECT * FROM (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b)`
2899pub fn expand_posexplode_duckdb(expr: Expression) -> Result<Expression> {
2900    use crate::expressions::{Alias, Function};
2901
2902    match expr {
2903        Expression::Select(mut select) => {
2904            // Check if any SELECT expression is a POSEXPLODE function
2905            let mut new_expressions = Vec::new();
2906            let mut changed = false;
2907
2908            for sel_expr in select.expressions.drain(..) {
2909                // Check for POSEXPLODE(x) AS (a, b) - aliased form
2910                if let Expression::Alias(ref alias_box) = sel_expr {
2911                    if let Expression::Function(ref func) = alias_box.this {
2912                        if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
2913                            let arg = func.args[0].clone();
2914                            // Get alias names: default pos, col
2915                            let (pos_name, col_name) = if alias_box.column_aliases.len() == 2 {
2916                                (alias_box.column_aliases[0].name.clone(), alias_box.column_aliases[1].name.clone())
2917                            } else if !alias_box.alias.is_empty() {
2918                                // Single alias like AS x - use as col name, "pos" for position
2919                                ("pos".to_string(), alias_box.alias.name.clone())
2920                            } else {
2921                                ("pos".to_string(), "col".to_string())
2922                            };
2923
2924                            // GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos_name
2925                            let gen_subscripts = Expression::Function(Box::new(Function::new(
2926                                "GENERATE_SUBSCRIPTS".to_string(),
2927                                vec![arg.clone(), Expression::Literal(Literal::Number("1".to_string()))],
2928                            )));
2929                            let sub_one = Expression::Sub(Box::new(BinaryOp::new(
2930                                gen_subscripts,
2931                                Expression::Literal(Literal::Number("1".to_string())),
2932                            )));
2933                            let pos_alias = Expression::Alias(Box::new(Alias {
2934                                this: sub_one,
2935                                alias: Identifier::new(pos_name),
2936                                column_aliases: Vec::new(),
2937                                pre_alias_comments: Vec::new(),
2938                                trailing_comments: Vec::new(),
2939                            }));
2940
2941                            // UNNEST(x) AS col_name
2942                            let unnest = Expression::Unnest(Box::new(UnnestFunc {
2943                                this: arg,
2944                                expressions: Vec::new(),
2945                                with_ordinality: false,
2946                                alias: None,
2947                                offset_alias: None,
2948                            }));
2949                            let col_alias = Expression::Alias(Box::new(Alias {
2950                                this: unnest,
2951                                alias: Identifier::new(col_name),
2952                                column_aliases: Vec::new(),
2953                                pre_alias_comments: Vec::new(),
2954                                trailing_comments: Vec::new(),
2955                            }));
2956
2957                            new_expressions.push(pos_alias);
2958                            new_expressions.push(col_alias);
2959                            changed = true;
2960                            continue;
2961                        }
2962                    }
2963                }
2964
2965                // Check for bare POSEXPLODE(x) - unaliased form
2966                if let Expression::Function(ref func) = sel_expr {
2967                    if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
2968                        let arg = func.args[0].clone();
2969                        let pos_name = "pos";
2970                        let col_name = "col";
2971
2972                        // GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos
2973                        let gen_subscripts = Expression::Function(Box::new(Function::new(
2974                            "GENERATE_SUBSCRIPTS".to_string(),
2975                            vec![arg.clone(), Expression::Literal(Literal::Number("1".to_string()))],
2976                        )));
2977                        let sub_one = Expression::Sub(Box::new(BinaryOp::new(
2978                            gen_subscripts,
2979                            Expression::Literal(Literal::Number("1".to_string())),
2980                        )));
2981                        let pos_alias = Expression::Alias(Box::new(Alias {
2982                            this: sub_one,
2983                            alias: Identifier::new(pos_name),
2984                            column_aliases: Vec::new(),
2985                            pre_alias_comments: Vec::new(),
2986                            trailing_comments: Vec::new(),
2987                        }));
2988
2989                        // UNNEST(x) AS col
2990                        let unnest = Expression::Unnest(Box::new(UnnestFunc {
2991                            this: arg,
2992                            expressions: Vec::new(),
2993                            with_ordinality: false,
2994                            alias: None,
2995                            offset_alias: None,
2996                        }));
2997                        let col_alias = Expression::Alias(Box::new(Alias {
2998                            this: unnest,
2999                            alias: Identifier::new(col_name),
3000                            column_aliases: Vec::new(),
3001                            pre_alias_comments: Vec::new(),
3002                            trailing_comments: Vec::new(),
3003                        }));
3004
3005                        new_expressions.push(pos_alias);
3006                        new_expressions.push(col_alias);
3007                        changed = true;
3008                        continue;
3009                    }
3010                }
3011
3012                // Not a POSEXPLODE, keep as-is
3013                new_expressions.push(sel_expr);
3014            }
3015
3016            if changed {
3017                select.expressions = new_expressions;
3018            } else {
3019                select.expressions = new_expressions;
3020            }
3021
3022            // Also handle POSEXPLODE in FROM clause:
3023            // SELECT * FROM POSEXPLODE(x) AS (a, b) -> SELECT * FROM (SELECT ...)
3024            if let Some(ref mut from) = select.from {
3025                expand_posexplode_in_from_duckdb(from)?;
3026            }
3027
3028            Ok(Expression::Select(select))
3029        }
3030        other => Ok(other),
3031    }
3032}
3033
3034/// Helper to expand POSEXPLODE in FROM clause for DuckDB
3035fn expand_posexplode_in_from_duckdb(from: &mut From) -> Result<()> {
3036    use crate::expressions::{Alias, Function};
3037
3038    let mut new_expressions = Vec::new();
3039    let mut _changed = false;
3040
3041    for table_expr in from.expressions.drain(..) {
3042        // Check for POSEXPLODE(x) AS (a, b) in FROM
3043        if let Expression::Alias(ref alias_box) = table_expr {
3044            if let Expression::Function(ref func) = alias_box.this {
3045                if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
3046                    let arg = func.args[0].clone();
3047                    let (pos_name, col_name) = if alias_box.column_aliases.len() == 2 {
3048                        (alias_box.column_aliases[0].name.clone(), alias_box.column_aliases[1].name.clone())
3049                    } else {
3050                        ("pos".to_string(), "col".to_string())
3051                    };
3052
3053                    // Create subquery: (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b)
3054                    let gen_subscripts = Expression::Function(Box::new(Function::new(
3055                        "GENERATE_SUBSCRIPTS".to_string(),
3056                        vec![arg.clone(), Expression::Literal(Literal::Number("1".to_string()))],
3057                    )));
3058                    let sub_one = Expression::Sub(Box::new(BinaryOp::new(
3059                        gen_subscripts,
3060                        Expression::Literal(Literal::Number("1".to_string())),
3061                    )));
3062                    let pos_alias = Expression::Alias(Box::new(Alias {
3063                        this: sub_one,
3064                        alias: Identifier::new(&pos_name),
3065                        column_aliases: Vec::new(),
3066                        pre_alias_comments: Vec::new(),
3067                        trailing_comments: Vec::new(),
3068                    }));
3069                    let unnest = Expression::Unnest(Box::new(UnnestFunc {
3070                        this: arg,
3071                        expressions: Vec::new(),
3072                        with_ordinality: false,
3073                        alias: None,
3074                        offset_alias: None,
3075                    }));
3076                    let col_alias = Expression::Alias(Box::new(Alias {
3077                        this: unnest,
3078                        alias: Identifier::new(&col_name),
3079                        column_aliases: Vec::new(),
3080                        pre_alias_comments: Vec::new(),
3081                        trailing_comments: Vec::new(),
3082                    }));
3083
3084                    let mut inner_select = Select::new();
3085                    inner_select.expressions = vec![pos_alias, col_alias];
3086
3087                    let subquery = Expression::Subquery(Box::new(Subquery {
3088                        this: Expression::Select(Box::new(inner_select)),
3089                        alias: None,
3090                        column_aliases: Vec::new(),
3091                        order_by: None,
3092                        limit: None,
3093                        offset: None,
3094                        distribute_by: None,
3095                        sort_by: None,
3096                        cluster_by: None,
3097                        lateral: false,
3098                        modifiers_inside: false,
3099                        trailing_comments: Vec::new(),
3100                    }));
3101                    new_expressions.push(subquery);
3102                    _changed = true;
3103                    continue;
3104                }
3105            }
3106        }
3107
3108        // Also check for bare POSEXPLODE(x) in FROM (no alias)
3109        if let Expression::Function(ref func) = table_expr {
3110            if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
3111                let arg = func.args[0].clone();
3112
3113                // Create subquery: (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos, UNNEST(x) AS col)
3114                let gen_subscripts = Expression::Function(Box::new(Function::new(
3115                    "GENERATE_SUBSCRIPTS".to_string(),
3116                    vec![arg.clone(), Expression::Literal(Literal::Number("1".to_string()))],
3117                )));
3118                let sub_one = Expression::Sub(Box::new(BinaryOp::new(
3119                    gen_subscripts,
3120                    Expression::Literal(Literal::Number("1".to_string())),
3121                )));
3122                let pos_alias = Expression::Alias(Box::new(Alias {
3123                    this: sub_one,
3124                    alias: Identifier::new("pos"),
3125                    column_aliases: Vec::new(),
3126                    pre_alias_comments: Vec::new(),
3127                    trailing_comments: Vec::new(),
3128                }));
3129                let unnest = Expression::Unnest(Box::new(UnnestFunc {
3130                    this: arg,
3131                    expressions: Vec::new(),
3132                    with_ordinality: false,
3133                    alias: None,
3134                    offset_alias: None,
3135                }));
3136                let col_alias = Expression::Alias(Box::new(Alias {
3137                    this: unnest,
3138                    alias: Identifier::new("col"),
3139                    column_aliases: Vec::new(),
3140                    pre_alias_comments: Vec::new(),
3141                    trailing_comments: Vec::new(),
3142                }));
3143
3144                let mut inner_select = Select::new();
3145                inner_select.expressions = vec![pos_alias, col_alias];
3146
3147                let subquery = Expression::Subquery(Box::new(Subquery {
3148                    this: Expression::Select(Box::new(inner_select)),
3149                    alias: None,
3150                    column_aliases: Vec::new(),
3151                    order_by: None,
3152                    limit: None,
3153                    offset: None,
3154                    distribute_by: None,
3155                    sort_by: None,
3156                    cluster_by: None,
3157                    lateral: false,
3158                    modifiers_inside: false,
3159                    trailing_comments: Vec::new(),
3160                }));
3161                new_expressions.push(subquery);
3162                _changed = true;
3163                continue;
3164            }
3165        }
3166
3167        new_expressions.push(table_expr);
3168    }
3169
3170    from.expressions = new_expressions;
3171    Ok(())
3172}
3173
3174/// Convert EXPLODE/POSEXPLODE in SELECT projections into CROSS JOIN UNNEST patterns.
3175///
3176/// This implements the `explode_projection_to_unnest` transform from Python sqlglot.
3177/// It restructures queries like:
3178///   `SELECT EXPLODE(x) FROM tbl`
3179/// into:
3180///   `SELECT IF(pos = pos_2, col, NULL) AS col FROM tbl CROSS JOIN UNNEST(...) AS pos CROSS JOIN UNNEST(x) AS col WITH OFFSET AS pos_2 WHERE ...`
3181///
3182/// The transform handles:
3183/// - EXPLODE(x) and POSEXPLODE(x) functions
3184/// - Name collision avoidance (_u, _u_2, ... and col, col_2, ...)
3185/// - Multiple EXPLODE/POSEXPLODE in one SELECT
3186/// - Queries with or without FROM clause
3187/// - Presto (index_offset=1) and BigQuery (index_offset=0) variants
3188pub fn explode_projection_to_unnest(expr: Expression, target: DialectType) -> Result<Expression> {
3189    match expr {
3190        Expression::Select(select) => {
3191            explode_projection_to_unnest_impl(*select, target)
3192        }
3193        other => Ok(other),
3194    }
3195}
3196
3197/// Snowflake-specific rewrite to mirror Python sqlglot's explode_projection_to_unnest behavior
3198/// when FLATTEN appears in a nested LATERAL within a SELECT projection.
3199///
3200/// This intentionally rewrites:
3201/// - `LATERAL FLATTEN(INPUT => x) alias`
3202/// into:
3203/// - `LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS alias(SEQ, KEY, PATH, INDEX, VALUE, THIS)`
3204/// and appends CROSS JOIN TABLE(FLATTEN(...)) range/entity joins plus alignment predicates
3205/// to the containing SELECT.
3206pub fn snowflake_flatten_projection_to_unnest(expr: Expression) -> Result<Expression> {
3207    match expr {
3208        Expression::Select(select) => snowflake_flatten_projection_to_unnest_impl(*select),
3209        other => Ok(other),
3210    }
3211}
3212
3213fn snowflake_flatten_projection_to_unnest_impl(mut select: Select) -> Result<Expression> {
3214    let mut flattened_inputs: Vec<Expression> = Vec::new();
3215    let mut new_selects: Vec<Expression> = Vec::with_capacity(select.expressions.len());
3216
3217    for sel_expr in select.expressions.into_iter() {
3218        let found_input: RefCell<Option<Expression>> = RefCell::new(None);
3219
3220        let rewritten = transform_recursive(sel_expr, &|e| {
3221            if let Expression::Lateral(lat) = e {
3222                if let Some(input_expr) = extract_flatten_input(&lat) {
3223                    if found_input.borrow().is_none() {
3224                        *found_input.borrow_mut() = Some(input_expr);
3225                    }
3226                    return Ok(Expression::Lateral(Box::new(rewrite_flatten_lateral(*lat))));
3227                }
3228                return Ok(Expression::Lateral(lat));
3229            }
3230            Ok(e)
3231        })?;
3232
3233        if let Some(input) = found_input.into_inner() {
3234            flattened_inputs.push(input);
3235        }
3236        new_selects.push(rewritten);
3237    }
3238
3239    if flattened_inputs.is_empty() {
3240        select.expressions = new_selects;
3241        return Ok(Expression::Select(Box::new(select)));
3242    }
3243
3244    select.expressions = new_selects;
3245
3246    for (idx, input_expr) in flattened_inputs.into_iter().enumerate() {
3247        // Match sqlglot naming: first pair is _u/_u_2 with pos/pos_2 and entity.
3248        let is_first = idx == 0;
3249        let series_alias = if is_first { "pos".to_string() } else { format!("pos_{}", idx + 1) };
3250        let series_source_alias = if is_first { "_u".to_string() } else { format!("_u_{}", idx * 2 + 1) };
3251        let unnest_source_alias = if is_first { "_u_2".to_string() } else { format!("_u_{}", idx * 2 + 2) };
3252        let pos2_alias = if is_first { "pos_2".to_string() } else { format!("{}_2", series_alias) };
3253        let entity_alias = if is_first { "entity".to_string() } else { format!("entity_{}", idx + 1) };
3254
3255        let array_size_call = Expression::Function(Box::new(Function::new(
3256            "ARRAY_SIZE".to_string(),
3257            vec![Expression::NamedArgument(Box::new(NamedArgument {
3258                name: Identifier::new("INPUT"),
3259                value: input_expr.clone(),
3260                separator: NamedArgSeparator::DArrow,
3261            }))],
3262        )));
3263
3264        let greatest = Expression::Function(Box::new(Function::new(
3265            "GREATEST".to_string(),
3266            vec![array_size_call.clone()],
3267        )));
3268
3269        let series_end = Expression::Add(Box::new(BinaryOp::new(
3270            Expression::Paren(Box::new(crate::expressions::Paren {
3271                this: Expression::Sub(Box::new(BinaryOp::new(
3272                    greatest,
3273                    Expression::Literal(Literal::Number("1".to_string())),
3274                ))),
3275                trailing_comments: Vec::new(),
3276            })),
3277            Expression::Literal(Literal::Number("1".to_string())),
3278        )));
3279
3280        let series_range = Expression::Function(Box::new(Function::new(
3281            "ARRAY_GENERATE_RANGE".to_string(),
3282            vec![Expression::Literal(Literal::Number("0".to_string())), series_end],
3283        )));
3284
3285        let series_flatten = Expression::Function(Box::new(Function::new(
3286            "FLATTEN".to_string(),
3287            vec![Expression::NamedArgument(Box::new(NamedArgument {
3288                name: Identifier::new("INPUT"),
3289                value: series_range,
3290                separator: NamedArgSeparator::DArrow,
3291            }))],
3292        )));
3293
3294        let series_table = Expression::Function(Box::new(Function::new(
3295            "TABLE".to_string(),
3296            vec![series_flatten],
3297        )));
3298
3299        let series_alias_expr = Expression::Alias(Box::new(Alias {
3300            this: series_table,
3301            alias: Identifier::new(series_source_alias.clone()),
3302            column_aliases: vec![
3303                Identifier::new("seq"),
3304                Identifier::new("key"),
3305                Identifier::new("path"),
3306                Identifier::new("index"),
3307                Identifier::new(series_alias.clone()),
3308                Identifier::new("this"),
3309            ],
3310            pre_alias_comments: Vec::new(),
3311            trailing_comments: Vec::new(),
3312        }));
3313
3314        select.joins.push(Join {
3315            this: series_alias_expr,
3316            on: None,
3317            using: Vec::new(),
3318            kind: JoinKind::Cross,
3319            use_inner_keyword: false,
3320            use_outer_keyword: false,
3321            deferred_condition: false,
3322            join_hint: None,
3323            match_condition: None,
3324            pivots: Vec::new(),
3325        });
3326
3327        let entity_flatten = Expression::Function(Box::new(Function::new(
3328            "FLATTEN".to_string(),
3329            vec![Expression::NamedArgument(Box::new(NamedArgument {
3330                name: Identifier::new("INPUT"),
3331                value: input_expr.clone(),
3332                separator: NamedArgSeparator::DArrow,
3333            }))],
3334        )));
3335
3336        let entity_table = Expression::Function(Box::new(Function::new(
3337            "TABLE".to_string(),
3338            vec![entity_flatten],
3339        )));
3340
3341        let entity_alias_expr = Expression::Alias(Box::new(Alias {
3342            this: entity_table,
3343            alias: Identifier::new(unnest_source_alias.clone()),
3344            column_aliases: vec![
3345                Identifier::new("seq"),
3346                Identifier::new("key"),
3347                Identifier::new("path"),
3348                Identifier::new(pos2_alias.clone()),
3349                Identifier::new(entity_alias.clone()),
3350                Identifier::new("this"),
3351            ],
3352            pre_alias_comments: Vec::new(),
3353            trailing_comments: Vec::new(),
3354        }));
3355
3356        select.joins.push(Join {
3357            this: entity_alias_expr,
3358            on: None,
3359            using: Vec::new(),
3360            kind: JoinKind::Cross,
3361            use_inner_keyword: false,
3362            use_outer_keyword: false,
3363            deferred_condition: false,
3364            join_hint: None,
3365            match_condition: None,
3366            pivots: Vec::new(),
3367        });
3368
3369        let pos_col = Expression::qualified_column(series_source_alias.clone(), series_alias.clone());
3370        let pos2_col = Expression::qualified_column(unnest_source_alias.clone(), pos2_alias.clone());
3371
3372        let eq = Expression::Eq(Box::new(BinaryOp::new(pos_col.clone(), pos2_col.clone())));
3373        let size_minus_1 = Expression::Paren(Box::new(crate::expressions::Paren {
3374            this: Expression::Sub(Box::new(BinaryOp::new(
3375                array_size_call,
3376                Expression::Literal(Literal::Number("1".to_string())),
3377            ))),
3378            trailing_comments: Vec::new(),
3379        }));
3380        let gt = Expression::Gt(Box::new(BinaryOp::new(pos_col, size_minus_1.clone())));
3381        let pos2_eq_size = Expression::Eq(Box::new(BinaryOp::new(pos2_col, size_minus_1)));
3382        let and_cond = Expression::And(Box::new(BinaryOp::new(gt, pos2_eq_size)));
3383        let or_cond = Expression::Or(Box::new(BinaryOp::new(
3384            eq,
3385            Expression::Paren(Box::new(crate::expressions::Paren {
3386                this: and_cond,
3387                trailing_comments: Vec::new(),
3388            })),
3389        )));
3390
3391        select.where_clause = Some(match select.where_clause.take() {
3392            Some(existing) => Where {
3393                this: Expression::And(Box::new(BinaryOp::new(existing.this, or_cond))),
3394            },
3395            None => Where { this: or_cond },
3396        });
3397    }
3398
3399    Ok(Expression::Select(Box::new(select)))
3400}
3401
3402fn extract_flatten_input(lat: &Lateral) -> Option<Expression> {
3403    let Expression::Function(f) = lat.this.as_ref() else {
3404        return None;
3405    };
3406    if !f.name.eq_ignore_ascii_case("FLATTEN") {
3407        return None;
3408    }
3409
3410    for arg in &f.args {
3411        if let Expression::NamedArgument(na) = arg {
3412            if na.name.name.eq_ignore_ascii_case("INPUT") {
3413                return Some(na.value.clone());
3414            }
3415        }
3416    }
3417    f.args.first().cloned()
3418}
3419
3420fn rewrite_flatten_lateral(mut lat: Lateral) -> Lateral {
3421    let cond = Expression::Eq(Box::new(BinaryOp::new(
3422        Expression::qualified_column("_u", "pos"),
3423        Expression::qualified_column("_u_2", "pos_2"),
3424    )));
3425    let true_expr = Expression::qualified_column("_u_2", "entity");
3426    let iff_expr = Expression::Function(Box::new(Function::new(
3427        "IFF".to_string(),
3428        vec![cond, true_expr, Expression::Null(crate::expressions::Null)],
3429    )));
3430
3431    lat.this = Box::new(iff_expr);
3432    if lat.column_aliases.is_empty() {
3433        lat.column_aliases = vec![
3434            "SEQ".to_string(),
3435            "KEY".to_string(),
3436            "PATH".to_string(),
3437            "INDEX".to_string(),
3438            "VALUE".to_string(),
3439            "THIS".to_string(),
3440        ];
3441    }
3442    lat
3443}
3444
3445/// Info about an EXPLODE/POSEXPLODE found in a SELECT projection
3446struct ExplodeInfo {
3447    /// The argument to EXPLODE/POSEXPLODE (the array expression)
3448    arg_sql: String,
3449    /// The alias for the exploded column
3450    explode_alias: String,
3451    /// The alias for the position column
3452    pos_alias: String,
3453    /// Source alias for this unnest (e.g., _u_2)
3454    unnest_source_alias: String,
3455}
3456
3457fn explode_projection_to_unnest_impl(select: Select, target: DialectType) -> Result<Expression> {
3458    let is_presto = matches!(target, DialectType::Presto | DialectType::Trino | DialectType::Athena);
3459    let is_bigquery = matches!(target, DialectType::BigQuery);
3460
3461    if !is_presto && !is_bigquery {
3462        return Ok(Expression::Select(Box::new(select)));
3463    }
3464
3465    // Check if any SELECT projection contains EXPLODE or POSEXPLODE
3466    let has_explode = select.expressions.iter().any(|e| expr_contains_explode(e));
3467    if !has_explode {
3468        return Ok(Expression::Select(Box::new(select)));
3469    }
3470
3471    // Collect taken names from existing SELECT expressions and FROM sources
3472    let mut taken_select_names = std::collections::HashSet::new();
3473    let mut taken_source_names = std::collections::HashSet::new();
3474
3475    // Collect names from existing SELECT expressions (output names)
3476    for sel in &select.expressions {
3477        if let Some(name) = get_output_name(sel) {
3478            taken_select_names.insert(name);
3479        }
3480    }
3481
3482    // Also add the explode arg name if it's a column reference
3483    for sel in &select.expressions {
3484        let explode_expr = find_explode_in_expr(sel);
3485        if let Some(arg) = explode_expr {
3486            if let Some(name) = get_output_name(&arg) {
3487                taken_select_names.insert(name);
3488            }
3489        }
3490    }
3491
3492    // Collect source names from FROM clause
3493    if let Some(ref from) = select.from {
3494        for from_expr in &from.expressions {
3495            collect_source_names(from_expr, &mut taken_source_names);
3496        }
3497    }
3498    // Also collect from JOINs
3499    for join in &select.joins {
3500        collect_source_names(&join.this, &mut taken_source_names);
3501    }
3502
3503    // Generate series alias
3504    let series_alias = new_name(&mut taken_select_names, "pos");
3505
3506    // Generate series source alias
3507    let series_source_alias = new_name(&mut taken_source_names, "_u");
3508
3509    // Get the target dialect for generating expression SQL
3510    let target_dialect = Dialect::get(target);
3511
3512    // Process each SELECT expression, collecting explode info
3513    let mut explode_infos: Vec<ExplodeInfo> = Vec::new();
3514    let mut new_projections: Vec<String> = Vec::new();
3515
3516    for (_idx, sel_expr) in select.expressions.iter().enumerate() {
3517        let explode_data = extract_explode_data(sel_expr);
3518
3519        if let Some((is_posexplode, arg_expr, explicit_alias, explicit_pos_alias)) = explode_data {
3520            // Generate the argument SQL in target dialect
3521            let arg_sql = target_dialect.generate(&arg_expr).unwrap_or_else(|_| "NULL".to_string());
3522
3523            let unnest_source_alias = new_name(&mut taken_source_names, "_u");
3524
3525            let explode_alias = if let Some(ref ea) = explicit_alias {
3526                // Use the explicit alias directly (it was explicitly specified by the user)
3527                // Remove from taken_select_names first to avoid false collision with itself
3528                taken_select_names.remove(ea.as_str());
3529                // Now check for collision with other names
3530                let name = new_name(&mut taken_select_names, ea);
3531                name
3532            } else {
3533                new_name(&mut taken_select_names, "col")
3534            };
3535
3536            let pos_alias = if let Some(ref pa) = explicit_pos_alias {
3537                // Use the explicit pos alias directly
3538                taken_select_names.remove(pa.as_str());
3539                let name = new_name(&mut taken_select_names, pa);
3540                name
3541            } else {
3542                new_name(&mut taken_select_names, "pos")
3543            };
3544
3545            // Build the IF projection
3546            if is_presto {
3547                // Presto: IF(_u.pos = _u_2.pos_2, _u_2.col) AS col
3548                let if_col = format!(
3549                    "IF({}.{} = {}.{}, {}.{}) AS {}",
3550                    series_source_alias, series_alias,
3551                    unnest_source_alias, pos_alias,
3552                    unnest_source_alias, explode_alias,
3553                    explode_alias
3554                );
3555                new_projections.push(if_col);
3556
3557                // For POSEXPLODE, also add the position projection
3558                if is_posexplode {
3559                    let if_pos = format!(
3560                        "IF({}.{} = {}.{}, {}.{}) AS {}",
3561                        series_source_alias, series_alias,
3562                        unnest_source_alias, pos_alias,
3563                        unnest_source_alias, pos_alias,
3564                        pos_alias
3565                    );
3566                    new_projections.push(if_pos);
3567                }
3568            } else {
3569                // BigQuery: IF(pos = pos_2, col, NULL) AS col
3570                let if_col = format!(
3571                    "IF({} = {}, {}, NULL) AS {}",
3572                    series_alias, pos_alias,
3573                    explode_alias, explode_alias
3574                );
3575                new_projections.push(if_col);
3576
3577                // For POSEXPLODE, also add the position projection
3578                if is_posexplode {
3579                    let if_pos = format!(
3580                        "IF({} = {}, {}, NULL) AS {}",
3581                        series_alias, pos_alias,
3582                        pos_alias, pos_alias
3583                    );
3584                    new_projections.push(if_pos);
3585                }
3586            }
3587
3588            explode_infos.push(ExplodeInfo {
3589                arg_sql,
3590                explode_alias,
3591                pos_alias,
3592                unnest_source_alias,
3593            });
3594        } else {
3595            // Not an EXPLODE expression, generate as-is
3596            let sel_sql = target_dialect.generate(sel_expr).unwrap_or_else(|_| "*".to_string());
3597            new_projections.push(sel_sql);
3598        }
3599    }
3600
3601    if explode_infos.is_empty() {
3602        return Ok(Expression::Select(Box::new(select)));
3603    }
3604
3605    // Build the FROM clause
3606    let mut from_parts: Vec<String> = Vec::new();
3607
3608    // Existing FROM sources
3609    if let Some(ref from) = select.from {
3610        for from_expr in &from.expressions {
3611            let from_sql = target_dialect.generate(from_expr).unwrap_or_default();
3612            from_parts.push(from_sql);
3613        }
3614    }
3615
3616    // Build the size expressions for the series generator
3617    let size_exprs: Vec<String> = explode_infos.iter().map(|info| {
3618        if is_presto {
3619            format!("CARDINALITY({})", info.arg_sql)
3620        } else {
3621            format!("ARRAY_LENGTH({})", info.arg_sql)
3622        }
3623    }).collect();
3624
3625    let greatest_arg = if size_exprs.len() == 1 {
3626        size_exprs[0].clone()
3627    } else {
3628        format!("GREATEST({})", size_exprs.join(", "))
3629    };
3630
3631    // Build the series source
3632    // greatest_arg is already "GREATEST(...)" when multiple, or "CARDINALITY(x)" / "ARRAY_LENGTH(x)" when single
3633    let series_sql = if is_presto {
3634        // SEQUENCE(1, GREATEST(CARDINALITY(x))) for single, SEQUENCE(1, GREATEST(C(a), C(b))) for multiple
3635        if size_exprs.len() == 1 {
3636            format!("UNNEST(SEQUENCE(1, GREATEST({}))) AS {}({})",
3637                greatest_arg, series_source_alias, series_alias)
3638        } else {
3639            // greatest_arg already has GREATEST(...) wrapper
3640            format!("UNNEST(SEQUENCE(1, {})) AS {}({})",
3641                greatest_arg, series_source_alias, series_alias)
3642        }
3643    } else {
3644        // GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1) for single
3645        if size_exprs.len() == 1 {
3646            format!("UNNEST(GENERATE_ARRAY(0, GREATEST({}) - 1)) AS {}",
3647                greatest_arg, series_alias)
3648        } else {
3649            // greatest_arg already has GREATEST(...) wrapper
3650            format!("UNNEST(GENERATE_ARRAY(0, {} - 1)) AS {}",
3651                greatest_arg, series_alias)
3652        }
3653    };
3654
3655    // Build CROSS JOIN UNNEST clauses
3656    // Always use Presto-style (WITH ORDINALITY) for the SQL string to parse,
3657    // then convert to BigQuery-style AST after parsing if needed
3658    let mut cross_joins: Vec<String> = Vec::new();
3659
3660    for info in &explode_infos {
3661        // Always use WITH ORDINALITY syntax (which our parser handles)
3662        cross_joins.push(format!(
3663            "CROSS JOIN UNNEST({}) WITH ORDINALITY AS {}({}, {})",
3664            info.arg_sql, info.unnest_source_alias, info.explode_alias, info.pos_alias
3665        ));
3666    }
3667
3668    // Build WHERE clause
3669    let mut where_conditions: Vec<String> = Vec::new();
3670
3671    for info in &explode_infos {
3672        let size_expr = if is_presto {
3673            format!("CARDINALITY({})", info.arg_sql)
3674        } else {
3675            format!("ARRAY_LENGTH({})", info.arg_sql)
3676        };
3677
3678        let cond = if is_presto {
3679            format!(
3680                "{series_src}.{series_al} = {unnest_src}.{pos_al} OR ({series_src}.{series_al} > {size} AND {unnest_src}.{pos_al} = {size})",
3681                series_src = series_source_alias,
3682                series_al = series_alias,
3683                unnest_src = info.unnest_source_alias,
3684                pos_al = info.pos_alias,
3685                size = size_expr
3686            )
3687        } else {
3688            format!(
3689                "{series_al} = {pos_al} OR ({series_al} > ({size} - 1) AND {pos_al} = ({size} - 1))",
3690                series_al = series_alias,
3691                pos_al = info.pos_alias,
3692                size = size_expr
3693            )
3694        };
3695
3696        where_conditions.push(cond);
3697    }
3698
3699    // Combine WHERE conditions with AND (wrapped in parens if multiple)
3700    let where_sql = if where_conditions.len() == 1 {
3701        where_conditions[0].clone()
3702    } else {
3703        where_conditions.iter().map(|c| format!("({})", c)).collect::<Vec<_>>().join(" AND ")
3704    };
3705
3706    // Build the complete SQL
3707    let select_part = new_projections.join(", ");
3708
3709    // FROM part: if there was no original FROM, the series becomes the FROM source
3710    let from_and_joins = if from_parts.is_empty() {
3711        // No original FROM: series is the FROM source, everything else is CROSS JOIN
3712        format!("FROM {} {}", series_sql, cross_joins.join(" "))
3713    } else {
3714        format!("FROM {} {} {}", from_parts.join(", "),
3715            format!("CROSS JOIN {}", series_sql),
3716            cross_joins.join(" "))
3717    };
3718
3719    let full_sql = format!(
3720        "SELECT {} {} WHERE {}",
3721        select_part, from_and_joins, where_sql
3722    );
3723
3724    // Parse the constructed SQL using the Generic dialect (which handles all SQL syntax)
3725    // We use Generic instead of the target dialect to avoid parser limitations
3726    let generic_dialect = Dialect::get(DialectType::Generic);
3727    let parsed = generic_dialect.parse(&full_sql);
3728    match parsed {
3729        Ok(mut stmts) if !stmts.is_empty() => {
3730            let mut result = stmts.remove(0);
3731
3732            // For BigQuery, convert Presto-style UNNEST AST to BigQuery-style
3733            // Presto: Alias(Unnest(with_ordinality=true), alias=_u_N, column_aliases=[col, pos])
3734            // BigQuery: Unnest(with_ordinality=true, alias=col, offset_alias=pos) [no outer Alias]
3735            if is_bigquery {
3736                convert_unnest_presto_to_bigquery(&mut result);
3737            }
3738
3739            Ok(result)
3740        }
3741        _ => {
3742            // If parsing fails, return the original expression unchanged
3743            Ok(Expression::Select(Box::new(select)))
3744        }
3745    }
3746}
3747
3748/// Convert Presto-style UNNEST WITH ORDINALITY to BigQuery-style UNNEST WITH OFFSET in the AST.
3749/// Presto: Alias(Unnest(with_ordinality=true), alias=_u_N, column_aliases=[col, pos_N])
3750/// BigQuery: Unnest(with_ordinality=true, alias=col, offset_alias=pos_N)
3751fn convert_unnest_presto_to_bigquery(expr: &mut Expression) {
3752    match expr {
3753        Expression::Select(ref mut select) => {
3754            // Convert in FROM clause
3755            if let Some(ref mut from) = select.from {
3756                for from_item in from.expressions.iter_mut() {
3757                    convert_unnest_presto_to_bigquery(from_item);
3758                }
3759            }
3760            // Convert in JOINs
3761            for join in select.joins.iter_mut() {
3762                convert_unnest_presto_to_bigquery(&mut join.this);
3763            }
3764        }
3765        Expression::Alias(ref alias) => {
3766            // Check if this is Alias(Unnest(with_ordinality=true), ..., column_aliases=[col, pos])
3767            if let Expression::Unnest(ref unnest) = alias.this {
3768                if unnest.with_ordinality && alias.column_aliases.len() >= 2 {
3769                    let col_alias = alias.column_aliases[0].clone();
3770                    let pos_alias = alias.column_aliases[1].clone();
3771                    let mut new_unnest = unnest.as_ref().clone();
3772                    new_unnest.alias = Some(col_alias);
3773                    new_unnest.offset_alias = Some(pos_alias);
3774                    // Replace the Alias(Unnest) with just Unnest
3775                    *expr = Expression::Unnest(Box::new(new_unnest));
3776                }
3777            }
3778        }
3779        _ => {}
3780    }
3781}
3782
3783/// Find a new name that doesn't conflict with existing names.
3784/// Tries `base`, then `base_2`, `base_3`, etc.
3785fn new_name(names: &mut std::collections::HashSet<String>, base: &str) -> String {
3786    if !names.contains(base) {
3787        names.insert(base.to_string());
3788        return base.to_string();
3789    }
3790    let mut i = 2;
3791    loop {
3792        let candidate = format!("{}_{}", base, i);
3793        if !names.contains(&candidate) {
3794            names.insert(candidate.clone());
3795            return candidate;
3796        }
3797        i += 1;
3798    }
3799}
3800
3801/// Check if an expression contains EXPLODE or POSEXPLODE
3802fn expr_contains_explode(expr: &Expression) -> bool {
3803    match expr {
3804        Expression::Explode(_) => true,
3805        Expression::ExplodeOuter(_) => true,
3806        Expression::Function(f) => {
3807            let name = f.name.to_uppercase();
3808            name == "POSEXPLODE" || name == "POSEXPLODE_OUTER"
3809        }
3810        Expression::Alias(a) => expr_contains_explode(&a.this),
3811        _ => false,
3812    }
3813}
3814
3815/// Find the EXPLODE/POSEXPLODE expression within a select item, return the arg
3816fn find_explode_in_expr(expr: &Expression) -> Option<Expression> {
3817    match expr {
3818        Expression::Explode(uf) => Some(uf.this.clone()),
3819        Expression::ExplodeOuter(uf) => Some(uf.this.clone()),
3820        Expression::Function(f) => {
3821            let name = f.name.to_uppercase();
3822            if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
3823                Some(f.args[0].clone())
3824            } else {
3825                None
3826            }
3827        }
3828        Expression::Alias(a) => find_explode_in_expr(&a.this),
3829        _ => None,
3830    }
3831}
3832
3833/// Extract explode data from a SELECT expression.
3834/// Returns (is_posexplode, arg_expression, explicit_col_alias, explicit_pos_alias)
3835fn extract_explode_data(expr: &Expression) -> Option<(bool, Expression, Option<String>, Option<String>)> {
3836    match expr {
3837        // Bare EXPLODE(x) without alias
3838        Expression::Explode(uf) => {
3839            Some((false, uf.this.clone(), None, None))
3840        }
3841        Expression::ExplodeOuter(uf) => {
3842            Some((false, uf.this.clone(), None, None))
3843        }
3844        // Bare POSEXPLODE(x) without alias
3845        Expression::Function(f) => {
3846            let name = f.name.to_uppercase();
3847            if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
3848                Some((true, f.args[0].clone(), None, None))
3849            } else {
3850                None
3851            }
3852        }
3853        // Aliased: EXPLODE(x) AS col, or POSEXPLODE(x) AS (a, b)
3854        Expression::Alias(a) => {
3855            match &a.this {
3856                Expression::Explode(uf) => {
3857                    let alias = if !a.alias.is_empty() {
3858                        Some(a.alias.name.clone())
3859                    } else {
3860                        None
3861                    };
3862                    Some((false, uf.this.clone(), alias, None))
3863                }
3864                Expression::ExplodeOuter(uf) => {
3865                    let alias = if !a.alias.is_empty() {
3866                        Some(a.alias.name.clone())
3867                    } else {
3868                        None
3869                    };
3870                    Some((false, uf.this.clone(), alias, None))
3871                }
3872                Expression::Function(f) => {
3873                    let name = f.name.to_uppercase();
3874                    if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
3875                        // Check for column aliases: AS (a, b)
3876                        if a.column_aliases.len() == 2 {
3877                            let pos_alias = a.column_aliases[0].name.clone();
3878                            let col_alias = a.column_aliases[1].name.clone();
3879                            Some((true, f.args[0].clone(), Some(col_alias), Some(pos_alias)))
3880                        } else if !a.alias.is_empty() {
3881                            // Single alias: AS x
3882                            Some((true, f.args[0].clone(), Some(a.alias.name.clone()), None))
3883                        } else {
3884                            Some((true, f.args[0].clone(), None, None))
3885                        }
3886                    } else {
3887                        None
3888                    }
3889                }
3890                _ => None,
3891            }
3892        }
3893        _ => None,
3894    }
3895}
3896
3897/// Get the output name of a SELECT expression
3898fn get_output_name(expr: &Expression) -> Option<String> {
3899    match expr {
3900        Expression::Alias(a) => {
3901            if !a.alias.is_empty() {
3902                Some(a.alias.name.clone())
3903            } else {
3904                None
3905            }
3906        }
3907        Expression::Column(c) => Some(c.name.name.clone()),
3908        Expression::Identifier(id) => Some(id.name.clone()),
3909        _ => None,
3910    }
3911}
3912
3913/// Collect source names from a FROM/JOIN expression
3914fn collect_source_names(expr: &Expression, names: &mut std::collections::HashSet<String>) {
3915    match expr {
3916        Expression::Alias(a) => {
3917            if !a.alias.is_empty() {
3918                names.insert(a.alias.name.clone());
3919            }
3920        }
3921        Expression::Subquery(s) => {
3922            if let Some(ref alias) = s.alias {
3923                names.insert(alias.name.clone());
3924            }
3925        }
3926        Expression::Table(t) => {
3927            if let Some(ref alias) = t.alias {
3928                names.insert(alias.name.clone());
3929            } else {
3930                names.insert(t.name.name.clone());
3931            }
3932        }
3933        Expression::Column(c) => {
3934            names.insert(c.name.name.clone());
3935        }
3936        Expression::Identifier(id) => {
3937            names.insert(id.name.clone());
3938        }
3939        _ => {}
3940    }
3941}
3942
3943/// Strip UNNEST wrapping from column reference arguments for Redshift target.
3944/// BigQuery UNNEST(column_ref) -> Redshift: just column_ref
3945pub fn strip_unnest_column_refs(expr: Expression) -> Result<Expression> {
3946    use crate::dialects::transform_recursive;
3947    transform_recursive(expr, &strip_unnest_column_refs_single)
3948}
3949
3950fn strip_unnest_column_refs_single(expr: Expression) -> Result<Expression> {
3951    if let Expression::Select(mut select) = expr {
3952        // Process JOINs (UNNEST items have been moved to joins by unnest_from_to_cross_join)
3953        for join in select.joins.iter_mut() {
3954            strip_unnest_from_expr(&mut join.this);
3955        }
3956        // Process FROM items too
3957        if let Some(ref mut from) = select.from {
3958            for from_item in from.expressions.iter_mut() {
3959                strip_unnest_from_expr(from_item);
3960            }
3961        }
3962        Ok(Expression::Select(select))
3963    } else {
3964        Ok(expr)
3965    }
3966}
3967
3968/// If expr is Alias(UNNEST(column_ref), alias) where UNNEST arg is a column/dot path,
3969/// replace with Alias(column_ref, alias) to strip the UNNEST.
3970fn strip_unnest_from_expr(expr: &mut Expression) {
3971    if let Expression::Alias(ref mut alias) = expr {
3972        if let Expression::Unnest(ref unnest) = alias.this {
3973            let is_column_ref = matches!(&unnest.this, Expression::Column(_) | Expression::Dot(_));
3974            if is_column_ref {
3975                // Replace UNNEST(col_ref) with just col_ref
3976                let inner = unnest.this.clone();
3977                alias.this = inner;
3978            }
3979        }
3980    }
3981}
3982
3983/// Wrap DuckDB UNNEST of struct arrays in (SELECT UNNEST(..., max_depth => 2)) subquery.
3984/// BigQuery UNNEST of struct arrays needs this wrapping for DuckDB to properly expand struct fields.
3985pub fn wrap_duckdb_unnest_struct(expr: Expression) -> Result<Expression> {
3986    use crate::dialects::transform_recursive;
3987    transform_recursive(expr, &wrap_duckdb_unnest_struct_single)
3988}
3989
3990fn wrap_duckdb_unnest_struct_single(expr: Expression) -> Result<Expression> {
3991    if let Expression::Select(mut select) = expr {
3992        // Process FROM items
3993        if let Some(ref mut from) = select.from {
3994            for from_item in from.expressions.iter_mut() {
3995                try_wrap_unnest_in_subquery(from_item);
3996            }
3997        }
3998
3999        // Process JOINs
4000        for join in select.joins.iter_mut() {
4001            try_wrap_unnest_in_subquery(&mut join.this);
4002        }
4003
4004        Ok(Expression::Select(select))
4005    } else {
4006        Ok(expr)
4007    }
4008}
4009
4010/// Check if an expression contains struct array elements that need DuckDB UNNEST wrapping.
4011fn is_struct_array_unnest_arg(expr: &Expression) -> bool {
4012    match expr {
4013        // Array literal containing struct elements
4014        Expression::Array(arr) => {
4015            arr.expressions.iter().any(|e| matches!(e, Expression::Struct(_)))
4016        }
4017        Expression::ArrayFunc(arr) => {
4018            arr.expressions.iter().any(|e| matches!(e, Expression::Struct(_)))
4019        }
4020        // CAST to struct array type, e.g. CAST([] AS STRUCT(x BIGINT)[])
4021        Expression::Cast(c) => {
4022            matches!(&c.to, DataType::Array { element_type, .. } if matches!(**element_type, DataType::Struct { .. }))
4023        }
4024        _ => false,
4025    }
4026}
4027
4028/// Try to wrap an UNNEST expression in a (SELECT UNNEST(..., max_depth => 2)) subquery.
4029/// Handles both bare UNNEST and Alias(UNNEST).
4030fn try_wrap_unnest_in_subquery(expr: &mut Expression) {
4031    // Check for Alias wrapping UNNEST
4032    if let Expression::Alias(ref alias) = expr {
4033        if let Expression::Unnest(ref unnest) = alias.this {
4034            if is_struct_array_unnest_arg(&unnest.this) {
4035                let unnest_clone = (**unnest).clone();
4036                let alias_name = alias.alias.clone();
4037                let new_expr = make_unnest_subquery(unnest_clone, Some(alias_name));
4038                *expr = new_expr;
4039                return;
4040            }
4041        }
4042    }
4043
4044    // Check for bare UNNEST
4045    if let Expression::Unnest(ref unnest) = expr {
4046        if is_struct_array_unnest_arg(&unnest.this) {
4047            let unnest_clone = (**unnest).clone();
4048            let new_expr = make_unnest_subquery(unnest_clone, None);
4049            *expr = new_expr;
4050        }
4051    }
4052}
4053
4054/// Create (SELECT UNNEST(arg, max_depth => 2)) [AS alias] subquery.
4055fn make_unnest_subquery(unnest: UnnestFunc, alias: Option<Identifier>) -> Expression {
4056    // Build UNNEST function call with max_depth => 2 named argument
4057    let max_depth_arg = Expression::NamedArgument(Box::new(NamedArgument {
4058        name: Identifier::new("max_depth".to_string()),
4059        value: Expression::Literal(Literal::Number("2".to_string())),
4060        separator: NamedArgSeparator::DArrow,
4061    }));
4062
4063    let mut unnest_args = vec![unnest.this];
4064    unnest_args.extend(unnest.expressions);
4065    unnest_args.push(max_depth_arg);
4066
4067    let unnest_func = Expression::Function(Box::new(Function::new(
4068        "UNNEST".to_string(),
4069        unnest_args,
4070    )));
4071
4072    // Build SELECT UNNEST(...)
4073    let mut inner_select = Select::new();
4074    inner_select.expressions = vec![unnest_func];
4075    let inner_select = Expression::Select(Box::new(inner_select));
4076
4077    // Wrap in subquery
4078    let subquery = Subquery {
4079        this: inner_select,
4080        alias,
4081        column_aliases: Vec::new(),
4082        order_by: None,
4083        limit: None,
4084        offset: None,
4085        distribute_by: None,
4086        sort_by: None,
4087        cluster_by: None,
4088        lateral: false,
4089        modifiers_inside: false,
4090        trailing_comments: Vec::new(),
4091    };
4092
4093    Expression::Subquery(Box::new(subquery))
4094}
4095
4096#[cfg(test)]
4097mod tests {
4098    use super::*;
4099    use crate::dialects::{Dialect, DialectType};
4100    use crate::expressions::Column;
4101
4102    fn parse(sql: &str) -> Expression {
4103        let dialect = Dialect::get(DialectType::Generic);
4104        let ast = dialect.parse(sql).unwrap();
4105        ast.into_iter().next().unwrap()
4106    }
4107
4108    fn gen(expr: &Expression) -> String {
4109        let dialect = Dialect::get(DialectType::Generic);
4110        dialect.generate(expr).unwrap()
4111    }
4112
4113    #[test]
4114    fn test_preprocess() {
4115        let expr = Expression::Boolean(BooleanLiteral { value: true });
4116        let result = preprocess(expr, &[replace_bool_with_int]).unwrap();
4117        assert!(matches!(result, Expression::Literal(Literal::Number(_))));
4118    }
4119
4120    #[test]
4121    fn test_preprocess_chain() {
4122        // Test chaining multiple transforms using function pointers
4123        let expr = Expression::Boolean(BooleanLiteral { value: true });
4124        // Create array of function pointers (all same type)
4125        let transforms: Vec<fn(Expression) -> Result<Expression>> =
4126            vec![replace_bool_with_int, replace_int_with_bool];
4127        let result = preprocess(expr, &transforms).unwrap();
4128        // After replace_bool_with_int: 1
4129        // After replace_int_with_bool: true
4130        if let Expression::Boolean(b) = result {
4131            assert!(b.value);
4132        } else {
4133            panic!("Expected boolean literal");
4134        }
4135    }
4136
4137    #[test]
4138    fn test_unnest_to_explode() {
4139        let unnest = Expression::Unnest(Box::new(UnnestFunc {
4140            this: Expression::Column(Column {
4141                name: Identifier::new("arr".to_string()),
4142                table: None,
4143                join_mark: false,
4144                trailing_comments: vec![],
4145            }),
4146            expressions: Vec::new(),
4147            with_ordinality: false,
4148            alias: None,
4149            offset_alias: None,
4150        }));
4151
4152        let result = unnest_to_explode(unnest).unwrap();
4153        assert!(matches!(result, Expression::Explode(_)));
4154    }
4155
4156    #[test]
4157    fn test_explode_to_unnest() {
4158        let explode = Expression::Explode(Box::new(UnaryFunc {
4159            this: Expression::Column(Column {
4160                name: Identifier::new("arr".to_string()),
4161                table: None,
4162                join_mark: false,
4163                trailing_comments: vec![],
4164            }),
4165            original_name: None,
4166        }));
4167
4168        let result = explode_to_unnest(explode).unwrap();
4169        assert!(matches!(result, Expression::Unnest(_)));
4170    }
4171
4172    #[test]
4173    fn test_replace_bool_with_int() {
4174        let true_expr = Expression::Boolean(BooleanLiteral { value: true });
4175        let result = replace_bool_with_int(true_expr).unwrap();
4176        if let Expression::Literal(Literal::Number(n)) = result {
4177            assert_eq!(n, "1");
4178        } else {
4179            panic!("Expected number literal");
4180        }
4181
4182        let false_expr = Expression::Boolean(BooleanLiteral { value: false });
4183        let result = replace_bool_with_int(false_expr).unwrap();
4184        if let Expression::Literal(Literal::Number(n)) = result {
4185            assert_eq!(n, "0");
4186        } else {
4187            panic!("Expected number literal");
4188        }
4189    }
4190
4191    #[test]
4192    fn test_replace_int_with_bool() {
4193        let one_expr = Expression::Literal(Literal::Number("1".to_string()));
4194        let result = replace_int_with_bool(one_expr).unwrap();
4195        if let Expression::Boolean(b) = result {
4196            assert!(b.value);
4197        } else {
4198            panic!("Expected boolean true");
4199        }
4200
4201        let zero_expr = Expression::Literal(Literal::Number("0".to_string()));
4202        let result = replace_int_with_bool(zero_expr).unwrap();
4203        if let Expression::Boolean(b) = result {
4204            assert!(!b.value);
4205        } else {
4206            panic!("Expected boolean false");
4207        }
4208
4209        // Test that other numbers are not converted
4210        let two_expr = Expression::Literal(Literal::Number("2".to_string()));
4211        let result = replace_int_with_bool(two_expr).unwrap();
4212        assert!(matches!(result, Expression::Literal(Literal::Number(_))));
4213    }
4214
4215    #[test]
4216    fn test_strip_data_type_params() {
4217        // Test Decimal
4218        let decimal = DataType::Decimal {
4219            precision: Some(10),
4220            scale: Some(2),
4221        };
4222        let stripped = strip_data_type_params(decimal);
4223        assert_eq!(
4224            stripped,
4225            DataType::Decimal {
4226                precision: None,
4227                scale: None
4228            }
4229        );
4230
4231        // Test VarChar
4232        let varchar = DataType::VarChar { length: Some(255), parenthesized_length: false };
4233        let stripped = strip_data_type_params(varchar);
4234        assert_eq!(stripped, DataType::VarChar { length: None, parenthesized_length: false });
4235
4236        // Test Char
4237        let char_type = DataType::Char { length: Some(10) };
4238        let stripped = strip_data_type_params(char_type);
4239        assert_eq!(stripped, DataType::Char { length: None });
4240
4241        // Test Timestamp (preserve timezone)
4242        let timestamp = DataType::Timestamp {
4243            precision: Some(6),
4244            timezone: true,
4245        };
4246        let stripped = strip_data_type_params(timestamp);
4247        assert_eq!(
4248            stripped,
4249            DataType::Timestamp {
4250                precision: None,
4251                timezone: true
4252            }
4253        );
4254
4255        // Test Array (recursive)
4256        let array = DataType::Array {
4257            element_type: Box::new(DataType::VarChar { length: Some(100), parenthesized_length: false }),
4258            dimension: None,
4259        };
4260        let stripped = strip_data_type_params(array);
4261        assert_eq!(
4262            stripped,
4263            DataType::Array {
4264                element_type: Box::new(DataType::VarChar { length: None, parenthesized_length: false }),
4265                dimension: None,
4266            }
4267        );
4268
4269        // Test types without params are unchanged
4270        let text = DataType::Text;
4271        let stripped = strip_data_type_params(text);
4272        assert_eq!(stripped, DataType::Text);
4273    }
4274
4275    #[test]
4276    fn test_remove_precision_parameterized_types_cast() {
4277        // Create a CAST(1 AS DECIMAL(10, 2)) expression
4278        let cast_expr = Expression::Cast(Box::new(Cast {
4279            this: Expression::Literal(Literal::Number("1".to_string())),
4280            to: DataType::Decimal {
4281                precision: Some(10),
4282                scale: Some(2),
4283            },
4284            trailing_comments: vec![],
4285            double_colon_syntax: false,
4286            format: None,
4287            default: None,
4288        }));
4289
4290        let result = remove_precision_parameterized_types(cast_expr).unwrap();
4291        if let Expression::Cast(cast) = result {
4292            assert_eq!(
4293                cast.to,
4294                DataType::Decimal {
4295                    precision: None,
4296                    scale: None
4297                }
4298            );
4299        } else {
4300            panic!("Expected Cast expression");
4301        }
4302    }
4303
4304    #[test]
4305    fn test_remove_precision_parameterized_types_varchar() {
4306        // Create a CAST('hello' AS VARCHAR(10)) expression
4307        let cast_expr = Expression::Cast(Box::new(Cast {
4308            this: Expression::Literal(Literal::String("hello".to_string())),
4309            to: DataType::VarChar { length: Some(10), parenthesized_length: false },
4310            trailing_comments: vec![],
4311            double_colon_syntax: false,
4312            format: None,
4313            default: None,
4314        }));
4315
4316        let result = remove_precision_parameterized_types(cast_expr).unwrap();
4317        if let Expression::Cast(cast) = result {
4318            assert_eq!(cast.to, DataType::VarChar { length: None, parenthesized_length: false });
4319        } else {
4320            panic!("Expected Cast expression");
4321        }
4322    }
4323
4324    #[test]
4325    fn test_remove_precision_direct_cast() {
4326        // Test transform on a direct Cast expression (not nested in Select)
4327        // The current implementation handles top-level Cast expressions;
4328        // a full implementation would need recursive AST traversal
4329        let cast = Expression::Cast(Box::new(Cast {
4330            this: Expression::Literal(Literal::Number("1".to_string())),
4331            to: DataType::Decimal {
4332                precision: Some(10),
4333                scale: Some(2),
4334            },
4335            trailing_comments: vec![],
4336            double_colon_syntax: false,
4337            format: None,
4338            default: None,
4339        }));
4340
4341        let transformed = remove_precision_parameterized_types(cast).unwrap();
4342        let generated = gen(&transformed);
4343
4344        // Should now be DECIMAL without precision
4345        assert!(generated.contains("DECIMAL"));
4346        assert!(!generated.contains("(10"));
4347    }
4348
4349    #[test]
4350    fn test_epoch_cast_to_ts() {
4351        // Test CAST('epoch' AS TIMESTAMP) → CAST('1970-01-01 00:00:00' AS TIMESTAMP)
4352        let cast_expr = Expression::Cast(Box::new(Cast {
4353            this: Expression::Literal(Literal::String("epoch".to_string())),
4354            to: DataType::Timestamp {
4355                precision: None,
4356                timezone: false,
4357            },
4358            trailing_comments: vec![],
4359            double_colon_syntax: false,
4360            format: None,
4361            default: None,
4362        }));
4363
4364        let result = epoch_cast_to_ts(cast_expr).unwrap();
4365        if let Expression::Cast(cast) = result {
4366            if let Expression::Literal(Literal::String(s)) = cast.this {
4367                assert_eq!(s, "1970-01-01 00:00:00");
4368            } else {
4369                panic!("Expected string literal");
4370            }
4371        } else {
4372            panic!("Expected Cast expression");
4373        }
4374    }
4375
4376    #[test]
4377    fn test_epoch_cast_to_ts_preserves_non_epoch() {
4378        // Test that non-epoch strings are preserved
4379        let cast_expr = Expression::Cast(Box::new(Cast {
4380            this: Expression::Literal(Literal::String("2024-01-15".to_string())),
4381            to: DataType::Timestamp {
4382                precision: None,
4383                timezone: false,
4384            },
4385            trailing_comments: vec![],
4386            double_colon_syntax: false,
4387            format: None,
4388            default: None,
4389        }));
4390
4391        let result = epoch_cast_to_ts(cast_expr).unwrap();
4392        if let Expression::Cast(cast) = result {
4393            if let Expression::Literal(Literal::String(s)) = cast.this {
4394                assert_eq!(s, "2024-01-15");
4395            } else {
4396                panic!("Expected string literal");
4397            }
4398        } else {
4399            panic!("Expected Cast expression");
4400        }
4401    }
4402
4403    #[test]
4404    fn test_unqualify_columns() {
4405        // Test that table qualifiers are removed
4406        let col = Expression::Column(Column {
4407            name: Identifier::new("id".to_string()),
4408            table: Some(Identifier::new("users".to_string())),
4409            join_mark: false,
4410            trailing_comments: vec![],
4411        });
4412
4413        let result = unqualify_columns(col).unwrap();
4414        if let Expression::Column(c) = result {
4415            assert!(c.table.is_none());
4416            assert_eq!(c.name.name, "id");
4417        } else {
4418            panic!("Expected Column expression");
4419        }
4420    }
4421
4422    #[test]
4423    fn test_is_temporal_type() {
4424        assert!(is_temporal_type(&DataType::Date));
4425        assert!(is_temporal_type(&DataType::Timestamp {
4426            precision: None,
4427            timezone: false
4428        }));
4429        assert!(is_temporal_type(&DataType::Time { precision: None, timezone: false }));
4430        assert!(!is_temporal_type(&DataType::Int { length: None, integer_spelling: false }));
4431        assert!(!is_temporal_type(&DataType::VarChar { length: None, parenthesized_length: false }));
4432    }
4433
4434    #[test]
4435    fn test_eliminate_semi_join_basic() {
4436        use crate::expressions::{Join, TableRef};
4437
4438        // Test that semi joins are converted to EXISTS
4439        let select = Expression::Select(Box::new(Select {
4440            expressions: vec![Expression::Column(Column {
4441                name: Identifier::new("a".to_string()),
4442                table: None,
4443                join_mark: false,
4444                trailing_comments: vec![],
4445            })],
4446            from: Some(From {
4447                expressions: vec![Expression::Table(TableRef::new("t1"))],
4448            }),
4449            joins: vec![Join {
4450                this: Expression::Table(TableRef::new("t2")),
4451                kind: JoinKind::Semi,
4452                on: Some(Expression::Eq(Box::new(BinaryOp {
4453                    left: Expression::Column(Column {
4454                        name: Identifier::new("x".to_string()),
4455                        table: None,
4456                        join_mark: false,
4457                        trailing_comments: vec![],
4458                    }),
4459                    right: Expression::Column(Column {
4460                        name: Identifier::new("y".to_string()),
4461                        table: None,
4462                        join_mark: false,
4463                        trailing_comments: vec![],
4464                    }),
4465                    left_comments: vec![],
4466                    operator_comments: vec![],
4467                    trailing_comments: vec![],
4468                }))),
4469                using: vec![],
4470                use_inner_keyword: false,
4471                use_outer_keyword: false,
4472                deferred_condition: false,
4473                join_hint: None,
4474                match_condition: None,
4475                pivots: Vec::new(),
4476            }],
4477            ..Select::new()
4478        }));
4479
4480        let result = eliminate_semi_and_anti_joins(select).unwrap();
4481        if let Expression::Select(s) = result {
4482            // Semi join should be removed
4483            assert!(s.joins.is_empty());
4484            // WHERE clause should have EXISTS
4485            assert!(s.where_clause.is_some());
4486        } else {
4487            panic!("Expected Select expression");
4488        }
4489    }
4490
4491    #[test]
4492    fn test_no_ilike_sql() {
4493        use crate::expressions::LikeOp;
4494
4495        // Test ILIKE conversion to LOWER+LIKE
4496        let ilike_expr = Expression::ILike(Box::new(LikeOp {
4497            left: Expression::Column(Column {
4498                name: Identifier::new("name".to_string()),
4499                table: None,
4500                join_mark: false,
4501                trailing_comments: vec![],
4502            }),
4503            right: Expression::Literal(Literal::String("%test%".to_string())),
4504            escape: None,
4505            quantifier: None,
4506        }));
4507
4508        let result = no_ilike_sql(ilike_expr).unwrap();
4509        if let Expression::Like(like) = result {
4510            // Left should be LOWER(name)
4511            if let Expression::Function(f) = &like.left {
4512                assert_eq!(f.name, "LOWER");
4513            } else {
4514                panic!("Expected LOWER function on left");
4515            }
4516            // Right should be LOWER('%test%')
4517            if let Expression::Function(f) = &like.right {
4518                assert_eq!(f.name, "LOWER");
4519            } else {
4520                panic!("Expected LOWER function on right");
4521            }
4522        } else {
4523            panic!("Expected Like expression");
4524        }
4525    }
4526
4527    #[test]
4528    fn test_no_trycast_sql() {
4529        // Test TryCast conversion to Cast
4530        let trycast_expr = Expression::TryCast(Box::new(Cast {
4531            this: Expression::Literal(Literal::String("123".to_string())),
4532            to: DataType::Int { length: None, integer_spelling: false },
4533            trailing_comments: vec![],
4534            double_colon_syntax: false,
4535            format: None,
4536            default: None,
4537        }));
4538
4539        let result = no_trycast_sql(trycast_expr).unwrap();
4540        assert!(matches!(result, Expression::Cast(_)));
4541    }
4542
4543    #[test]
4544    fn test_no_safe_cast_sql() {
4545        // Test SafeCast conversion to Cast
4546        let safe_cast_expr = Expression::SafeCast(Box::new(Cast {
4547            this: Expression::Literal(Literal::String("123".to_string())),
4548            to: DataType::Int { length: None, integer_spelling: false },
4549            trailing_comments: vec![],
4550            double_colon_syntax: false,
4551            format: None,
4552            default: None,
4553        }));
4554
4555        let result = no_safe_cast_sql(safe_cast_expr).unwrap();
4556        assert!(matches!(result, Expression::Cast(_)));
4557    }
4558
4559    #[test]
4560    fn test_explode_to_unnest_presto() {
4561        let spark = Dialect::get(DialectType::Spark);
4562        let result = spark.transpile_to("SELECT EXPLODE(x) FROM tbl", DialectType::Presto).unwrap();
4563        assert_eq!(
4564            result[0],
4565            "SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col FROM tbl CROSS JOIN UNNEST(SEQUENCE(1, GREATEST(CARDINALITY(x)))) AS _u(pos) CROSS JOIN UNNEST(x) WITH ORDINALITY AS _u_2(col, pos_2) WHERE _u.pos = _u_2.pos_2 OR (_u.pos > CARDINALITY(x) AND _u_2.pos_2 = CARDINALITY(x))"
4566        );
4567    }
4568
4569    #[test]
4570    fn test_explode_to_unnest_bigquery() {
4571        let spark = Dialect::get(DialectType::Spark);
4572        let result = spark.transpile_to("SELECT EXPLODE(x) FROM tbl", DialectType::BigQuery).unwrap();
4573        assert_eq!(
4574            result[0],
4575            "SELECT IF(pos = pos_2, col, NULL) AS col FROM tbl CROSS JOIN UNNEST(GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1)) AS pos CROSS JOIN UNNEST(x) AS col WITH OFFSET AS pos_2 WHERE pos = pos_2 OR (pos > (ARRAY_LENGTH(x) - 1) AND pos_2 = (ARRAY_LENGTH(x) - 1))"
4576        );
4577    }
4578
4579}