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::dialects::transform_recursive;
10use crate::dialects::{Dialect, DialectType};
11use crate::error::{Error, Result};
12use crate::expressions::{
13    Alias, BinaryOp, BooleanLiteral, Cast, DataType, Exists, Expression, From, Function,
14    Identifier, Join, JoinKind, Lateral, LateralView, Literal, NamedArgSeparator, NamedArgument,
15    Over, Select, StructField, Subquery, Tuple, UnaryFunc, UnnestFunc, Where, WindowFunction, With,
16    WithinGroup,
17};
18use std::cell::RefCell;
19
20/// Apply a chain of transforms to an expression
21///
22/// # Arguments
23/// * `expr` - The expression to transform
24/// * `transforms` - A list of transform functions to apply in order
25///
26/// # Returns
27/// The transformed expression
28pub fn preprocess<F>(expr: Expression, transforms: &[F]) -> Result<Expression>
29where
30    F: Fn(Expression) -> Result<Expression>,
31{
32    let mut result = expr;
33    for transform in transforms {
34        result = transform(result)?;
35    }
36    Ok(result)
37}
38
39const MAX_TSQL_GROUPING_SETS: usize = 4096;
40
41/// Flatten nested GROUPING SETS and structural grouping tuples into syntax accepted by
42/// T-SQL and Fabric. Unlike GROUP BY DISTINCT expansion, this preserves duplicate sets
43/// and leaves ROLLUP/CUBE items unexpanded.
44#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
45pub(crate) fn normalize_grouping_sets_for_tsql(expr: Expression) -> Result<Expression> {
46    transform_recursive(expr, &|expr| {
47        let Expression::Select(mut select) = expr else {
48            return Ok(expr);
49        };
50
51        if let Some(group_by) = select.group_by.as_mut() {
52            group_by.expressions = std::mem::take(&mut group_by.expressions)
53                .into_iter()
54                .map(normalize_tsql_grouping_element)
55                .collect();
56        }
57
58        Ok(Expression::Select(select))
59    })
60}
61
62#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
63fn normalize_tsql_grouping_element(expression: Expression) -> Expression {
64    match expression {
65        Expression::Function(mut function)
66            if !function.quoted && function.name.eq_ignore_ascii_case("GROUPING SETS") =>
67        {
68            function.args = normalize_tsql_grouping_sets(std::mem::take(&mut function.args));
69            Expression::Function(function)
70        }
71        Expression::GroupingSets(mut grouping_sets) => {
72            grouping_sets.expressions =
73                normalize_tsql_grouping_sets(std::mem::take(&mut grouping_sets.expressions));
74            Expression::GroupingSets(grouping_sets)
75        }
76        Expression::Function(mut function)
77            if !function.quoted
78                && (function.name.eq_ignore_ascii_case("ROLLUP")
79                    || function.name.eq_ignore_ascii_case("CUBE")) =>
80        {
81            function.args = std::mem::take(&mut function.args)
82                .into_iter()
83                .map(normalize_tsql_grouping_unit)
84                .collect();
85            Expression::Function(function)
86        }
87        Expression::Rollup(mut rollup) => {
88            rollup.expressions = std::mem::take(&mut rollup.expressions)
89                .into_iter()
90                .map(normalize_tsql_grouping_unit)
91                .collect();
92            Expression::Rollup(rollup)
93        }
94        Expression::Cube(mut cube) => {
95            cube.expressions = std::mem::take(&mut cube.expressions)
96                .into_iter()
97                .map(normalize_tsql_grouping_unit)
98                .collect();
99            Expression::Cube(cube)
100        }
101        other => other,
102    }
103}
104
105#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
106fn normalize_tsql_grouping_sets(expressions: Vec<Expression>) -> Vec<Expression> {
107    let mut normalized = Vec::new();
108
109    for expression in expressions {
110        match expression {
111            Expression::Function(mut function)
112                if !function.quoted && function.name.eq_ignore_ascii_case("GROUPING SETS") =>
113            {
114                normalized.extend(normalize_tsql_grouping_sets(std::mem::take(
115                    &mut function.args,
116                )));
117            }
118            Expression::GroupingSets(mut grouping_sets) => {
119                normalized.extend(normalize_tsql_grouping_sets(std::mem::take(
120                    &mut grouping_sets.expressions,
121                )));
122            }
123            other => normalized.push(normalize_tsql_grouping_unit(
124                normalize_tsql_grouping_element(other),
125            )),
126        }
127    }
128
129    normalized
130}
131
132#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
133fn normalize_tsql_grouping_unit(expression: Expression) -> Expression {
134    let Expression::Tuple(tuple) = expression else {
135        return expression;
136    };
137
138    let mut expressions = Vec::new();
139    for expression in tuple.expressions {
140        append_tsql_grouping_unit(expression, &mut expressions);
141    }
142
143    Expression::Tuple(Box::new(Tuple { expressions }))
144}
145
146#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
147fn append_tsql_grouping_unit(expression: Expression, expressions: &mut Vec<Expression>) {
148    match expression {
149        Expression::Tuple(tuple) => {
150            for expression in tuple.expressions {
151                append_tsql_grouping_unit(expression, expressions);
152            }
153        }
154        Expression::Paren(paren) => append_tsql_grouping_unit(paren.this, expressions),
155        other => {
156            if !expressions.contains(&other) {
157                expressions.push(other);
158            }
159        }
160    }
161}
162
163/// Expand PostgreSQL-style GROUP BY DISTINCT over advanced grouping elements
164/// into a de-duplicated GROUPING SETS list accepted by T-SQL and Fabric.
165pub fn expand_distinct_grouping_sets_for_tsql(
166    expr: Expression,
167    target: DialectType,
168) -> Result<Expression> {
169    transform_recursive(expr, &|expr| {
170        let Expression::Select(mut select) = expr else {
171            return Ok(expr);
172        };
173
174        let Some(group_by) = select.group_by.as_mut() else {
175            return Ok(Expression::Select(select));
176        };
177        if group_by.all != Some(false) {
178            return Ok(Expression::Select(select));
179        }
180
181        let mut product = vec![Vec::new()];
182        let mut has_advanced_grouping = false;
183
184        for element in &group_by.expressions {
185            let alternatives = match expand_grouping_element(element, target)? {
186                Some(alternatives) => {
187                    has_advanced_grouping = true;
188                    alternatives
189                }
190                None => vec![vec![element.clone()]],
191            };
192
193            let expanded_len = product
194                .len()
195                .checked_mul(alternatives.len())
196                .filter(|len| *len <= MAX_TSQL_GROUPING_SETS)
197                .ok_or_else(|| grouping_set_expansion_error(target))?;
198            let mut next = Vec::with_capacity(expanded_len);
199
200            for left in &product {
201                for right in &alternatives {
202                    let mut combined = left.clone();
203                    for expression in right {
204                        if !combined.contains(expression) {
205                            combined.push(expression.clone());
206                        }
207                    }
208                    next.push(combined);
209                }
210            }
211            product = next;
212        }
213
214        if !has_advanced_grouping {
215            return Ok(Expression::Select(select));
216        }
217
218        let mut distinct_sets = Vec::with_capacity(product.len());
219        for grouping_set in product {
220            if !distinct_sets.contains(&grouping_set) {
221                distinct_sets.push(grouping_set);
222            }
223        }
224
225        let sets = distinct_sets
226            .into_iter()
227            .map(|expressions| Expression::Tuple(Box::new(Tuple { expressions })))
228            .collect();
229        group_by.all = None;
230        group_by.expressions = vec![Expression::Function(Box::new(Function::new(
231            "GROUPING SETS".to_string(),
232            sets,
233        )))];
234
235        Ok(Expression::Select(select))
236    })
237}
238
239fn expand_grouping_element(
240    expression: &Expression,
241    target: DialectType,
242) -> Result<Option<Vec<Vec<Expression>>>> {
243    match expression {
244        Expression::Function(function) if function.name.eq_ignore_ascii_case("ROLLUP") => {
245            Ok(Some(expand_rollup(&function.args)))
246        }
247        Expression::Function(function) if function.name.eq_ignore_ascii_case("CUBE") => {
248            Ok(Some(expand_cube(&function.args, target)?))
249        }
250        Expression::Function(function) if function.name.eq_ignore_ascii_case("GROUPING SETS") => {
251            Ok(Some(expand_explicit_grouping_sets(&function.args, target)?))
252        }
253        Expression::Rollup(rollup) => Ok(Some(expand_rollup(&rollup.expressions))),
254        Expression::Cube(cube) => Ok(Some(expand_cube(&cube.expressions, target)?)),
255        Expression::GroupingSets(grouping_sets) => Ok(Some(expand_explicit_grouping_sets(
256            &grouping_sets.expressions,
257            target,
258        )?)),
259        _ => Ok(None),
260    }
261}
262
263fn expand_rollup(elements: &[Expression]) -> Vec<Vec<Expression>> {
264    (0..=elements.len())
265        .rev()
266        .map(|end| flatten_grouping_units(&elements[..end]))
267        .collect()
268}
269
270fn expand_cube(elements: &[Expression], target: DialectType) -> Result<Vec<Vec<Expression>>> {
271    let set_count = 1usize
272        .checked_shl(elements.len() as u32)
273        .filter(|count| *count <= MAX_TSQL_GROUPING_SETS)
274        .ok_or_else(|| grouping_set_expansion_error(target))?;
275
276    Ok((0..set_count)
277        .rev()
278        .map(|mask| {
279            let selected = elements
280                .iter()
281                .enumerate()
282                .filter_map(|(index, element)| {
283                    let bit = elements.len() - index - 1;
284                    (mask & (1usize << bit) != 0).then_some(element)
285                })
286                .cloned()
287                .collect::<Vec<_>>();
288            flatten_grouping_units(&selected)
289        })
290        .collect())
291}
292
293fn expand_explicit_grouping_sets(
294    elements: &[Expression],
295    target: DialectType,
296) -> Result<Vec<Vec<Expression>>> {
297    let mut sets = Vec::new();
298    for element in elements {
299        if let Some(nested) = expand_grouping_element(element, target)? {
300            sets.extend(nested);
301        } else {
302            sets.push(flatten_grouping_unit(element));
303        }
304        if sets.len() > MAX_TSQL_GROUPING_SETS {
305            return Err(grouping_set_expansion_error(target));
306        }
307    }
308    Ok(sets)
309}
310
311fn flatten_grouping_units(elements: &[Expression]) -> Vec<Expression> {
312    elements.iter().flat_map(flatten_grouping_unit).collect()
313}
314
315fn flatten_grouping_unit(expression: &Expression) -> Vec<Expression> {
316    match expression {
317        Expression::Tuple(tuple) => tuple.expressions.clone(),
318        Expression::Paren(paren) => flatten_grouping_unit(&paren.this),
319        _ => vec![expression.clone()],
320    }
321}
322
323fn grouping_set_expansion_error(target: DialectType) -> Error {
324    Error::unsupported(
325        format!("GROUP BY DISTINCT expansion beyond {MAX_TSQL_GROUPING_SETS} grouping sets"),
326        target.to_string(),
327    )
328}
329
330/// Rewrite PostgreSQL ordered-set percentile aggregates grouped by ordinary
331/// GROUP BY expressions into T-SQL/Fabric's analytic percentile form.
332///
333/// PostgreSQL allows `PERCENTILE_CONT/DISC(p) WITHIN GROUP (...)` as grouped
334/// aggregates. T-SQL and Fabric expose the same functions as window functions,
335/// so the equivalent row-per-group shape uses an analytic percentile over the
336/// grouping keys. When all grouping keys are projected, `SELECT DISTINCT ...
337/// OVER (PARTITION BY group_key)` is sufficient. Otherwise a derived table
338/// retains the hidden grouping keys during deduplication and an outer SELECT
339/// restores the original projection shape.
340pub fn grouped_percentiles_to_tsql_windows(expr: Expression) -> Result<Expression> {
341    transform_recursive(expr, &grouped_percentiles_to_tsql_windows_inner)
342}
343
344fn grouped_percentiles_to_tsql_windows_inner(expr: Expression) -> Result<Expression> {
345    let Expression::Select(select) = expr else {
346        return Ok(expr);
347    };
348
349    rewrite_grouped_percentile_select(*select)
350}
351
352fn rewrite_grouped_percentile_select(mut select: Select) -> Result<Expression> {
353    let Some(group_by) = &select.group_by else {
354        return Ok(Expression::Select(Box::new(select)));
355    };
356
357    if select.having.is_some()
358        || group_by.all.is_some()
359        || group_by.totals
360        || group_by.expressions.is_empty()
361        || group_by.expressions.iter().any(is_complex_grouping_expr)
362    {
363        return Ok(Expression::Select(Box::new(select)));
364    }
365
366    let partition_by = group_by.expressions.clone();
367    let original_expressions = select.expressions.clone();
368    let mut changed = false;
369    let mut rewritten_expressions = Vec::with_capacity(select.expressions.len());
370
371    for expression in &select.expressions {
372        if is_group_projection(expression, &partition_by) {
373            rewritten_expressions.push(expression.clone());
374            continue;
375        }
376
377        let Some(rewritten) = rewrite_grouped_percentile_projection(expression, &partition_by)
378        else {
379            return Ok(Expression::Select(Box::new(select)));
380        };
381
382        changed = true;
383        rewritten_expressions.push(rewritten);
384    }
385
386    if !changed {
387        return Ok(Expression::Select(Box::new(select)));
388    }
389
390    let all_grouping_keys_projected = partition_by.iter().all(|group_expr| {
391        original_expressions
392            .iter()
393            .any(|projection| is_projection_of_group_expr(projection, group_expr))
394    });
395
396    if all_grouping_keys_projected {
397        select.expressions = rewritten_expressions;
398        select.group_by = None;
399        select.distinct = true;
400        return Ok(Expression::Select(Box::new(select)));
401    }
402
403    rewrite_grouped_percentile_select_with_hidden_groups(
404        select,
405        original_expressions,
406        rewritten_expressions,
407        partition_by,
408    )
409}
410
411fn is_complex_grouping_expr(expr: &Expression) -> bool {
412    matches!(
413        expr,
414        Expression::Cube(_) | Expression::Rollup(_) | Expression::GroupingSets(_)
415    ) || matches!(expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("GROUPING SETS"))
416}
417
418fn is_group_projection(expr: &Expression, group_by: &[Expression]) -> bool {
419    let inner = unalias_expression(expr);
420
421    group_by.iter().any(|group_expr| inner == group_expr)
422}
423
424fn is_projection_of_group_expr(projection: &Expression, group_expr: &Expression) -> bool {
425    unalias_expression(projection) == group_expr
426}
427
428fn unalias_expression(expr: &Expression) -> &Expression {
429    match expr {
430        Expression::Alias(alias) => &alias.this,
431        other => other,
432    }
433}
434
435fn rewrite_grouped_percentile_select_with_hidden_groups(
436    mut inner: Select,
437    original_expressions: Vec<Expression>,
438    rewritten_expressions: Vec<Expression>,
439    group_by: Vec<Expression>,
440) -> Result<Expression> {
441    let original_distinct = inner.distinct;
442
443    let mut used_column_names = original_expressions
444        .iter()
445        .filter_map(grouped_percentile_output_identifier)
446        .map(|identifier| identifier.name)
447        .collect::<Vec<_>>();
448    let projection_aliases = (0..rewritten_expressions.len())
449        .map(|index| {
450            Identifier::new(fresh_grouped_percentile_name(
451                &mut used_column_names,
452                &format!("_polyglot_projection_{index}"),
453            ))
454        })
455        .collect::<Vec<_>>();
456    let group_aliases = (0..group_by.len())
457        .map(|index| {
458            Identifier::new(fresh_grouped_percentile_name(
459                &mut used_column_names,
460                &format!("_polyglot_group_{index}"),
461            ))
462        })
463        .collect::<Vec<_>>();
464
465    let mut used_source_names = Vec::new();
466    if let Some(from) = &inner.from {
467        for source in &from.expressions {
468            collect_grouped_percentile_source_names(source, &mut used_source_names);
469        }
470    }
471    for join in &inner.joins {
472        collect_grouped_percentile_source_names(&join.this, &mut used_source_names);
473    }
474    let derived_alias = Identifier::new(fresh_grouped_percentile_name(
475        &mut used_source_names,
476        "_polyglot_grouped_percentile",
477    ));
478
479    let mut inner_expressions = rewritten_expressions
480        .into_iter()
481        .zip(&projection_aliases)
482        .map(|(expression, alias)| {
483            Expression::Alias(Box::new(Alias::new(
484                take_alias_expression(expression),
485                alias.clone(),
486            )))
487        })
488        .collect::<Vec<_>>();
489    inner_expressions.extend(group_by.iter().cloned().zip(&group_aliases).map(
490        |(expression, alias)| Expression::Alias(Box::new(Alias::new(expression, alias.clone()))),
491    ));
492
493    let outer_order_by = inner.order_by.take();
494    let outer_with = inner.with.take();
495    let outer_top = inner.top.take();
496    let outer_limit = inner.limit.take();
497    let outer_offset = inner.offset.take();
498    let outer_limit_by = inner.limit_by.take();
499    let outer_fetch = inner.fetch.take();
500    let outer_distribute_by = inner.distribute_by.take();
501    let outer_cluster_by = inner.cluster_by.take();
502    let outer_sort_by = inner.sort_by.take();
503    let outer_settings = inner.settings.take();
504    let outer_format = inner.format.take();
505    let outer_hint = inner.hint.take();
506    let outer_into = inner.into.take();
507    let outer_locks = std::mem::take(&mut inner.locks);
508    let outer_for_xml = std::mem::take(&mut inner.for_xml);
509    let outer_for_json = std::mem::take(&mut inner.for_json);
510    let outer_leading_comments = std::mem::take(&mut inner.leading_comments);
511    let outer_post_select_comments = std::mem::take(&mut inner.post_select_comments);
512    let outer_kind = inner.kind.take();
513    let outer_operation_modifiers = std::mem::take(&mut inner.operation_modifiers);
514    let outer_option = inner.option.take();
515    let outer_exclude = inner.exclude.take();
516
517    inner.expressions = inner_expressions;
518    inner.group_by = None;
519    inner.distinct = true;
520
521    let outer_expressions = original_expressions
522        .iter()
523        .zip(&projection_aliases)
524        .map(|(original, internal_alias)| {
525            let reference = grouped_percentile_derived_column(&derived_alias, internal_alias);
526            match original {
527                Expression::Alias(alias) => {
528                    let mut outer_alias = alias.as_ref().clone();
529                    outer_alias.this = reference;
530                    Expression::Alias(Box::new(outer_alias))
531                }
532                _ => grouped_percentile_output_identifier(original)
533                    .map_or(reference.clone(), |output_alias| {
534                        Expression::Alias(Box::new(Alias::new(reference, output_alias)))
535                    }),
536            }
537        })
538        .collect::<Vec<_>>();
539
540    let outer_order_by = outer_order_by
541        .map(|mut order_by| {
542            for ordered in &mut order_by.expressions {
543                ordered.this = rewrite_grouped_percentile_outer_reference(
544                    std::mem::replace(
545                        &mut ordered.this,
546                        Expression::Null(crate::expressions::Null),
547                    ),
548                    &original_expressions,
549                    &projection_aliases,
550                    &group_by,
551                    &group_aliases,
552                    &derived_alias,
553                )?;
554            }
555            Ok(order_by)
556        })
557        .transpose()?;
558
559    let subquery = Subquery {
560        this: Expression::Select(Box::new(inner)),
561        alias: Some(derived_alias),
562        column_aliases: Vec::new(),
563        alias_explicit_as: true,
564        alias_keyword: None,
565        order_by: None,
566        limit: None,
567        offset: None,
568        distribute_by: None,
569        sort_by: None,
570        cluster_by: None,
571        lateral: false,
572        modifiers_inside: false,
573        trailing_comments: Vec::new(),
574        inferred_type: None,
575    };
576
577    let mut outer = Select::new();
578    outer.expressions = outer_expressions;
579    outer.from = Some(From {
580        expressions: vec![Expression::Subquery(Box::new(subquery))],
581    });
582    outer.distinct = original_distinct;
583    outer.with = outer_with;
584    outer.order_by = outer_order_by;
585    outer.top = outer_top;
586    outer.limit = outer_limit;
587    outer.offset = outer_offset;
588    outer.limit_by = outer_limit_by;
589    outer.fetch = outer_fetch;
590    outer.distribute_by = outer_distribute_by;
591    outer.cluster_by = outer_cluster_by;
592    outer.sort_by = outer_sort_by;
593    outer.settings = outer_settings;
594    outer.format = outer_format;
595    outer.hint = outer_hint;
596    outer.into = outer_into;
597    outer.locks = outer_locks;
598    outer.for_xml = outer_for_xml;
599    outer.for_json = outer_for_json;
600    outer.leading_comments = outer_leading_comments;
601    outer.post_select_comments = outer_post_select_comments;
602    outer.kind = outer_kind;
603    outer.operation_modifiers = outer_operation_modifiers;
604    outer.option = outer_option;
605    outer.exclude = outer_exclude;
606
607    Ok(Expression::Select(Box::new(outer)))
608}
609
610fn take_alias_expression(expression: Expression) -> Expression {
611    match expression {
612        Expression::Alias(alias) => alias.this,
613        other => other,
614    }
615}
616
617fn grouped_percentile_output_identifier(expression: &Expression) -> Option<Identifier> {
618    match expression {
619        Expression::Alias(alias) if !alias.alias.is_empty() => Some(alias.alias.clone()),
620        Expression::Column(column) => Some(column.name.clone()),
621        Expression::Identifier(identifier) => Some(identifier.clone()),
622        Expression::Function(function) => Some(if function.quoted {
623            Identifier::quoted(function.name.clone())
624        } else {
625            Identifier::new(function.name.to_ascii_lowercase())
626        }),
627        Expression::AggregateFunction(function) => {
628            Some(Identifier::new(function.name.to_ascii_lowercase()))
629        }
630        Expression::WithinGroup(within_group) => {
631            grouped_percentile_output_identifier(&within_group.this)
632        }
633        Expression::PercentileCont(_) => Some(Identifier::new("percentile_cont")),
634        Expression::PercentileDisc(_) => Some(Identifier::new("percentile_disc")),
635        _ => None,
636    }
637}
638
639fn fresh_grouped_percentile_name(used_names: &mut Vec<String>, base: &str) -> String {
640    let mut suffix = 1;
641    loop {
642        let candidate = if suffix == 1 {
643            base.to_string()
644        } else {
645            format!("{base}_{suffix}")
646        };
647        if !used_names
648            .iter()
649            .any(|name| name.eq_ignore_ascii_case(&candidate))
650        {
651            used_names.push(candidate.clone());
652            return candidate;
653        }
654        suffix += 1;
655    }
656}
657
658fn collect_grouped_percentile_source_names(expression: &Expression, names: &mut Vec<String>) {
659    match expression {
660        Expression::Alias(alias) if !alias.alias.is_empty() => names.push(alias.alias.name.clone()),
661        Expression::Subquery(subquery) => {
662            if let Some(alias) = &subquery.alias {
663                names.push(alias.name.clone());
664            }
665        }
666        Expression::Table(table) => {
667            names.push(table.alias.as_ref().unwrap_or(&table.name).name.clone())
668        }
669        _ => {}
670    }
671}
672
673fn grouped_percentile_derived_column(
674    derived_alias: &Identifier,
675    column_alias: &Identifier,
676) -> Expression {
677    Expression::qualified_column(derived_alias.name.clone(), column_alias.name.clone())
678}
679
680fn rewrite_grouped_percentile_outer_reference(
681    expression: Expression,
682    original_expressions: &[Expression],
683    projection_aliases: &[Identifier],
684    group_by: &[Expression],
685    group_aliases: &[Identifier],
686    derived_alias: &Identifier,
687) -> Result<Expression> {
688    transform_recursive(expression, &|node| {
689        if let Some((_, alias)) =
690            original_expressions
691                .iter()
692                .zip(projection_aliases)
693                .find(|(original, _)| {
694                    unalias_expression(original) == &node
695                        || grouped_percentile_output_identifier(original).is_some_and(
696                            |identifier| expression_references_identifier(&node, &identifier),
697                        )
698                })
699        {
700            return Ok(grouped_percentile_derived_column(derived_alias, alias));
701        }
702
703        if let Some((_, alias)) = group_by
704            .iter()
705            .zip(group_aliases)
706            .find(|(group_expr, _)| *group_expr == &node)
707        {
708            return Ok(grouped_percentile_derived_column(derived_alias, alias));
709        }
710
711        Ok(node)
712    })
713}
714
715fn expression_references_identifier(expression: &Expression, identifier: &Identifier) -> bool {
716    let referenced = match expression {
717        Expression::Column(column) if column.table.is_none() => Some(&column.name),
718        Expression::Identifier(referenced) => Some(referenced),
719        _ => None,
720    };
721
722    referenced.is_some_and(|referenced| {
723        if referenced.quoted || identifier.quoted {
724            referenced.name == identifier.name
725        } else {
726            referenced.name.eq_ignore_ascii_case(&identifier.name)
727        }
728    })
729}
730
731fn rewrite_grouped_percentile_projection(
732    expr: &Expression,
733    partition_by: &[Expression],
734) -> Option<Expression> {
735    match expr {
736        Expression::Alias(alias) => {
737            let rewritten = rewrite_grouped_percentile_expr(&alias.this, partition_by)?;
738            let mut alias = alias.as_ref().clone();
739            alias.this = rewritten;
740            Some(Expression::Alias(Box::new(alias)))
741        }
742        other => rewrite_grouped_percentile_expr(other, partition_by),
743    }
744}
745
746fn rewrite_grouped_percentile_expr(
747    expr: &Expression,
748    partition_by: &[Expression],
749) -> Option<Expression> {
750    let Expression::WithinGroup(within_group) = expr else {
751        return None;
752    };
753
754    if !is_percentile_ordered_set(&within_group.this) || within_group.order_by.len() != 1 {
755        return None;
756    }
757
758    let mut order_by = within_group.order_by.clone();
759    // T-SQL/Fabric percentile functions allow a single ORDER BY expression.
760    // They ignore NULL inputs, so PostgreSQL null-order emulation would be both
761    // unnecessary and invalid here.
762    order_by[0].nulls_first = None;
763
764    Some(Expression::WindowFunction(Box::new(WindowFunction {
765        this: Expression::WithinGroup(Box::new(WithinGroup {
766            this: within_group.this.clone(),
767            order_by,
768        })),
769        over: Over {
770            window_name: None,
771            partition_by: partition_by.to_vec(),
772            order_by: Vec::new(),
773            frame: None,
774            alias: None,
775        },
776        keep: None,
777        inferred_type: None,
778    })))
779}
780
781fn is_percentile_ordered_set(expr: &Expression) -> bool {
782    match expr {
783        Expression::Function(function) => is_percentile_name(&function.name),
784        Expression::AggregateFunction(function) => is_percentile_name(&function.name),
785        Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
786        _ => false,
787    }
788}
789
790fn is_percentile_name(name: &str) -> bool {
791    name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
792}
793
794/// Convert UNNEST to EXPLODE (for Spark/Hive compatibility)
795///
796/// UNNEST is standard SQL but Spark uses EXPLODE instead.
797pub fn unnest_to_explode(expr: Expression) -> Result<Expression> {
798    match expr {
799        Expression::Unnest(unnest) => {
800            Ok(Expression::Explode(Box::new(UnaryFunc::new(unnest.this))))
801        }
802        _ => Ok(expr),
803    }
804}
805
806/// Convert CROSS JOIN UNNEST to LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks.
807///
808/// This is a SELECT-level structural transformation that:
809/// 1. Converts UNNEST in FROM clause to INLINE/EXPLODE
810/// 2. Converts CROSS JOIN (LATERAL) UNNEST to LATERAL VIEW entries
811/// 3. For single-arg UNNEST: uses EXPLODE
812/// 4. For multi-arg UNNEST: uses INLINE(ARRAYS_ZIP(...))
813///
814/// Based on Python sqlglot's `unnest_to_explode` transform in transforms.py (lines 290-391).
815pub fn unnest_to_explode_select(expr: Expression) -> Result<Expression> {
816    transform_recursive(expr, &unnest_to_explode_select_inner)
817}
818
819/// Helper to determine the UDTF function for an UNNEST expression.
820/// Single-arg UNNEST → EXPLODE, multi-arg → INLINE
821fn make_udtf_expr(unnest: &UnnestFunc) -> Expression {
822    let has_multi_expr = !unnest.expressions.is_empty();
823    if has_multi_expr {
824        // Multi-arg: INLINE(ARRAYS_ZIP(arg1, arg2, ...))
825        let mut all_args = vec![unnest.this.clone()];
826        all_args.extend(unnest.expressions.iter().cloned());
827        let arrays_zip =
828            Expression::Function(Box::new(Function::new("ARRAYS_ZIP".to_string(), all_args)));
829        Expression::Function(Box::new(Function::new(
830            "INLINE".to_string(),
831            vec![arrays_zip],
832        )))
833    } else {
834        // Single-arg: EXPLODE(arg)
835        Expression::Explode(Box::new(UnaryFunc::new(unnest.this.clone())))
836    }
837}
838
839fn unnest_to_explode_select_inner(expr: Expression) -> Result<Expression> {
840    let Expression::Select(mut select) = expr else {
841        return Ok(expr);
842    };
843
844    // Process FROM clause: UNNEST items need conversion
845    if let Some(ref mut from) = select.from {
846        if from.expressions.len() >= 1 {
847            let mut new_from_exprs = Vec::new();
848            let mut new_lateral_views = Vec::new();
849            let first_is_unnest = is_unnest_expr(&from.expressions[0]);
850
851            for (idx, from_item) in from.expressions.drain(..).enumerate() {
852                if idx == 0 && first_is_unnest {
853                    // UNNEST is the first (and possibly only) item in FROM
854                    // Replace it with INLINE/EXPLODE, keeping alias
855                    let replaced = replace_from_unnest(from_item);
856                    new_from_exprs.push(replaced);
857                } else if idx > 0 && is_unnest_expr(&from_item) {
858                    // Additional UNNEST items in FROM (comma-joined) → LATERAL VIEW
859                    let (alias_name, column_aliases, unnest_func) = extract_unnest_info(from_item);
860                    let udtf = make_udtf_expr(&unnest_func);
861                    new_lateral_views.push(LateralView {
862                        this: udtf,
863                        table_alias: alias_name,
864                        column_aliases,
865                        outer: false,
866                    });
867                } else {
868                    new_from_exprs.push(from_item);
869                }
870            }
871
872            from.expressions = new_from_exprs;
873            // Append lateral views for comma-joined UNNESTs
874            select.lateral_views.extend(new_lateral_views);
875        }
876    }
877
878    // Process joins: CROSS JOIN (LATERAL) UNNEST → LATERAL VIEW
879    let mut remaining_joins = Vec::new();
880    for join in select.joins.drain(..) {
881        if matches!(join.kind, JoinKind::Cross | JoinKind::Inner) {
882            let (is_unnest, is_lateral) = check_join_unnest(&join.this);
883            if is_unnest {
884                // Extract UNNEST info from join, handling Lateral wrapper
885                let (lateral_alias, lateral_col_aliases, join_expr) = if is_lateral {
886                    if let Expression::Lateral(lat) = join.this {
887                        // Extract alias from Lateral struct
888                        let alias = lat.alias.map(|s| Identifier::new(&s));
889                        let col_aliases: Vec<Identifier> = lat
890                            .column_aliases
891                            .iter()
892                            .map(|s| Identifier::new(s))
893                            .collect();
894                        (alias, col_aliases, *lat.this)
895                    } else {
896                        (None, Vec::new(), join.this)
897                    }
898                } else {
899                    (None, Vec::new(), join.this)
900                };
901
902                let (alias_name, column_aliases, unnest_func) = extract_unnest_info(join_expr);
903
904                // Prefer Lateral's alias over UNNEST's alias
905                let final_alias = lateral_alias.or(alias_name);
906                let final_col_aliases = if !lateral_col_aliases.is_empty() {
907                    lateral_col_aliases
908                } else {
909                    column_aliases
910                };
911
912                // Use "unnest" as default alias if none provided (for single-arg case)
913                let table_alias = final_alias.or_else(|| Some(Identifier::new("unnest")));
914                let col_aliases = if final_col_aliases.is_empty() {
915                    vec![Identifier::new("unnest")]
916                } else {
917                    final_col_aliases
918                };
919
920                let udtf = make_udtf_expr(&unnest_func);
921                select.lateral_views.push(LateralView {
922                    this: udtf,
923                    table_alias,
924                    column_aliases: col_aliases,
925                    outer: false,
926                });
927            } else {
928                remaining_joins.push(join);
929            }
930        } else {
931            remaining_joins.push(join);
932        }
933    }
934    select.joins = remaining_joins;
935
936    Ok(Expression::Select(select))
937}
938
939/// Check if an expression is or wraps an UNNEST
940fn is_unnest_expr(expr: &Expression) -> bool {
941    match expr {
942        Expression::Unnest(_) => true,
943        Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
944        _ => false,
945    }
946}
947
948/// Check if a join's expression is an UNNEST (possibly wrapped in Lateral)
949fn check_join_unnest(expr: &Expression) -> (bool, bool) {
950    match expr {
951        Expression::Unnest(_) => (true, false),
952        Expression::Alias(a) => {
953            if matches!(a.this, Expression::Unnest(_)) {
954                (true, false)
955            } else {
956                (false, false)
957            }
958        }
959        Expression::Lateral(lat) => match &*lat.this {
960            Expression::Unnest(_) => (true, true),
961            Expression::Alias(a) => {
962                if matches!(a.this, Expression::Unnest(_)) {
963                    (true, true)
964                } else {
965                    (false, true)
966                }
967            }
968            _ => (false, true),
969        },
970        _ => (false, false),
971    }
972}
973
974/// Replace an UNNEST in FROM with INLINE/EXPLODE, preserving alias structure
975fn replace_from_unnest(from_item: Expression) -> Expression {
976    match from_item {
977        Expression::Alias(mut a) => {
978            if let Expression::Unnest(unnest) = a.this {
979                a.this = make_udtf_expr(&unnest);
980            }
981            Expression::Alias(a)
982        }
983        Expression::Unnest(unnest) => make_udtf_expr(&unnest),
984        other => other,
985    }
986}
987
988/// Extract alias info and UnnestFunc from an expression (possibly wrapped in Alias)
989fn extract_unnest_info(expr: Expression) -> (Option<Identifier>, Vec<Identifier>, UnnestFunc) {
990    match expr {
991        Expression::Alias(a) => {
992            if let Expression::Unnest(unnest) = a.this {
993                (Some(a.alias), a.column_aliases, *unnest)
994            } else {
995                // Should not happen if we already checked is_unnest_expr
996                (
997                    Some(a.alias),
998                    a.column_aliases,
999                    UnnestFunc {
1000                        this: a.this,
1001                        expressions: Vec::new(),
1002                        with_ordinality: false,
1003                        alias: None,
1004                        offset_alias: None,
1005                        inferred_type: None,
1006                    },
1007                )
1008            }
1009        }
1010        Expression::Unnest(unnest) => {
1011            let alias = unnest.alias.clone();
1012            (alias, Vec::new(), *unnest)
1013        }
1014        _ => (
1015            None,
1016            Vec::new(),
1017            UnnestFunc {
1018                this: expr,
1019                expressions: Vec::new(),
1020                with_ordinality: false,
1021                alias: None,
1022                offset_alias: None,
1023                inferred_type: None,
1024            },
1025        ),
1026    }
1027}
1028
1029/// Convert EXPLODE to UNNEST (for standard SQL compatibility)
1030pub fn explode_to_unnest(expr: Expression) -> Result<Expression> {
1031    match expr {
1032        Expression::Explode(explode) => Ok(Expression::Unnest(Box::new(UnnestFunc {
1033            this: explode.this,
1034            expressions: Vec::new(),
1035            with_ordinality: false,
1036            alias: None,
1037            offset_alias: None,
1038            inferred_type: None,
1039        }))),
1040        _ => Ok(expr),
1041    }
1042}
1043
1044/// Replace boolean literals for dialects that don't support them
1045///
1046/// Converts TRUE/FALSE to 1/0 for dialects like older MySQL versions
1047pub fn replace_bool_with_int(expr: Expression) -> Result<Expression> {
1048    match expr {
1049        Expression::Boolean(b) => {
1050            let value = if b.value { "1" } else { "0" };
1051            Ok(Expression::Literal(Box::new(Literal::Number(
1052                value.to_string(),
1053            ))))
1054        }
1055        _ => Ok(expr),
1056    }
1057}
1058
1059/// Replace integer literals for dialects that prefer boolean
1060///
1061/// Converts 1/0 to TRUE/FALSE
1062pub fn replace_int_with_bool(expr: Expression) -> Result<Expression> {
1063    match expr {
1064        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1" || n == "0") =>
1065        {
1066            let Literal::Number(n) = lit.as_ref() else {
1067                unreachable!()
1068            };
1069            Ok(Expression::Boolean(BooleanLiteral { value: n == "1" }))
1070        }
1071        _ => Ok(expr),
1072    }
1073}
1074
1075/// Remove precision from parameterized types
1076///
1077/// Some dialects don't support precision parameters on certain types.
1078/// This transform removes them, e.g., VARCHAR(255) → VARCHAR, DECIMAL(10,2) → DECIMAL
1079pub fn remove_precision_parameterized_types(expr: Expression) -> Result<Expression> {
1080    Ok(strip_type_params_recursive(expr))
1081}
1082
1083/// Recursively strip type parameters from DataType values in an expression
1084fn strip_type_params_recursive(expr: Expression) -> Expression {
1085    match expr {
1086        // Handle Cast expressions - strip precision from target type
1087        Expression::Cast(mut cast) => {
1088            cast.to = strip_data_type_params(cast.to);
1089            // Also recursively process the expression being cast
1090            cast.this = strip_type_params_recursive(cast.this);
1091            Expression::Cast(cast)
1092        }
1093        // Handle TryCast expressions (uses same Cast struct)
1094        Expression::TryCast(mut try_cast) => {
1095            try_cast.to = strip_data_type_params(try_cast.to);
1096            try_cast.this = strip_type_params_recursive(try_cast.this);
1097            Expression::TryCast(try_cast)
1098        }
1099        // Handle SafeCast expressions (uses same Cast struct)
1100        Expression::SafeCast(mut safe_cast) => {
1101            safe_cast.to = strip_data_type_params(safe_cast.to);
1102            safe_cast.this = strip_type_params_recursive(safe_cast.this);
1103            Expression::SafeCast(safe_cast)
1104        }
1105        // For now, pass through other expressions
1106        // A full implementation would recursively visit all nodes
1107        _ => expr,
1108    }
1109}
1110
1111/// Strip precision/scale/length parameters from a DataType
1112fn strip_data_type_params(dt: DataType) -> DataType {
1113    match dt {
1114        // Numeric types with precision/scale
1115        DataType::Decimal { .. } => DataType::Decimal {
1116            precision: None,
1117            scale: None,
1118        },
1119        DataType::TinyInt { .. } => DataType::TinyInt { length: None },
1120        DataType::SmallInt { .. } => DataType::SmallInt { length: None },
1121        DataType::Int { .. } => DataType::Int {
1122            length: None,
1123            integer_spelling: false,
1124        },
1125        DataType::BigInt { .. } => DataType::BigInt { length: None },
1126
1127        // String types with length
1128        DataType::Char { .. } => DataType::Char { length: None },
1129        DataType::VarChar { .. } => DataType::VarChar {
1130            length: None,
1131            parenthesized_length: false,
1132        },
1133
1134        // Binary types with length
1135        DataType::Binary { .. } => DataType::Binary { length: None },
1136        DataType::VarBinary { .. } => DataType::VarBinary { length: None },
1137
1138        // Bit types with length
1139        DataType::Bit { .. } => DataType::Bit { length: None },
1140        DataType::VarBit { .. } => DataType::VarBit { length: None },
1141
1142        // Time types with precision
1143        DataType::Time { .. } => DataType::Time {
1144            precision: None,
1145            timezone: false,
1146        },
1147        DataType::Timestamp { timezone, .. } => DataType::Timestamp {
1148            precision: None,
1149            timezone,
1150        },
1151
1152        // Array - recursively strip element type
1153        DataType::Array {
1154            element_type,
1155            dimension,
1156        } => DataType::Array {
1157            element_type: Box::new(strip_data_type_params(*element_type)),
1158            dimension,
1159        },
1160
1161        // Map - recursively strip key and value types
1162        DataType::Map {
1163            key_type,
1164            value_type,
1165        } => DataType::Map {
1166            key_type: Box::new(strip_data_type_params(*key_type)),
1167            value_type: Box::new(strip_data_type_params(*value_type)),
1168        },
1169
1170        // Struct - recursively strip field types
1171        DataType::Struct { fields, nested } => DataType::Struct {
1172            fields: fields
1173                .into_iter()
1174                .map(|f| {
1175                    StructField::with_options(
1176                        f.name,
1177                        strip_data_type_params(f.data_type),
1178                        f.options,
1179                    )
1180                })
1181                .collect(),
1182            nested,
1183        },
1184
1185        // Vector - strip dimension
1186        DataType::Vector { element_type, .. } => DataType::Vector {
1187            element_type: element_type.map(|et| Box::new(strip_data_type_params(*et))),
1188            dimension: None,
1189        },
1190
1191        // Object - recursively strip field types
1192        DataType::Object { fields, modifier } => DataType::Object {
1193            fields: fields
1194                .into_iter()
1195                .map(|(name, ty, not_null)| (name, strip_data_type_params(ty), not_null))
1196                .collect(),
1197            modifier,
1198        },
1199
1200        // Other types pass through unchanged
1201        other => other,
1202    }
1203}
1204
1205/// Eliminate QUALIFY clause by converting to a subquery with WHERE filter
1206///
1207/// QUALIFY is supported by Snowflake, BigQuery, and DuckDB but not by most other dialects.
1208///
1209/// Converts:
1210/// ```sql
1211/// SELECT * FROM t QUALIFY ROW_NUMBER() OVER (...) = 1
1212/// ```
1213/// To:
1214/// ```sql
1215/// SELECT * FROM (SELECT *, ROW_NUMBER() OVER (...) AS _w FROM t) _t WHERE _w = 1
1216/// ```
1217///
1218/// Reference: `transforms.py:194-255`
1219pub fn eliminate_qualify(expr: Expression) -> Result<Expression> {
1220    match expr {
1221        Expression::Select(mut select) => {
1222            if let Some(qualify) = select.qualify.take() {
1223                // Python sqlglot approach:
1224                // 1. Extract the window function from the qualify condition
1225                // 2. Add it as _w alias to the inner select
1226                // 3. Replace the window function reference with _w in the outer WHERE
1227                // 4. Keep original select expressions in the outer query
1228
1229                let qualify_filter = qualify.this;
1230                let window_alias_name = "_w".to_string();
1231                let window_alias_ident = Identifier::new(window_alias_name.clone());
1232
1233                // Try to extract window function from comparison
1234                // Pattern: WINDOW_FUNC = value -> inner adds WINDOW_FUNC AS _w, outer WHERE _w = value
1235                let (window_expr, outer_where) =
1236                    extract_window_from_condition(qualify_filter.clone(), &window_alias_ident);
1237
1238                if let Some(win_expr) = window_expr {
1239                    // Add window function as _w alias to inner select
1240                    let window_alias_expr =
1241                        Expression::Alias(Box::new(crate::expressions::Alias {
1242                            this: win_expr,
1243                            alias: window_alias_ident.clone(),
1244                            column_aliases: vec![],
1245                            alias_explicit_as: false,
1246                            alias_keyword: None,
1247                            pre_alias_comments: vec![],
1248                            trailing_comments: vec![],
1249                            inferred_type: None,
1250                        }));
1251
1252                    // For the outer SELECT, replace aliased expressions with just the alias reference
1253                    // e.g., `1 AS other_id` in inner -> `other_id` in outer
1254                    // Non-aliased expressions (columns, identifiers) stay as-is
1255                    let outer_exprs: Vec<Expression> = select
1256                        .expressions
1257                        .iter()
1258                        .map(|expr| {
1259                            if let Expression::Alias(a) = expr {
1260                                // Replace with just the alias identifier as a column reference
1261                                Expression::Column(Box::new(crate::expressions::Column {
1262                                    name: a.alias.clone(),
1263                                    table: None,
1264                                    join_mark: false,
1265                                    trailing_comments: vec![],
1266                                    span: None,
1267                                    inferred_type: None,
1268                                }))
1269                            } else {
1270                                expr.clone()
1271                            }
1272                        })
1273                        .collect();
1274                    select.expressions.push(window_alias_expr);
1275
1276                    // Create the inner subquery
1277                    let inner_select = Expression::Select(select);
1278                    let subquery = Subquery {
1279                        this: inner_select,
1280                        alias: Some(Identifier::new("_t".to_string())),
1281                        column_aliases: vec![],
1282                        alias_explicit_as: false,
1283                        alias_keyword: None,
1284                        order_by: None,
1285                        limit: None,
1286                        offset: None,
1287                        distribute_by: None,
1288                        sort_by: None,
1289                        cluster_by: None,
1290                        lateral: false,
1291                        modifiers_inside: false,
1292                        trailing_comments: vec![],
1293                        inferred_type: None,
1294                    };
1295
1296                    // Create the outer SELECT with alias-resolved expressions and WHERE _w <op> value
1297                    let outer_select = Select {
1298                        vertica: None,
1299                        expressions: outer_exprs,
1300                        from: Some(From {
1301                            expressions: vec![Expression::Subquery(Box::new(subquery))],
1302                        }),
1303                        where_clause: Some(Where { this: outer_where }),
1304                        ..Select::new()
1305                    };
1306
1307                    return Ok(Expression::Select(Box::new(outer_select)));
1308                } else {
1309                    // Fallback: if we can't extract a window function, use old approach
1310                    let qualify_alias = Expression::Alias(Box::new(crate::expressions::Alias {
1311                        this: qualify_filter.clone(),
1312                        alias: window_alias_ident.clone(),
1313                        column_aliases: vec![],
1314                        alias_explicit_as: false,
1315                        alias_keyword: None,
1316                        pre_alias_comments: vec![],
1317                        trailing_comments: vec![],
1318                        inferred_type: None,
1319                    }));
1320
1321                    let original_exprs = select.expressions.clone();
1322                    select.expressions.push(qualify_alias);
1323
1324                    let inner_select = Expression::Select(select);
1325                    let subquery = Subquery {
1326                        this: inner_select,
1327                        alias: Some(Identifier::new("_t".to_string())),
1328                        column_aliases: vec![],
1329                        alias_explicit_as: false,
1330                        alias_keyword: None,
1331                        order_by: None,
1332                        limit: None,
1333                        offset: None,
1334                        distribute_by: None,
1335                        sort_by: None,
1336                        cluster_by: None,
1337                        lateral: false,
1338                        modifiers_inside: false,
1339                        trailing_comments: vec![],
1340                        inferred_type: None,
1341                    };
1342
1343                    let outer_select = Select {
1344                        vertica: None,
1345                        expressions: original_exprs,
1346                        from: Some(From {
1347                            expressions: vec![Expression::Subquery(Box::new(subquery))],
1348                        }),
1349                        where_clause: Some(Where {
1350                            this: Expression::Column(Box::new(crate::expressions::Column {
1351                                name: window_alias_ident,
1352                                table: None,
1353                                join_mark: false,
1354                                trailing_comments: vec![],
1355                                span: None,
1356                                inferred_type: None,
1357                            })),
1358                        }),
1359                        ..Select::new()
1360                    };
1361
1362                    return Ok(Expression::Select(Box::new(outer_select)));
1363                }
1364            }
1365            Ok(Expression::Select(select))
1366        }
1367        other => Ok(other),
1368    }
1369}
1370
1371/// Extract a window function from a qualify condition.
1372/// Returns (window_expression, rewritten_condition) if found.
1373/// The rewritten condition replaces the window function with a column reference to the alias.
1374fn extract_window_from_condition(
1375    condition: Expression,
1376    alias: &Identifier,
1377) -> (Option<Expression>, Expression) {
1378    let alias_col = Expression::Column(Box::new(crate::expressions::Column {
1379        name: alias.clone(),
1380        table: None,
1381        join_mark: false,
1382        trailing_comments: vec![],
1383        span: None,
1384        inferred_type: None,
1385    }));
1386
1387    // Check if condition is a simple comparison with a window function on one side
1388    match condition {
1389        // WINDOW_FUNC = value
1390        Expression::Eq(ref op) => {
1391            if is_window_expr(&op.left) {
1392                (
1393                    Some(op.left.clone()),
1394                    Expression::Eq(Box::new(BinaryOp {
1395                        left: alias_col,
1396                        right: op.right.clone(),
1397                        ..(**op).clone()
1398                    })),
1399                )
1400            } else if is_window_expr(&op.right) {
1401                (
1402                    Some(op.right.clone()),
1403                    Expression::Eq(Box::new(BinaryOp {
1404                        left: op.left.clone(),
1405                        right: alias_col,
1406                        ..(**op).clone()
1407                    })),
1408                )
1409            } else {
1410                (None, condition)
1411            }
1412        }
1413        Expression::Neq(ref op) => {
1414            if is_window_expr(&op.left) {
1415                (
1416                    Some(op.left.clone()),
1417                    Expression::Neq(Box::new(BinaryOp {
1418                        left: alias_col,
1419                        right: op.right.clone(),
1420                        ..(**op).clone()
1421                    })),
1422                )
1423            } else if is_window_expr(&op.right) {
1424                (
1425                    Some(op.right.clone()),
1426                    Expression::Neq(Box::new(BinaryOp {
1427                        left: op.left.clone(),
1428                        right: alias_col,
1429                        ..(**op).clone()
1430                    })),
1431                )
1432            } else {
1433                (None, condition)
1434            }
1435        }
1436        Expression::Lt(ref op) => {
1437            if is_window_expr(&op.left) {
1438                (
1439                    Some(op.left.clone()),
1440                    Expression::Lt(Box::new(BinaryOp {
1441                        left: alias_col,
1442                        right: op.right.clone(),
1443                        ..(**op).clone()
1444                    })),
1445                )
1446            } else if is_window_expr(&op.right) {
1447                (
1448                    Some(op.right.clone()),
1449                    Expression::Lt(Box::new(BinaryOp {
1450                        left: op.left.clone(),
1451                        right: alias_col,
1452                        ..(**op).clone()
1453                    })),
1454                )
1455            } else {
1456                (None, condition)
1457            }
1458        }
1459        Expression::Lte(ref op) => {
1460            if is_window_expr(&op.left) {
1461                (
1462                    Some(op.left.clone()),
1463                    Expression::Lte(Box::new(BinaryOp {
1464                        left: alias_col,
1465                        right: op.right.clone(),
1466                        ..(**op).clone()
1467                    })),
1468                )
1469            } else if is_window_expr(&op.right) {
1470                (
1471                    Some(op.right.clone()),
1472                    Expression::Lte(Box::new(BinaryOp {
1473                        left: op.left.clone(),
1474                        right: alias_col,
1475                        ..(**op).clone()
1476                    })),
1477                )
1478            } else {
1479                (None, condition)
1480            }
1481        }
1482        Expression::Gt(ref op) => {
1483            if is_window_expr(&op.left) {
1484                (
1485                    Some(op.left.clone()),
1486                    Expression::Gt(Box::new(BinaryOp {
1487                        left: alias_col,
1488                        right: op.right.clone(),
1489                        ..(**op).clone()
1490                    })),
1491                )
1492            } else if is_window_expr(&op.right) {
1493                (
1494                    Some(op.right.clone()),
1495                    Expression::Gt(Box::new(BinaryOp {
1496                        left: op.left.clone(),
1497                        right: alias_col,
1498                        ..(**op).clone()
1499                    })),
1500                )
1501            } else {
1502                (None, condition)
1503            }
1504        }
1505        Expression::Gte(ref op) => {
1506            if is_window_expr(&op.left) {
1507                (
1508                    Some(op.left.clone()),
1509                    Expression::Gte(Box::new(BinaryOp {
1510                        left: alias_col,
1511                        right: op.right.clone(),
1512                        ..(**op).clone()
1513                    })),
1514                )
1515            } else if is_window_expr(&op.right) {
1516                (
1517                    Some(op.right.clone()),
1518                    Expression::Gte(Box::new(BinaryOp {
1519                        left: op.left.clone(),
1520                        right: alias_col,
1521                        ..(**op).clone()
1522                    })),
1523                )
1524            } else {
1525                (None, condition)
1526            }
1527        }
1528        // If the condition is just a window function (bare QUALIFY expression)
1529        _ if is_window_expr(&condition) => (Some(condition), alias_col),
1530        // Can't extract window function
1531        _ => (None, condition),
1532    }
1533}
1534
1535/// Check if an expression is a window function
1536fn is_window_expr(expr: &Expression) -> bool {
1537    matches!(expr, Expression::Window(_) | Expression::WindowFunction(_))
1538}
1539
1540/// Eliminate DISTINCT ON clause by converting to a subquery with ROW_NUMBER
1541///
1542/// DISTINCT ON is PostgreSQL-specific. For dialects that don't support it,
1543/// this converts it to a subquery with a ROW_NUMBER() window function.
1544///
1545/// Converts:
1546/// ```sql
1547/// SELECT DISTINCT ON (a) a, b FROM t ORDER BY a, b
1548/// ```
1549/// To:
1550/// ```sql
1551/// SELECT a, b FROM (
1552///     SELECT a, b, ROW_NUMBER() OVER (PARTITION BY a ORDER BY a, b) AS _row_number
1553///     FROM t
1554/// ) _t WHERE _row_number = 1
1555/// ```
1556///
1557/// Reference: `transforms.py:138-191`
1558pub fn eliminate_distinct_on(expr: Expression) -> Result<Expression> {
1559    eliminate_distinct_on_for_dialect(expr, None, None)
1560}
1561
1562/// Strip PostgreSQL CTE materialization hints for targets that do not support
1563/// `AS MATERIALIZED` / `AS NOT MATERIALIZED`.
1564pub fn strip_cte_materialization(expr: Expression) -> Result<Expression> {
1565    transform_recursive(expr, &strip_cte_materialization_single)
1566}
1567
1568fn strip_cte_materialization_single(expr: Expression) -> Result<Expression> {
1569    Ok(match expr {
1570        Expression::Select(mut select) => {
1571            strip_with_cte_materialization(&mut select.with);
1572            Expression::Select(select)
1573        }
1574        Expression::Union(mut union) => {
1575            strip_with_cte_materialization(&mut union.with);
1576            Expression::Union(union)
1577        }
1578        Expression::Intersect(mut intersect) => {
1579            strip_with_cte_materialization(&mut intersect.with);
1580            Expression::Intersect(intersect)
1581        }
1582        Expression::Except(mut except) => {
1583            strip_with_cte_materialization(&mut except.with);
1584            Expression::Except(except)
1585        }
1586        Expression::Pivot(mut pivot) => {
1587            strip_with_cte_materialization(&mut pivot.with);
1588            Expression::Pivot(pivot)
1589        }
1590        Expression::Insert(mut insert) => {
1591            strip_with_cte_materialization(&mut insert.with);
1592            Expression::Insert(insert)
1593        }
1594        Expression::Update(mut update) => {
1595            strip_with_cte_materialization(&mut update.with);
1596            Expression::Update(update)
1597        }
1598        Expression::Delete(mut delete) => {
1599            strip_with_cte_materialization(&mut delete.with);
1600            Expression::Delete(delete)
1601        }
1602        Expression::CreateTable(mut create_table) => {
1603            strip_with_cte_materialization(&mut create_table.with_cte);
1604            Expression::CreateTable(create_table)
1605        }
1606        Expression::With(mut with) => {
1607            strip_cte_materialization_in_with(&mut with);
1608            Expression::With(with)
1609        }
1610        Expression::Cte(mut cte) => {
1611            cte.materialized = None;
1612            Expression::Cte(cte)
1613        }
1614        _ => expr,
1615    })
1616}
1617
1618fn strip_with_cte_materialization(with: &mut Option<With>) {
1619    if let Some(with) = with {
1620        strip_cte_materialization_in_with(with);
1621    }
1622}
1623
1624fn strip_cte_materialization_in_with(with: &mut With) {
1625    for cte in &mut with.ctes {
1626        cte.materialized = None;
1627    }
1628}
1629
1630#[derive(Clone, Copy)]
1631enum DistinctOnNullsMode {
1632    None,
1633    NullsFirst,
1634    CaseExpr,
1635}
1636
1637/// Eliminate DISTINCT ON with dialect-specific NULL ordering behavior.
1638///
1639/// For dialects where NULLs don't sort first by default in DESC ordering,
1640/// we need to add explicit NULL ordering to preserve DISTINCT ON semantics.
1641pub fn eliminate_distinct_on_for_dialect(
1642    expr: Expression,
1643    target: Option<DialectType>,
1644    source: Option<DialectType>,
1645) -> Result<Expression> {
1646    // PostgreSQL and DuckDB support DISTINCT ON natively - skip elimination
1647    if matches!(
1648        target,
1649        Some(DialectType::PostgreSQL) | Some(DialectType::DuckDB)
1650    ) {
1651        return Ok(expr);
1652    }
1653
1654    // Determine NULL ordering mode based on target dialect
1655    // Oracle/Redshift/Snowflake: NULLS FIRST is default for DESC -> no change needed
1656    // BigQuery/Spark/Presto/Hive/etc: need explicit NULLS FIRST
1657    // MySQL/TSQL: no NULLS FIRST syntax -> use CASE WHEN IS NULL
1658    let nulls_mode = match target {
1659        Some(DialectType::MySQL)
1660        | Some(DialectType::SingleStore)
1661        | Some(DialectType::TSQL)
1662        | Some(DialectType::Fabric) => DistinctOnNullsMode::CaseExpr,
1663        Some(DialectType::Oracle) | Some(DialectType::Redshift) | Some(DialectType::Snowflake) => {
1664            DistinctOnNullsMode::None
1665        }
1666        Some(DialectType::StarRocks) => {
1667            if matches!(source, Some(DialectType::Redshift)) {
1668                DistinctOnNullsMode::CaseExpr
1669            } else {
1670                DistinctOnNullsMode::None
1671            }
1672        }
1673        // All other dialects that don't support DISTINCT ON: use NULLS FIRST
1674        _ => DistinctOnNullsMode::NullsFirst,
1675    };
1676
1677    transform_recursive(expr, &|expr| eliminate_distinct_on_select(expr, nulls_mode))
1678}
1679
1680fn eliminate_distinct_on_select(
1681    expr: Expression,
1682    nulls_mode: DistinctOnNullsMode,
1683) -> Result<Expression> {
1684    use crate::expressions::Case;
1685
1686    match expr {
1687        Expression::Select(mut select) => {
1688            if let Some(distinct_cols) = select.distinct_on.take() {
1689                if !distinct_cols.is_empty() {
1690                    // Create ROW_NUMBER() OVER (PARTITION BY distinct_cols ORDER BY ...)
1691                    let row_number_alias = Identifier::new("_row_number".to_string());
1692
1693                    // Get order_by expressions, or use distinct_cols as default order
1694                    let order_exprs = if let Some(ref order_by) = select.order_by {
1695                        let mut exprs = order_by.expressions.clone();
1696                        // Add NULL ordering based on target dialect
1697                        match nulls_mode {
1698                            DistinctOnNullsMode::NullsFirst => {
1699                                for ord in &mut exprs {
1700                                    if ord.desc && ord.nulls_first.is_none() {
1701                                        ord.nulls_first = Some(true);
1702                                    }
1703                                }
1704                            }
1705                            DistinctOnNullsMode::CaseExpr => {
1706                                // For each DESC column without explicit nulls ordering,
1707                                // prepend: CASE WHEN col IS NULL THEN 1 ELSE 0 END DESC
1708                                let mut new_exprs = Vec::new();
1709                                for ord in &exprs {
1710                                    if ord.desc && ord.nulls_first.is_none() {
1711                                        // Add CASE WHEN col IS NULL THEN 1 ELSE 0 END DESC
1712                                        let null_check = Expression::Case(Box::new(Case {
1713                                            operand: None,
1714                                            whens: vec![(
1715                                                Expression::IsNull(Box::new(
1716                                                    crate::expressions::IsNull {
1717                                                        this: ord.this.clone(),
1718                                                        not: false,
1719                                                        postfix_form: false,
1720                                                    },
1721                                                )),
1722                                                Expression::Literal(Box::new(Literal::Number(
1723                                                    "1".to_string(),
1724                                                ))),
1725                                            )],
1726                                            else_: Some(Expression::Literal(Box::new(
1727                                                Literal::Number("0".to_string()),
1728                                            ))),
1729                                            comments: Vec::new(),
1730                                            inferred_type: None,
1731                                        }));
1732                                        new_exprs.push(crate::expressions::Ordered {
1733                                            nulls_auto: false,
1734                                            this: null_check,
1735                                            desc: true,
1736                                            nulls_first: None,
1737                                            explicit_asc: false,
1738                                            with_fill: None,
1739                                        });
1740                                    }
1741                                    new_exprs.push(ord.clone());
1742                                }
1743                                exprs = new_exprs;
1744                            }
1745                            DistinctOnNullsMode::None => {}
1746                        }
1747                        exprs
1748                    } else {
1749                        distinct_cols
1750                            .iter()
1751                            .map(|e| crate::expressions::Ordered {
1752                                nulls_auto: false,
1753                                this: e.clone(),
1754                                desc: false,
1755                                nulls_first: None,
1756                                explicit_asc: false,
1757                                with_fill: None,
1758                            })
1759                            .collect()
1760                    };
1761
1762                    // Create window function: ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)
1763                    let row_number_func =
1764                        Expression::WindowFunction(Box::new(crate::expressions::WindowFunction {
1765                            this: Expression::RowNumber(crate::expressions::RowNumber),
1766                            over: Over {
1767                                partition_by: distinct_cols,
1768                                order_by: order_exprs,
1769                                frame: None,
1770                                window_name: None,
1771                                alias: None,
1772                            },
1773                            keep: None,
1774                            inferred_type: None,
1775                        }));
1776
1777                    // Build aliased inner expressions and outer column references
1778                    // Inner: SELECT a AS a, b AS b, ROW_NUMBER() OVER (...) AS _row_number
1779                    // Outer: SELECT a, b FROM (...)
1780                    let mut inner_aliased_exprs = Vec::new();
1781                    let mut outer_select_exprs = Vec::new();
1782                    for orig_expr in &select.expressions {
1783                        match orig_expr {
1784                            Expression::Alias(alias) => {
1785                                // Already aliased - keep as-is in inner, reference alias in outer
1786                                inner_aliased_exprs.push(orig_expr.clone());
1787                                outer_select_exprs.push(Expression::Column(Box::new(
1788                                    crate::expressions::Column {
1789                                        name: alias.alias.clone(),
1790                                        table: None,
1791                                        join_mark: false,
1792                                        trailing_comments: vec![],
1793                                        span: None,
1794                                        inferred_type: None,
1795                                    },
1796                                )));
1797                            }
1798                            Expression::Column(col) => {
1799                                // Wrap in alias: a AS a in inner, just a in outer
1800                                inner_aliased_exprs.push(Expression::Alias(Box::new(
1801                                    crate::expressions::Alias {
1802                                        this: orig_expr.clone(),
1803                                        alias: col.name.clone(),
1804                                        column_aliases: vec![],
1805                                        alias_explicit_as: false,
1806                                        alias_keyword: None,
1807                                        pre_alias_comments: vec![],
1808                                        trailing_comments: vec![],
1809                                        inferred_type: None,
1810                                    },
1811                                )));
1812                                outer_select_exprs.push(Expression::Column(Box::new(
1813                                    crate::expressions::Column {
1814                                        name: col.name.clone(),
1815                                        table: None,
1816                                        join_mark: false,
1817                                        trailing_comments: vec![],
1818                                        span: None,
1819                                        inferred_type: None,
1820                                    },
1821                                )));
1822                            }
1823                            _ => {
1824                                // Complex expression without alias - include as-is in both
1825                                inner_aliased_exprs.push(orig_expr.clone());
1826                                outer_select_exprs.push(orig_expr.clone());
1827                            }
1828                        }
1829                    }
1830
1831                    // Add ROW_NUMBER as aliased expression to inner select list
1832                    let row_number_alias_expr =
1833                        Expression::Alias(Box::new(crate::expressions::Alias {
1834                            this: row_number_func,
1835                            alias: row_number_alias.clone(),
1836                            column_aliases: vec![],
1837                            alias_explicit_as: false,
1838                            alias_keyword: None,
1839                            pre_alias_comments: vec![],
1840                            trailing_comments: vec![],
1841                            inferred_type: None,
1842                        }));
1843                    inner_aliased_exprs.push(row_number_alias_expr);
1844
1845                    // Replace inner select's expressions with aliased versions
1846                    select.expressions = inner_aliased_exprs;
1847
1848                    // Remove ORDER BY from inner query (it's now in the window function)
1849                    let _inner_order_by = select.order_by.take();
1850
1851                    // Clear DISTINCT from inner select (DISTINCT ON is replaced by ROW_NUMBER)
1852                    select.distinct = false;
1853
1854                    // Create inner subquery
1855                    let inner_select = Expression::Select(select);
1856                    let subquery = Subquery {
1857                        this: inner_select,
1858                        alias: Some(Identifier::new("_t".to_string())),
1859                        column_aliases: vec![],
1860                        alias_explicit_as: false,
1861                        alias_keyword: None,
1862                        order_by: None,
1863                        limit: None,
1864                        offset: None,
1865                        distribute_by: None,
1866                        sort_by: None,
1867                        cluster_by: None,
1868                        lateral: false,
1869                        modifiers_inside: false,
1870                        trailing_comments: vec![],
1871                        inferred_type: None,
1872                    };
1873
1874                    // Create outer SELECT with WHERE _row_number = 1
1875                    // No ORDER BY on outer query
1876                    let outer_select = Select {
1877                        vertica: None,
1878                        expressions: outer_select_exprs,
1879                        from: Some(From {
1880                            expressions: vec![Expression::Subquery(Box::new(subquery))],
1881                        }),
1882                        where_clause: Some(Where {
1883                            this: Expression::Eq(Box::new(BinaryOp {
1884                                left: Expression::Column(Box::new(crate::expressions::Column {
1885                                    name: row_number_alias,
1886                                    table: None,
1887                                    join_mark: false,
1888                                    trailing_comments: vec![],
1889                                    span: None,
1890                                    inferred_type: None,
1891                                })),
1892                                right: Expression::Literal(Box::new(Literal::Number(
1893                                    "1".to_string(),
1894                                ))),
1895                                left_comments: vec![],
1896                                operator_comments: vec![],
1897                                trailing_comments: vec![],
1898                                inferred_type: None,
1899                            })),
1900                        }),
1901                        ..Select::new()
1902                    };
1903
1904                    return Ok(Expression::Select(Box::new(outer_select)));
1905                }
1906            }
1907            Ok(Expression::Select(select))
1908        }
1909        other => Ok(other),
1910    }
1911}
1912
1913/// Convert SEMI and ANTI joins into equivalent forms that use EXISTS instead.
1914///
1915/// For dialects that don't support SEMI/ANTI join syntax, this converts:
1916/// - `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)`
1917/// - `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)`
1918///
1919/// Reference: `transforms.py:607-621`
1920pub fn eliminate_semi_and_anti_joins(expr: Expression) -> Result<Expression> {
1921    match expr {
1922        Expression::Select(mut select) => {
1923            let mut new_joins = Vec::new();
1924            let mut extra_where_conditions = Vec::new();
1925
1926            for join in select.joins.drain(..) {
1927                match join.kind {
1928                    JoinKind::Semi | JoinKind::LeftSemi => {
1929                        if let Some(on_condition) = join.on {
1930                            // Create: EXISTS (SELECT 1 FROM join_table WHERE on_condition)
1931                            let subquery_select = Select {
1932                                vertica: None,
1933                                expressions: vec![Expression::Literal(Box::new(Literal::Number(
1934                                    "1".to_string(),
1935                                )))],
1936                                from: Some(From {
1937                                    expressions: vec![join.this],
1938                                }),
1939                                where_clause: Some(Where { this: on_condition }),
1940                                ..Select::new()
1941                            };
1942
1943                            let exists = Expression::Exists(Box::new(Exists {
1944                                this: Expression::Subquery(Box::new(Subquery {
1945                                    this: Expression::Select(Box::new(subquery_select)),
1946                                    alias: None,
1947                                    column_aliases: vec![],
1948                                    alias_explicit_as: false,
1949                                    alias_keyword: None,
1950                                    order_by: None,
1951                                    limit: None,
1952                                    offset: None,
1953                                    distribute_by: None,
1954                                    sort_by: None,
1955                                    cluster_by: None,
1956                                    lateral: false,
1957                                    modifiers_inside: false,
1958                                    trailing_comments: vec![],
1959                                    inferred_type: None,
1960                                })),
1961                                not: false,
1962                            }));
1963
1964                            extra_where_conditions.push(exists);
1965                        }
1966                    }
1967                    JoinKind::Anti | JoinKind::LeftAnti => {
1968                        if let Some(on_condition) = join.on {
1969                            // Create: NOT EXISTS (SELECT 1 FROM join_table WHERE on_condition)
1970                            let subquery_select = Select {
1971                                vertica: None,
1972                                expressions: vec![Expression::Literal(Box::new(Literal::Number(
1973                                    "1".to_string(),
1974                                )))],
1975                                from: Some(From {
1976                                    expressions: vec![join.this],
1977                                }),
1978                                where_clause: Some(Where { this: on_condition }),
1979                                ..Select::new()
1980                            };
1981
1982                            // Use Exists with not: true for NOT EXISTS
1983                            let not_exists = Expression::Exists(Box::new(Exists {
1984                                this: Expression::Subquery(Box::new(Subquery {
1985                                    this: Expression::Select(Box::new(subquery_select)),
1986                                    alias: None,
1987                                    column_aliases: vec![],
1988                                    alias_explicit_as: false,
1989                                    alias_keyword: None,
1990                                    order_by: None,
1991                                    limit: None,
1992                                    offset: None,
1993                                    distribute_by: None,
1994                                    sort_by: None,
1995                                    cluster_by: None,
1996                                    lateral: false,
1997                                    modifiers_inside: false,
1998                                    trailing_comments: vec![],
1999                                    inferred_type: None,
2000                                })),
2001                                not: true,
2002                            }));
2003
2004                            extra_where_conditions.push(not_exists);
2005                        }
2006                    }
2007                    _ => {
2008                        // Keep other join types as-is
2009                        new_joins.push(join);
2010                    }
2011                }
2012            }
2013
2014            select.joins = new_joins;
2015
2016            // Add EXISTS conditions to WHERE clause
2017            if !extra_where_conditions.is_empty() {
2018                let combined = extra_where_conditions
2019                    .into_iter()
2020                    .reduce(|acc, cond| {
2021                        Expression::And(Box::new(BinaryOp {
2022                            left: acc,
2023                            right: cond,
2024                            left_comments: vec![],
2025                            operator_comments: vec![],
2026                            trailing_comments: vec![],
2027                            inferred_type: None,
2028                        }))
2029                    })
2030                    .unwrap();
2031
2032                select.where_clause = match select.where_clause {
2033                    Some(Where { this: existing }) => Some(Where {
2034                        this: Expression::And(Box::new(BinaryOp {
2035                            left: existing,
2036                            right: combined,
2037                            left_comments: vec![],
2038                            operator_comments: vec![],
2039                            trailing_comments: vec![],
2040                            inferred_type: None,
2041                        })),
2042                    }),
2043                    None => Some(Where { this: combined }),
2044                };
2045            }
2046
2047            Ok(Expression::Select(select))
2048        }
2049        other => Ok(other),
2050    }
2051}
2052
2053/// Convert FULL OUTER JOIN to a UNION of LEFT and RIGHT OUTER joins.
2054///
2055/// For dialects that don't support FULL OUTER JOIN, this converts:
2056/// ```sql
2057/// SELECT * FROM a FULL OUTER JOIN b ON a.x = b.x
2058/// ```
2059/// To:
2060/// ```sql
2061/// SELECT * FROM a LEFT OUTER JOIN b ON a.x = b.x
2062/// UNION ALL
2063/// SELECT * FROM a RIGHT OUTER JOIN b ON a.x = b.x
2064/// WHERE NOT EXISTS (SELECT 1 FROM a WHERE a.x = b.x)
2065/// ```
2066///
2067/// Note: This transformation currently only works for queries with a single FULL OUTER join.
2068///
2069/// Reference: `transforms.py:624-661`
2070pub fn eliminate_full_outer_join(expr: Expression) -> Result<Expression> {
2071    match expr {
2072        Expression::Select(mut select) => {
2073            // Find FULL OUTER joins
2074            let full_outer_join_idx = select.joins.iter().position(|j| j.kind == JoinKind::Full);
2075
2076            if let Some(idx) = full_outer_join_idx {
2077                // We only handle queries with a single FULL OUTER join
2078                let full_join_count = select
2079                    .joins
2080                    .iter()
2081                    .filter(|j| j.kind == JoinKind::Full)
2082                    .count();
2083                if full_join_count != 1 {
2084                    return Ok(Expression::Select(select));
2085                }
2086
2087                // Clone the query for the right side of the UNION
2088                let mut right_select = select.clone();
2089
2090                // Get the join condition from the FULL OUTER join
2091                let full_join = &select.joins[idx];
2092                let join_condition = full_join.on.clone();
2093
2094                // Left side: convert FULL to LEFT
2095                select.joins[idx].kind = JoinKind::Left;
2096
2097                // Right side: convert FULL to RIGHT and add NOT EXISTS condition
2098                right_select.joins[idx].kind = JoinKind::Right;
2099
2100                // Build NOT EXISTS for the right side to exclude rows that matched
2101                if let (Some(ref from), Some(ref join_cond)) = (&select.from, &join_condition) {
2102                    if !from.expressions.is_empty() {
2103                        let anti_subquery = Expression::Select(Box::new(Select {
2104                            vertica: None,
2105                            expressions: vec![Expression::Literal(Box::new(Literal::Number(
2106                                "1".to_string(),
2107                            )))],
2108                            from: Some(from.clone()),
2109                            where_clause: Some(Where {
2110                                this: join_cond.clone(),
2111                            }),
2112                            ..Select::new()
2113                        }));
2114
2115                        let not_exists = Expression::Not(Box::new(crate::expressions::UnaryOp {
2116                            inferred_type: None,
2117                            this: Expression::Exists(Box::new(Exists {
2118                                this: Expression::Subquery(Box::new(Subquery {
2119                                    this: anti_subquery,
2120                                    alias: None,
2121                                    column_aliases: vec![],
2122                                    alias_explicit_as: false,
2123                                    alias_keyword: None,
2124                                    order_by: None,
2125                                    limit: None,
2126                                    offset: None,
2127                                    distribute_by: None,
2128                                    sort_by: None,
2129                                    cluster_by: None,
2130                                    lateral: false,
2131                                    modifiers_inside: false,
2132                                    trailing_comments: vec![],
2133                                    inferred_type: None,
2134                                })),
2135                                not: false,
2136                            })),
2137                        }));
2138
2139                        // Add NOT EXISTS to the WHERE clause
2140                        right_select.where_clause = Some(Where {
2141                            this: match right_select.where_clause {
2142                                Some(w) => Expression::And(Box::new(BinaryOp {
2143                                    left: w.this,
2144                                    right: not_exists,
2145                                    left_comments: vec![],
2146                                    operator_comments: vec![],
2147                                    trailing_comments: vec![],
2148                                    inferred_type: None,
2149                                })),
2150                                None => not_exists,
2151                            },
2152                        });
2153                    }
2154                }
2155
2156                // Remove WITH clause from right side (CTEs should only be on left)
2157                right_select.with = None;
2158
2159                // Remove ORDER BY from left side (will be applied after UNION)
2160                let order_by = select.order_by.take();
2161
2162                // Create UNION ALL of left and right
2163                let union = crate::expressions::Union {
2164                    left: Expression::Select(select),
2165                    right: Expression::Select(right_select),
2166                    all: true, // UNION ALL
2167                    distinct: false,
2168                    with: None,
2169                    order_by,
2170                    limit: None,
2171                    offset: None,
2172                    distribute_by: None,
2173                    sort_by: None,
2174                    cluster_by: None,
2175                    by_name: false,
2176                    side: None,
2177                    kind: None,
2178                    corresponding: false,
2179                    strict: false,
2180                    on_columns: Vec::new(),
2181                };
2182
2183                return Ok(Expression::Union(Box::new(union)));
2184            }
2185
2186            Ok(Expression::Select(select))
2187        }
2188        other => Ok(other),
2189    }
2190}
2191
2192/// Move CTEs to the top level of the query.
2193///
2194/// Some dialects (e.g., Hive, T-SQL, Spark prior to version 3) only allow CTEs to be
2195/// defined at the top-level, so for example queries like:
2196///
2197/// ```sql
2198/// SELECT * FROM (WITH t(c) AS (SELECT 1) SELECT * FROM t) AS subq
2199/// ```
2200///
2201/// are invalid in those dialects. This transformation moves all CTEs to the top level.
2202///
2203/// Reference: `transforms.py:664-700`
2204pub fn move_ctes_to_top_level(expr: Expression) -> Result<Expression> {
2205    match expr {
2206        Expression::Select(mut select) => {
2207            // Phase 1: Collect CTEs from nested subqueries (not inside CTE definitions)
2208            let mut collected_ctes: Vec<crate::expressions::Cte> = Vec::new();
2209            let mut has_recursive = false;
2210
2211            collect_nested_ctes(
2212                &Expression::Select(select.clone()),
2213                &mut collected_ctes,
2214                &mut has_recursive,
2215                true,
2216            );
2217
2218            // Phase 2: Flatten CTEs nested inside top-level CTE definitions
2219            // This handles: WITH c AS (WITH b AS (...) SELECT ...) -> WITH b AS (...), c AS (SELECT ...)
2220            let mut cte_body_collected: Vec<(String, Vec<crate::expressions::Cte>)> = Vec::new();
2221            if let Some(ref with) = select.with {
2222                for cte in &with.ctes {
2223                    let mut body_ctes: Vec<crate::expressions::Cte> = Vec::new();
2224                    collect_ctes_from_cte_body(&cte.this, &mut body_ctes, &mut has_recursive);
2225                    if !body_ctes.is_empty() {
2226                        cte_body_collected.push((cte.alias.name.clone(), body_ctes));
2227                    }
2228                }
2229            }
2230
2231            let has_subquery_ctes = !collected_ctes.is_empty();
2232            let has_body_ctes = !cte_body_collected.is_empty();
2233
2234            if has_subquery_ctes || has_body_ctes {
2235                // Strip WITH clauses from inner subqueries
2236                strip_nested_with_clauses(&mut select, true);
2237
2238                // Strip WITH clauses from CTE body definitions
2239                if has_body_ctes {
2240                    if let Some(ref mut with) = select.with {
2241                        for cte in with.ctes.iter_mut() {
2242                            strip_with_from_cte_body(&mut cte.this);
2243                        }
2244                    }
2245                }
2246
2247                let top_with = select.with.get_or_insert_with(|| crate::expressions::With {
2248                    ctes: Vec::new(),
2249                    recursive: false,
2250                    leading_comments: vec![],
2251                    search: None,
2252                });
2253
2254                if has_recursive {
2255                    top_with.recursive = true;
2256                }
2257
2258                // Insert body CTEs before their parent CTE (Python sqlglot behavior)
2259                if has_body_ctes {
2260                    let mut new_ctes: Vec<crate::expressions::Cte> = Vec::new();
2261                    for mut cte in top_with.ctes.drain(..) {
2262                        // Check if this CTE has nested CTEs to insert before it
2263                        if let Some(pos) = cte_body_collected
2264                            .iter()
2265                            .position(|(name, _)| *name == cte.alias.name)
2266                        {
2267                            let (_, mut nested) = cte_body_collected.remove(pos);
2268                            // Strip WITH from each nested CTE's body too
2269                            for nested_cte in nested.iter_mut() {
2270                                strip_with_from_cte_body(&mut nested_cte.this);
2271                            }
2272                            new_ctes.extend(nested);
2273                        }
2274                        // Also strip WITH from the parent CTE's body
2275                        strip_with_from_cte_body(&mut cte.this);
2276                        new_ctes.push(cte);
2277                    }
2278                    top_with.ctes = new_ctes;
2279                }
2280
2281                // Append collected subquery CTEs after existing ones
2282                top_with.ctes.extend(collected_ctes);
2283            }
2284
2285            Ok(Expression::Select(select))
2286        }
2287        other => Ok(other),
2288    }
2289}
2290
2291/// Recursively collect CTEs from within CTE body expressions (for deep nesting)
2292fn collect_ctes_from_cte_body(
2293    expr: &Expression,
2294    collected: &mut Vec<crate::expressions::Cte>,
2295    has_recursive: &mut bool,
2296) {
2297    if let Expression::Select(select) = expr {
2298        if let Some(ref with) = select.with {
2299            if with.recursive {
2300                *has_recursive = true;
2301            }
2302            for cte in &with.ctes {
2303                // Recursively collect from this CTE's body first (depth-first)
2304                collect_ctes_from_cte_body(&cte.this, collected, has_recursive);
2305                // Then add this CTE itself
2306                collected.push(cte.clone());
2307            }
2308        }
2309    }
2310}
2311
2312/// Strip WITH clauses from CTE body expressions
2313fn strip_with_from_cte_body(expr: &mut Expression) {
2314    if let Expression::Select(ref mut select) = expr {
2315        select.with = None;
2316    }
2317}
2318
2319/// Strip WITH clauses from nested subqueries (after hoisting to top level)
2320fn strip_nested_with_clauses(select: &mut Select, _is_top_level: bool) {
2321    // Strip WITH from FROM subqueries
2322    if let Some(ref mut from) = select.from {
2323        for expr in from.expressions.iter_mut() {
2324            strip_with_from_expr(expr);
2325        }
2326    }
2327    // Strip from JOINs
2328    for join in select.joins.iter_mut() {
2329        strip_with_from_expr(&mut join.this);
2330    }
2331    // Strip from select expressions
2332    for expr in select.expressions.iter_mut() {
2333        strip_with_from_expr(expr);
2334    }
2335    // Strip from WHERE
2336    if let Some(ref mut w) = select.where_clause {
2337        strip_with_from_expr(&mut w.this);
2338    }
2339}
2340
2341fn strip_with_from_expr(expr: &mut Expression) {
2342    match expr {
2343        Expression::Subquery(ref mut subquery) => {
2344            strip_with_from_inner_query(&mut subquery.this);
2345        }
2346        Expression::Alias(ref mut alias) => {
2347            strip_with_from_expr(&mut alias.this);
2348        }
2349        Expression::Select(ref mut select) => {
2350            // Strip WITH from this SELECT (it's nested)
2351            select.with = None;
2352            // Recurse into its subqueries
2353            strip_nested_with_clauses(select, false);
2354        }
2355        _ => {}
2356    }
2357}
2358
2359fn strip_with_from_inner_query(expr: &mut Expression) {
2360    if let Expression::Select(ref mut select) = expr {
2361        select.with = None;
2362        strip_nested_with_clauses(select, false);
2363    }
2364}
2365
2366/// Helper to recursively collect CTEs from nested subqueries
2367fn collect_nested_ctes(
2368    expr: &Expression,
2369    collected: &mut Vec<crate::expressions::Cte>,
2370    has_recursive: &mut bool,
2371    is_top_level: bool,
2372) {
2373    match expr {
2374        Expression::Select(select) => {
2375            // If this is not the top level and has a WITH clause, collect its CTEs
2376            if !is_top_level {
2377                if let Some(ref with) = select.with {
2378                    if with.recursive {
2379                        *has_recursive = true;
2380                    }
2381                    collected.extend(with.ctes.clone());
2382                }
2383            }
2384
2385            // Recurse into FROM clause
2386            if let Some(ref from) = select.from {
2387                for expr in &from.expressions {
2388                    collect_nested_ctes(expr, collected, has_recursive, false);
2389                }
2390            }
2391
2392            // Recurse into JOINs
2393            for join in &select.joins {
2394                collect_nested_ctes(&join.this, collected, has_recursive, false);
2395            }
2396
2397            // Recurse into select expressions (for subqueries in SELECT)
2398            for sel_expr in &select.expressions {
2399                collect_nested_ctes(sel_expr, collected, has_recursive, false);
2400            }
2401
2402            // Recurse into WHERE
2403            if let Some(ref where_clause) = select.where_clause {
2404                collect_nested_ctes(&where_clause.this, collected, has_recursive, false);
2405            }
2406        }
2407        Expression::Subquery(subquery) => {
2408            // Process the inner query
2409            collect_nested_ctes(&subquery.this, collected, has_recursive, false);
2410        }
2411        Expression::Alias(alias) => {
2412            collect_nested_ctes(&alias.this, collected, has_recursive, false);
2413        }
2414        // Add more expression types as needed
2415        _ => {}
2416    }
2417}
2418
2419/// Inline window definitions from WINDOW clause.
2420///
2421/// Some dialects don't support named windows. This transform inlines them:
2422///
2423/// ```sql
2424/// SELECT SUM(a) OVER w FROM t WINDOW w AS (PARTITION BY b)
2425/// ```
2426///
2427/// To:
2428///
2429/// ```sql
2430/// SELECT SUM(a) OVER (PARTITION BY b) FROM t
2431/// ```
2432///
2433/// Reference: `transforms.py:975-1003`
2434pub fn eliminate_window_clause(expr: Expression) -> Result<Expression> {
2435    match expr {
2436        Expression::Select(mut select) => {
2437            if let Some(named_windows) = select.windows.take() {
2438                // Build a map of window name -> window spec
2439                let window_map: std::collections::HashMap<String, &Over> = named_windows
2440                    .iter()
2441                    .map(|nw| (nw.name.name.to_lowercase(), &nw.spec))
2442                    .collect();
2443
2444                // Inline window references in the select expressions
2445                select.expressions = select
2446                    .expressions
2447                    .into_iter()
2448                    .map(|e| inline_window_refs(e, &window_map))
2449                    .collect();
2450            }
2451            Ok(Expression::Select(select))
2452        }
2453        other => Ok(other),
2454    }
2455}
2456
2457/// Helper function to inline window references in an expression
2458fn inline_window_refs(
2459    expr: Expression,
2460    window_map: &std::collections::HashMap<String, &Over>,
2461) -> Expression {
2462    match expr {
2463        Expression::WindowFunction(mut wf) => {
2464            // Check if this window references a named window
2465            if let Some(ref name) = wf.over.window_name {
2466                let key = name.name.to_lowercase();
2467                if let Some(named_spec) = window_map.get(&key) {
2468                    // Inherit properties from the named window
2469                    if wf.over.partition_by.is_empty() && !named_spec.partition_by.is_empty() {
2470                        wf.over.partition_by = named_spec.partition_by.clone();
2471                    }
2472                    if wf.over.order_by.is_empty() && !named_spec.order_by.is_empty() {
2473                        wf.over.order_by = named_spec.order_by.clone();
2474                    }
2475                    if wf.over.frame.is_none() && named_spec.frame.is_some() {
2476                        wf.over.frame = named_spec.frame.clone();
2477                    }
2478                    // Clear the window name reference
2479                    wf.over.window_name = None;
2480                }
2481            }
2482            Expression::WindowFunction(wf)
2483        }
2484        Expression::Alias(mut alias) => {
2485            // Recurse into aliased expressions
2486            alias.this = inline_window_refs(alias.this, window_map);
2487            Expression::Alias(alias)
2488        }
2489        // For a complete implementation, we would need to recursively visit all expressions
2490        // that can contain window functions (CASE, subqueries, etc.)
2491        other => other,
2492    }
2493}
2494
2495/// Eliminate Oracle-style (+) join marks by converting to standard JOINs.
2496///
2497/// Oracle uses (+) syntax for outer joins:
2498/// ```sql
2499/// SELECT * FROM a, b WHERE a.x = b.x(+)
2500/// ```
2501///
2502/// This is converted to standard LEFT OUTER JOIN:
2503/// ```sql
2504/// SELECT * FROM a LEFT OUTER JOIN b ON a.x = b.x
2505/// ```
2506///
2507/// Reference: `transforms.py:828-945`
2508pub fn eliminate_join_marks(expr: Expression) -> Result<Expression> {
2509    match expr {
2510        Expression::Select(mut select) => {
2511            // Check if there are any join marks in the WHERE clause
2512            let has_join_marks = select
2513                .where_clause
2514                .as_ref()
2515                .map_or(false, |w| contains_join_mark(&w.this));
2516
2517            if !has_join_marks {
2518                return Ok(Expression::Select(select));
2519            }
2520
2521            // Collect tables from FROM clause
2522            let from_tables: Vec<String> = select
2523                .from
2524                .as_ref()
2525                .map(|f| {
2526                    f.expressions
2527                        .iter()
2528                        .filter_map(|e| get_table_name(e))
2529                        .collect()
2530                })
2531                .unwrap_or_default();
2532
2533            // Extract join conditions and their marked tables from WHERE
2534            let mut join_conditions: std::collections::HashMap<String, Vec<Expression>> =
2535                std::collections::HashMap::new();
2536            let mut remaining_conditions: Vec<Expression> = Vec::new();
2537
2538            if let Some(ref where_clause) = select.where_clause {
2539                extract_join_mark_conditions(
2540                    &where_clause.this,
2541                    &mut join_conditions,
2542                    &mut remaining_conditions,
2543                );
2544            }
2545
2546            // Build new JOINs for each marked table
2547            let mut new_joins = select.joins.clone();
2548            for (table_name, conditions) in join_conditions {
2549                // Find if this table is in FROM or existing JOINs
2550                let table_in_from = from_tables.contains(&table_name);
2551
2552                if table_in_from && !conditions.is_empty() {
2553                    // Create LEFT JOIN with combined conditions
2554                    let combined_condition = conditions.into_iter().reduce(|a, b| {
2555                        Expression::And(Box::new(BinaryOp {
2556                            left: a,
2557                            right: b,
2558                            left_comments: vec![],
2559                            operator_comments: vec![],
2560                            trailing_comments: vec![],
2561                            inferred_type: None,
2562                        }))
2563                    });
2564
2565                    // Find the table in FROM and move it to a JOIN
2566                    if let Some(ref mut from) = select.from {
2567                        if let Some(pos) = from
2568                            .expressions
2569                            .iter()
2570                            .position(|e| get_table_name(e).map_or(false, |n| n == table_name))
2571                        {
2572                            if from.expressions.len() > 1 {
2573                                let join_table = from.expressions.remove(pos);
2574                                new_joins.push(crate::expressions::Join {
2575                                    this: join_table,
2576                                    kind: JoinKind::Left,
2577                                    on: combined_condition,
2578                                    using: vec![],
2579                                    use_inner_keyword: false,
2580                                    use_outer_keyword: true,
2581                                    deferred_condition: false,
2582                                    join_hint: None,
2583                                    match_condition: None,
2584                                    pivots: Vec::new(),
2585                                    comments: Vec::new(),
2586                                    nesting_group: 0,
2587                                    directed: false,
2588                                });
2589                            }
2590                        }
2591                    }
2592                }
2593            }
2594
2595            select.joins = new_joins;
2596
2597            // Update WHERE with remaining conditions
2598            if remaining_conditions.is_empty() {
2599                select.where_clause = None;
2600            } else {
2601                let combined = remaining_conditions.into_iter().reduce(|a, b| {
2602                    Expression::And(Box::new(BinaryOp {
2603                        left: a,
2604                        right: b,
2605                        left_comments: vec![],
2606                        operator_comments: vec![],
2607                        trailing_comments: vec![],
2608                        inferred_type: None,
2609                    }))
2610                });
2611                select.where_clause = combined.map(|c| Where { this: c });
2612            }
2613
2614            // Clear join marks from all columns
2615            clear_join_marks(&mut Expression::Select(select.clone()));
2616
2617            Ok(Expression::Select(select))
2618        }
2619        other => Ok(other),
2620    }
2621}
2622
2623/// Check if an expression contains any columns with join marks
2624fn contains_join_mark(expr: &Expression) -> bool {
2625    match expr {
2626        Expression::Column(col) => col.join_mark,
2627        Expression::And(op) | Expression::Or(op) => {
2628            contains_join_mark(&op.left) || contains_join_mark(&op.right)
2629        }
2630        Expression::Eq(op)
2631        | Expression::Neq(op)
2632        | Expression::Lt(op)
2633        | Expression::Lte(op)
2634        | Expression::Gt(op)
2635        | Expression::Gte(op) => contains_join_mark(&op.left) || contains_join_mark(&op.right),
2636        Expression::Not(op) => contains_join_mark(&op.this),
2637        _ => false,
2638    }
2639}
2640
2641/// Get table name from a table expression
2642fn get_table_name(expr: &Expression) -> Option<String> {
2643    match expr {
2644        Expression::Table(t) => Some(t.name.name.clone()),
2645        Expression::Alias(a) => Some(a.alias.name.clone()),
2646        _ => None,
2647    }
2648}
2649
2650/// Extract join mark conditions from WHERE clause
2651fn extract_join_mark_conditions(
2652    expr: &Expression,
2653    join_conditions: &mut std::collections::HashMap<String, Vec<Expression>>,
2654    remaining: &mut Vec<Expression>,
2655) {
2656    match expr {
2657        Expression::And(op) => {
2658            extract_join_mark_conditions(&op.left, join_conditions, remaining);
2659            extract_join_mark_conditions(&op.right, join_conditions, remaining);
2660        }
2661        _ => {
2662            if let Some(table) = get_join_mark_table(expr) {
2663                join_conditions
2664                    .entry(table)
2665                    .or_insert_with(Vec::new)
2666                    .push(expr.clone());
2667            } else {
2668                remaining.push(expr.clone());
2669            }
2670        }
2671    }
2672}
2673
2674/// Get the table name of a column with join mark in an expression
2675fn get_join_mark_table(expr: &Expression) -> Option<String> {
2676    match expr {
2677        Expression::Eq(op)
2678        | Expression::Neq(op)
2679        | Expression::Lt(op)
2680        | Expression::Lte(op)
2681        | Expression::Gt(op)
2682        | Expression::Gte(op) => {
2683            // Check both sides for join mark columns
2684            if let Expression::Column(col) = &op.left {
2685                if col.join_mark {
2686                    return col.table.as_ref().map(|t| t.name.clone());
2687                }
2688            }
2689            if let Expression::Column(col) = &op.right {
2690                if col.join_mark {
2691                    return col.table.as_ref().map(|t| t.name.clone());
2692                }
2693            }
2694            None
2695        }
2696        _ => None,
2697    }
2698}
2699
2700/// Clear join marks from all columns in an expression
2701fn clear_join_marks(expr: &mut Expression) {
2702    match expr {
2703        Expression::Column(col) => col.join_mark = false,
2704        Expression::Select(select) => {
2705            if let Some(ref mut w) = select.where_clause {
2706                clear_join_marks(&mut w.this);
2707            }
2708            for sel_expr in &mut select.expressions {
2709                clear_join_marks(sel_expr);
2710            }
2711        }
2712        Expression::And(op) | Expression::Or(op) => {
2713            clear_join_marks(&mut op.left);
2714            clear_join_marks(&mut op.right);
2715        }
2716        Expression::Eq(op)
2717        | Expression::Neq(op)
2718        | Expression::Lt(op)
2719        | Expression::Lte(op)
2720        | Expression::Gt(op)
2721        | Expression::Gte(op) => {
2722            clear_join_marks(&mut op.left);
2723            clear_join_marks(&mut op.right);
2724        }
2725        _ => {}
2726    }
2727}
2728
2729/// Add column names to recursive CTE definitions.
2730///
2731/// Uses projection output names in recursive CTE definitions to define the CTEs' columns.
2732/// This is required by some dialects that need explicit column names in recursive CTEs.
2733///
2734/// Reference: `transforms.py:576-592`
2735pub fn add_recursive_cte_column_names(expr: Expression) -> Result<Expression> {
2736    match expr {
2737        Expression::Select(mut select) => {
2738            if let Some(ref mut with) = select.with {
2739                if with.recursive {
2740                    let mut counter = 0;
2741                    for cte in &mut with.ctes {
2742                        if cte.columns.is_empty() {
2743                            // Try to get column names from the CTE's SELECT
2744                            if let Expression::Select(ref cte_select) = cte.this {
2745                                let names: Vec<Identifier> = cte_select
2746                                    .expressions
2747                                    .iter()
2748                                    .map(|e| match e {
2749                                        Expression::Alias(a) => a.alias.clone(),
2750                                        Expression::Column(c) => c.name.clone(),
2751                                        _ => {
2752                                            counter += 1;
2753                                            Identifier::new(format!("_c_{}", counter))
2754                                        }
2755                                    })
2756                                    .collect();
2757                                cte.columns = names;
2758                            }
2759                        }
2760                    }
2761                }
2762            }
2763            Ok(Expression::Select(select))
2764        }
2765        other => Ok(other),
2766    }
2767}
2768
2769/// Convert epoch string in CAST to timestamp literal.
2770///
2771/// Replaces `CAST('epoch' AS TIMESTAMP)` with `CAST('1970-01-01 00:00:00' AS TIMESTAMP)`
2772/// for dialects that don't support the 'epoch' keyword.
2773///
2774/// Reference: `transforms.py:595-604`
2775pub fn epoch_cast_to_ts(expr: Expression) -> Result<Expression> {
2776    match expr {
2777        Expression::Cast(mut cast) => {
2778            if let Expression::Literal(ref lit) = cast.this {
2779                if let Literal::String(ref s) = lit.as_ref() {
2780                    if s.to_lowercase() == "epoch" {
2781                        if is_temporal_type(&cast.to) {
2782                            cast.this = Expression::Literal(Box::new(Literal::String(
2783                                "1970-01-01 00:00:00".to_string(),
2784                            )));
2785                        }
2786                    }
2787                }
2788            }
2789            Ok(Expression::Cast(cast))
2790        }
2791        Expression::TryCast(mut try_cast) => {
2792            if let Expression::Literal(ref lit) = try_cast.this {
2793                if let Literal::String(ref s) = lit.as_ref() {
2794                    if s.to_lowercase() == "epoch" {
2795                        if is_temporal_type(&try_cast.to) {
2796                            try_cast.this = Expression::Literal(Box::new(Literal::String(
2797                                "1970-01-01 00:00:00".to_string(),
2798                            )));
2799                        }
2800                    }
2801                }
2802            }
2803            Ok(Expression::TryCast(try_cast))
2804        }
2805        other => Ok(other),
2806    }
2807}
2808
2809/// Check if a DataType is a temporal type (DATE, TIMESTAMP, etc.)
2810fn is_temporal_type(dt: &DataType) -> bool {
2811    matches!(
2812        dt,
2813        DataType::Date | DataType::Timestamp { .. } | DataType::Time { .. }
2814    )
2815}
2816
2817/// Ensure boolean values in conditions.
2818///
2819/// Converts numeric values used in conditions into explicit boolean expressions.
2820/// For dialects that require explicit booleans in WHERE clauses.
2821///
2822/// Converts:
2823/// ```sql
2824/// WHERE column
2825/// ```
2826/// To:
2827/// ```sql
2828/// WHERE column <> 0
2829/// ```
2830///
2831/// And:
2832/// ```sql
2833/// WHERE 1
2834/// ```
2835/// To:
2836/// ```sql
2837/// WHERE 1 <> 0
2838/// ```
2839///
2840/// Reference: `transforms.py:703-721`
2841pub fn ensure_bools(expr: Expression) -> Result<Expression> {
2842    let expr = ensure_bools_in_value_context(expr);
2843
2844    Ok(match expr {
2845        // Top-level AND/OR/NOT expressions also need ensure_bools processing
2846        Expression::And(_) | Expression::Or(_) | Expression::Not(_) => ensure_bool_condition(expr),
2847        other => other,
2848    })
2849}
2850
2851/// Recursively walk the expression tree to find Case expressions and apply
2852/// ensure_bool_condition to their WHEN conditions. This ensures that
2853/// `CASE WHEN TRUE` becomes `CASE WHEN (1 = 1)` etc.
2854fn ensure_bools_in_value_context(expr: Expression) -> Expression {
2855    match expr {
2856        Expression::Case(mut case) => {
2857            let is_simple_case = case.operand.is_some();
2858            if let Some(operand) = case.operand.take() {
2859                case.operand = Some(ensure_bools_in_value_context(operand));
2860            }
2861            case.whens = case
2862                .whens
2863                .into_iter()
2864                .map(|(condition, result)| {
2865                    let new_condition = if is_simple_case {
2866                        ensure_bools_in_value_context(condition)
2867                    } else {
2868                        ensure_bool_condition(ensure_bools_in_value_context(condition))
2869                    };
2870                    let new_result = ensure_bools_in_value_context(result);
2871                    (new_condition, new_result)
2872                })
2873                .collect();
2874            if let Some(else_expr) = case.else_ {
2875                case.else_ = Some(ensure_bools_in_value_context(else_expr));
2876            }
2877            Expression::Case(Box::new(*case))
2878        }
2879        Expression::Select(select) => Expression::Select(Box::new(ensure_bools_in_select(*select))),
2880        Expression::Subquery(mut subquery) => {
2881            subquery.this = ensure_bools_in_value_context(subquery.this);
2882            Expression::Subquery(subquery)
2883        }
2884        Expression::JoinedTable(mut joined_table) => {
2885            joined_table.left = ensure_bools_in_value_context(joined_table.left);
2886            joined_table.joins = joined_table
2887                .joins
2888                .into_iter()
2889                .map(ensure_bools_in_join)
2890                .collect();
2891            joined_table.lateral_views = joined_table
2892                .lateral_views
2893                .into_iter()
2894                .map(|mut lateral_view| {
2895                    lateral_view.this = ensure_bools_in_value_context(lateral_view.this);
2896                    lateral_view
2897                })
2898                .collect();
2899            Expression::JoinedTable(joined_table)
2900        }
2901        Expression::Union(mut union) => {
2902            let left = std::mem::replace(&mut union.left, Expression::null());
2903            let right = std::mem::replace(&mut union.right, Expression::null());
2904            union.left = ensure_bools_in_value_context(left);
2905            union.right = ensure_bools_in_value_context(right);
2906            if let Some(with) = union.with.take() {
2907                union.with = Some(ensure_bools_in_with(with));
2908            }
2909            Expression::Union(union)
2910        }
2911        Expression::Intersect(mut intersect) => {
2912            let left = std::mem::replace(&mut intersect.left, Expression::null());
2913            let right = std::mem::replace(&mut intersect.right, Expression::null());
2914            intersect.left = ensure_bools_in_value_context(left);
2915            intersect.right = ensure_bools_in_value_context(right);
2916            if let Some(with) = intersect.with.take() {
2917                intersect.with = Some(ensure_bools_in_with(with));
2918            }
2919            Expression::Intersect(intersect)
2920        }
2921        Expression::Except(mut except) => {
2922            let left = std::mem::replace(&mut except.left, Expression::null());
2923            let right = std::mem::replace(&mut except.right, Expression::null());
2924            except.left = ensure_bools_in_value_context(left);
2925            except.right = ensure_bools_in_value_context(right);
2926            if let Some(with) = except.with.take() {
2927                except.with = Some(ensure_bools_in_with(with));
2928            }
2929            Expression::Except(except)
2930        }
2931        Expression::Alias(mut alias) => {
2932            alias.this = ensure_bools_in_value_context(alias.this);
2933            Expression::Alias(alias)
2934        }
2935        Expression::Paren(mut paren) => {
2936            paren.this = ensure_bools_in_value_context(paren.this);
2937            Expression::Paren(paren)
2938        }
2939        other => other,
2940    }
2941}
2942
2943fn ensure_bools_in_select(mut select: Select) -> Select {
2944    select.expressions = select
2945        .expressions
2946        .into_iter()
2947        .map(ensure_bools_in_value_context)
2948        .collect();
2949
2950    if let Some(from) = select.from.take() {
2951        select.from = Some(crate::expressions::From {
2952            expressions: from
2953                .expressions
2954                .into_iter()
2955                .map(ensure_bools_in_value_context)
2956                .collect(),
2957        });
2958    }
2959
2960    select.joins = select.joins.into_iter().map(ensure_bools_in_join).collect();
2961
2962    if let Some(mut where_clause) = select.where_clause.take() {
2963        where_clause.this = ensure_bool_condition(ensure_bools_in_value_context(where_clause.this));
2964        select.where_clause = Some(where_clause);
2965    }
2966
2967    if let Some(mut having) = select.having.take() {
2968        having.this = ensure_bool_condition(ensure_bools_in_value_context(having.this));
2969        select.having = Some(having);
2970    }
2971
2972    if let Some(with) = select.with.take() {
2973        select.with = Some(ensure_bools_in_with(with));
2974    }
2975
2976    select
2977}
2978
2979fn ensure_bools_in_join(mut join: Join) -> Join {
2980    join.this = ensure_bools_in_value_context(join.this);
2981
2982    if let Some(on) = join.on.take() {
2983        join.on = Some(ensure_bool_condition(ensure_bools_in_value_context(on)));
2984    }
2985
2986    if let Some(match_condition) = join.match_condition.take() {
2987        join.match_condition = Some(ensure_bool_condition(ensure_bools_in_value_context(
2988            match_condition,
2989        )));
2990    }
2991
2992    join.pivots = join
2993        .pivots
2994        .into_iter()
2995        .map(ensure_bools_in_value_context)
2996        .collect();
2997
2998    join
2999}
3000
3001fn ensure_bools_in_with(mut with: With) -> With {
3002    with.ctes = with
3003        .ctes
3004        .into_iter()
3005        .map(|mut cte| {
3006            cte.this = ensure_bools_in_value_context(cte.this);
3007            cte
3008        })
3009        .collect();
3010    with
3011}
3012
3013/// Helper to check if an expression is inherently boolean (returns a boolean value).
3014/// Inherently boolean expressions include comparisons, predicates, logical operators, etc.
3015fn is_boolean_expression(expr: &Expression) -> bool {
3016    matches!(
3017        expr,
3018        Expression::Eq(_)
3019            | Expression::Neq(_)
3020            | Expression::Lt(_)
3021            | Expression::Lte(_)
3022            | Expression::Gt(_)
3023            | Expression::Gte(_)
3024            | Expression::Is(_)
3025            | Expression::IsNull(_)
3026            | Expression::IsTrue(_)
3027            | Expression::IsFalse(_)
3028            | Expression::Like(_)
3029            | Expression::ILike(_)
3030            | Expression::StartsWith(_)
3031            | Expression::SimilarTo(_)
3032            | Expression::Glob(_)
3033            | Expression::RegexpLike(_)
3034            | Expression::In(_)
3035            | Expression::Between(_)
3036            | Expression::Exists(_)
3037            | Expression::And(_)
3038            | Expression::Or(_)
3039            | Expression::Not(_)
3040            | Expression::Any(_)
3041            | Expression::All(_)
3042            | Expression::NullSafeEq(_)
3043            | Expression::NullSafeNeq(_)
3044            | Expression::EqualNull(_)
3045    )
3046}
3047
3048/// Helper to wrap a non-boolean expression with `<> 0`
3049fn wrap_neq_zero(expr: Expression) -> Expression {
3050    Expression::Neq(Box::new(BinaryOp {
3051        left: expr,
3052        right: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
3053        left_comments: vec![],
3054        operator_comments: vec![],
3055        trailing_comments: vec![],
3056        inferred_type: None,
3057    }))
3058}
3059
3060/// Helper to convert a condition expression to ensure it's boolean.
3061///
3062/// In TSQL, conditions in WHERE/HAVING must be boolean expressions.
3063/// Non-boolean expressions (columns, literals, casts, function calls, etc.)
3064/// are wrapped with `<> 0`. Boolean literals are converted to `(1 = 1)` or `(1 = 0)`.
3065pub(crate) fn ensure_bool_condition(expr: Expression) -> Expression {
3066    match expr {
3067        // For AND/OR, recursively process children
3068        Expression::And(op) => {
3069            let new_op = BinaryOp {
3070                left: ensure_bool_condition(op.left.clone()),
3071                right: ensure_bool_condition(op.right.clone()),
3072                left_comments: op.left_comments.clone(),
3073                operator_comments: op.operator_comments.clone(),
3074                trailing_comments: op.trailing_comments.clone(),
3075                inferred_type: None,
3076            };
3077            Expression::And(Box::new(new_op))
3078        }
3079        Expression::Or(op) => {
3080            let new_op = BinaryOp {
3081                left: ensure_bool_condition(op.left.clone()),
3082                right: ensure_bool_condition(op.right.clone()),
3083                left_comments: op.left_comments.clone(),
3084                operator_comments: op.operator_comments.clone(),
3085                trailing_comments: op.trailing_comments.clone(),
3086                inferred_type: None,
3087            };
3088            Expression::Or(Box::new(new_op))
3089        }
3090        // For NOT, recursively process the inner expression
3091        Expression::Not(op) => Expression::Not(Box::new(crate::expressions::UnaryOp {
3092            this: ensure_bool_condition(op.this.clone()),
3093            inferred_type: None,
3094        })),
3095        // For Paren, recurse into inner expression
3096        Expression::Paren(paren) => Expression::Paren(Box::new(crate::expressions::Paren {
3097            this: ensure_bool_condition(paren.this.clone()),
3098            trailing_comments: paren.trailing_comments.clone(),
3099        })),
3100        // Boolean literals: true -> (1 = 1), false -> (1 = 0)
3101        Expression::Boolean(BooleanLiteral { value: true }) => {
3102            Expression::Paren(Box::new(crate::expressions::Paren {
3103                this: Expression::Eq(Box::new(BinaryOp {
3104                    left: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3105                    right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3106                    left_comments: vec![],
3107                    operator_comments: vec![],
3108                    trailing_comments: vec![],
3109                    inferred_type: None,
3110                })),
3111                trailing_comments: vec![],
3112            }))
3113        }
3114        Expression::Boolean(BooleanLiteral { value: false }) => {
3115            Expression::Paren(Box::new(crate::expressions::Paren {
3116                this: Expression::Eq(Box::new(BinaryOp {
3117                    left: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3118                    right: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
3119                    left_comments: vec![],
3120                    operator_comments: vec![],
3121                    trailing_comments: vec![],
3122                    inferred_type: None,
3123                })),
3124                trailing_comments: vec![],
3125            }))
3126        }
3127        // Already boolean expressions pass through unchanged
3128        ref e if is_boolean_expression(e) => expr,
3129        // Everything else (Column, Identifier, Cast, Literal::Number, function calls, etc.)
3130        // gets wrapped with <> 0
3131        _ => wrap_neq_zero(expr),
3132    }
3133}
3134
3135/// Remove table qualifiers from column references.
3136///
3137/// Converts `table.column` to just `column` throughout the expression tree.
3138///
3139/// Reference: `transforms.py:724-730`
3140pub fn unqualify_columns(expr: Expression) -> Result<Expression> {
3141    Ok(unqualify_columns_recursive(expr))
3142}
3143
3144/// Recursively remove table qualifiers from column references
3145fn unqualify_columns_recursive(expr: Expression) -> Expression {
3146    match expr {
3147        Expression::Column(mut col) => {
3148            col.table = None;
3149            Expression::Column(col)
3150        }
3151        Expression::Select(mut select) => {
3152            select.expressions = select
3153                .expressions
3154                .into_iter()
3155                .map(unqualify_columns_recursive)
3156                .collect();
3157            if let Some(ref mut where_clause) = select.where_clause {
3158                where_clause.this = unqualify_columns_recursive(where_clause.this.clone());
3159            }
3160            if let Some(ref mut having) = select.having {
3161                having.this = unqualify_columns_recursive(having.this.clone());
3162            }
3163            if let Some(ref mut group_by) = select.group_by {
3164                group_by.expressions = group_by
3165                    .expressions
3166                    .iter()
3167                    .cloned()
3168                    .map(unqualify_columns_recursive)
3169                    .collect();
3170            }
3171            if let Some(ref mut order_by) = select.order_by {
3172                order_by.expressions = order_by
3173                    .expressions
3174                    .iter()
3175                    .map(|o| crate::expressions::Ordered {
3176                        nulls_auto: o.nulls_auto,
3177                        this: unqualify_columns_recursive(o.this.clone()),
3178                        desc: o.desc,
3179                        nulls_first: o.nulls_first,
3180                        explicit_asc: o.explicit_asc,
3181                        with_fill: o.with_fill.clone(),
3182                    })
3183                    .collect();
3184            }
3185            for join in &mut select.joins {
3186                if let Some(ref mut on) = join.on {
3187                    *on = unqualify_columns_recursive(on.clone());
3188                }
3189            }
3190            Expression::Select(select)
3191        }
3192        Expression::Alias(mut alias) => {
3193            alias.this = unqualify_columns_recursive(alias.this);
3194            Expression::Alias(alias)
3195        }
3196        // Binary operations
3197        Expression::And(op) => Expression::And(Box::new(unqualify_binary_op(*op))),
3198        Expression::Or(op) => Expression::Or(Box::new(unqualify_binary_op(*op))),
3199        Expression::Eq(op) => Expression::Eq(Box::new(unqualify_binary_op(*op))),
3200        Expression::Neq(op) => Expression::Neq(Box::new(unqualify_binary_op(*op))),
3201        Expression::Lt(op) => Expression::Lt(Box::new(unqualify_binary_op(*op))),
3202        Expression::Lte(op) => Expression::Lte(Box::new(unqualify_binary_op(*op))),
3203        Expression::Gt(op) => Expression::Gt(Box::new(unqualify_binary_op(*op))),
3204        Expression::Gte(op) => Expression::Gte(Box::new(unqualify_binary_op(*op))),
3205        Expression::Add(op) => Expression::Add(Box::new(unqualify_binary_op(*op))),
3206        Expression::Sub(op) => Expression::Sub(Box::new(unqualify_binary_op(*op))),
3207        Expression::Mul(op) => Expression::Mul(Box::new(unqualify_binary_op(*op))),
3208        Expression::Div(op) => Expression::Div(Box::new(unqualify_binary_op(*op))),
3209        // Functions
3210        Expression::Function(mut func) => {
3211            func.args = func
3212                .args
3213                .into_iter()
3214                .map(unqualify_columns_recursive)
3215                .collect();
3216            Expression::Function(func)
3217        }
3218        Expression::AggregateFunction(mut func) => {
3219            func.args = func
3220                .args
3221                .into_iter()
3222                .map(unqualify_columns_recursive)
3223                .collect();
3224            Expression::AggregateFunction(func)
3225        }
3226        Expression::Case(mut case) => {
3227            case.whens = case
3228                .whens
3229                .into_iter()
3230                .map(|(cond, result)| {
3231                    (
3232                        unqualify_columns_recursive(cond),
3233                        unqualify_columns_recursive(result),
3234                    )
3235                })
3236                .collect();
3237            if let Some(else_expr) = case.else_ {
3238                case.else_ = Some(unqualify_columns_recursive(else_expr));
3239            }
3240            Expression::Case(case)
3241        }
3242        // Other expressions pass through unchanged
3243        other => other,
3244    }
3245}
3246
3247/// Helper to unqualify columns in a binary operation
3248fn unqualify_binary_op(mut op: BinaryOp) -> BinaryOp {
3249    op.left = unqualify_columns_recursive(op.left);
3250    op.right = unqualify_columns_recursive(op.right);
3251    op
3252}
3253
3254/// Convert UNNEST(GENERATE_DATE_ARRAY(...)) to recursive CTE.
3255///
3256/// For dialects that don't support GENERATE_DATE_ARRAY, this converts:
3257/// ```sql
3258/// SELECT * FROM UNNEST(GENERATE_DATE_ARRAY('2024-01-01', '2024-01-31', INTERVAL 1 DAY)) AS d(date_value)
3259/// ```
3260/// To a recursive CTE:
3261/// ```sql
3262/// WITH RECURSIVE _generated_dates(date_value) AS (
3263///     SELECT CAST('2024-01-01' AS DATE) AS date_value
3264///     UNION ALL
3265///     SELECT CAST(DATE_ADD(date_value, 1, DAY) AS DATE)
3266///     FROM _generated_dates
3267///     WHERE CAST(DATE_ADD(date_value, 1, DAY) AS DATE) <= CAST('2024-01-31' AS DATE)
3268/// )
3269/// SELECT date_value FROM _generated_dates
3270/// ```
3271///
3272/// Reference: `transforms.py:68-122`
3273pub fn unnest_generate_date_array_using_recursive_cte(expr: Expression) -> Result<Expression> {
3274    match expr {
3275        Expression::Select(mut select) => {
3276            let mut cte_count = 0;
3277            let mut new_ctes: Vec<crate::expressions::Cte> = Vec::new();
3278
3279            // Process existing CTE bodies first (to handle CTE-wrapped GENERATE_DATE_ARRAY)
3280            if let Some(ref mut with) = select.with {
3281                for cte in &mut with.ctes {
3282                    process_expression_for_gda(&mut cte.this, &mut cte_count, &mut new_ctes);
3283                }
3284            }
3285
3286            // Process FROM clause
3287            if let Some(ref mut from) = select.from {
3288                for table_expr in &mut from.expressions {
3289                    if let Some((cte, replacement)) =
3290                        try_convert_generate_date_array(table_expr, &mut cte_count)
3291                    {
3292                        new_ctes.push(cte);
3293                        *table_expr = replacement;
3294                    }
3295                }
3296            }
3297
3298            // Process JOINs
3299            for join in &mut select.joins {
3300                if let Some((cte, replacement)) =
3301                    try_convert_generate_date_array(&join.this, &mut cte_count)
3302                {
3303                    new_ctes.push(cte);
3304                    join.this = replacement;
3305                }
3306            }
3307
3308            // Add collected CTEs to the WITH clause
3309            if !new_ctes.is_empty() {
3310                let with_clause = select.with.get_or_insert_with(|| crate::expressions::With {
3311                    ctes: Vec::new(),
3312                    recursive: true, // Recursive CTEs
3313                    leading_comments: vec![],
3314                    search: None,
3315                });
3316                with_clause.recursive = true;
3317
3318                // Prepend new CTEs before existing ones
3319                let mut all_ctes = new_ctes;
3320                all_ctes.append(&mut with_clause.ctes);
3321                with_clause.ctes = all_ctes;
3322            }
3323
3324            Ok(Expression::Select(select))
3325        }
3326        other => Ok(other),
3327    }
3328}
3329
3330/// Recursively process an expression tree to find and convert UNNEST(GENERATE_DATE_ARRAY)
3331/// inside CTE bodies, subqueries, etc.
3332fn process_expression_for_gda(
3333    expr: &mut Expression,
3334    cte_count: &mut usize,
3335    new_ctes: &mut Vec<crate::expressions::Cte>,
3336) {
3337    match expr {
3338        Expression::Select(ref mut select) => {
3339            // Process FROM clause
3340            if let Some(ref mut from) = select.from {
3341                for table_expr in &mut from.expressions {
3342                    if let Some((cte, replacement)) =
3343                        try_convert_generate_date_array(table_expr, cte_count)
3344                    {
3345                        new_ctes.push(cte);
3346                        *table_expr = replacement;
3347                    }
3348                }
3349            }
3350            // Process JOINs
3351            for join in &mut select.joins {
3352                if let Some((cte, replacement)) =
3353                    try_convert_generate_date_array(&join.this, cte_count)
3354                {
3355                    new_ctes.push(cte);
3356                    join.this = replacement;
3357                }
3358            }
3359        }
3360        Expression::Union(ref mut u) => {
3361            process_expression_for_gda(&mut u.left, cte_count, new_ctes);
3362            process_expression_for_gda(&mut u.right, cte_count, new_ctes);
3363        }
3364        Expression::Subquery(ref mut sq) => {
3365            process_expression_for_gda(&mut sq.this, cte_count, new_ctes);
3366        }
3367        _ => {}
3368    }
3369}
3370
3371/// Try to convert an UNNEST(GENERATE_DATE_ARRAY(...)) to a recursive CTE reference.
3372/// `column_name_override` allows the caller to specify a custom column name (from alias).
3373fn try_convert_generate_date_array(
3374    expr: &Expression,
3375    cte_count: &mut usize,
3376) -> Option<(crate::expressions::Cte, Expression)> {
3377    try_convert_generate_date_array_with_name(expr, cte_count, None)
3378}
3379
3380fn try_convert_generate_date_array_with_name(
3381    expr: &Expression,
3382    cte_count: &mut usize,
3383    column_name_override: Option<&str>,
3384) -> Option<(crate::expressions::Cte, Expression)> {
3385    // Helper: extract (start, end, step) from GENERATE_DATE_ARRAY/GenerateSeries variants
3386    fn extract_gda_args(
3387        inner: &Expression,
3388    ) -> Option<(&Expression, &Expression, Option<&Expression>)> {
3389        match inner {
3390            Expression::GenerateDateArray(gda) => {
3391                let start = gda.start.as_ref()?;
3392                let end = gda.end.as_ref()?;
3393                let step = gda.step.as_deref();
3394                Some((start, end, step))
3395            }
3396            Expression::GenerateSeries(gs) => {
3397                let start = gs.start.as_deref()?;
3398                let end = gs.end.as_deref()?;
3399                let step = gs.step.as_deref();
3400                Some((start, end, step))
3401            }
3402            Expression::Function(f) if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") => {
3403                if f.args.len() >= 2 {
3404                    let start = &f.args[0];
3405                    let end = &f.args[1];
3406                    let step = f.args.get(2);
3407                    Some((start, end, step))
3408                } else {
3409                    None
3410                }
3411            }
3412            _ => None,
3413        }
3414    }
3415
3416    // Look for UNNEST containing GENERATE_DATE_ARRAY
3417    if let Expression::Unnest(unnest) = expr {
3418        if let Some((start, end, step_opt)) = extract_gda_args(&unnest.this) {
3419            let start = start;
3420            let end = end;
3421            let step: Option<&Expression> = step_opt;
3422
3423            // Generate CTE name
3424            let cte_name = if *cte_count == 0 {
3425                "_generated_dates".to_string()
3426            } else {
3427                format!("_generated_dates_{}", cte_count)
3428            };
3429            *cte_count += 1;
3430
3431            let column_name =
3432                Identifier::new(column_name_override.unwrap_or("date_value").to_string());
3433
3434            // Helper: wrap expression in CAST(... AS DATE) unless already a date literal or CAST to DATE
3435            let cast_to_date = |expr: &Expression| -> Expression {
3436                match expr {
3437                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
3438                        // DATE '...' -> convert to CAST('...' AS DATE) to match expected output
3439                        if let Expression::Literal(lit) = expr {
3440                            if let Literal::Date(d) = lit.as_ref() {
3441                                Expression::Cast(Box::new(Cast {
3442                                    this: Expression::Literal(Box::new(Literal::String(d.clone()))),
3443                                    to: DataType::Date,
3444                                    trailing_comments: vec![],
3445                                    double_colon_syntax: false,
3446                                    format: None,
3447                                    default: None,
3448                                    inferred_type: None,
3449                                }))
3450                            } else {
3451                                expr.clone()
3452                            }
3453                        } else {
3454                            unreachable!()
3455                        }
3456                    }
3457                    Expression::Cast(c) if matches!(c.to, DataType::Date) => expr.clone(),
3458                    _ => Expression::Cast(Box::new(Cast {
3459                        this: expr.clone(),
3460                        to: DataType::Date,
3461                        trailing_comments: vec![],
3462                        double_colon_syntax: false,
3463                        format: None,
3464                        default: None,
3465                        inferred_type: None,
3466                    })),
3467                }
3468            };
3469
3470            // Build base case: SELECT CAST(start AS DATE) AS date_value
3471            let base_select = Select {
3472                vertica: None,
3473                expressions: vec![Expression::Alias(Box::new(crate::expressions::Alias {
3474                    this: cast_to_date(start),
3475                    alias: column_name.clone(),
3476                    column_aliases: vec![],
3477                    alias_explicit_as: false,
3478                    alias_keyword: None,
3479                    pre_alias_comments: vec![],
3480                    trailing_comments: vec![],
3481                    inferred_type: None,
3482                }))],
3483                ..Select::new()
3484            };
3485
3486            // Normalize interval: convert String("1") -> Number("1") so it generates without quotes
3487            let normalize_interval = |expr: &Expression| -> Expression {
3488                if let Expression::Interval(ref iv) = expr {
3489                    let mut iv_clone = iv.as_ref().clone();
3490                    if let Some(Expression::Literal(ref lit)) = iv_clone.this {
3491                        if let Literal::String(ref s) = lit.as_ref() {
3492                            // Convert numeric strings to Number literals for unquoted output
3493                            if s.parse::<f64>().is_ok() {
3494                                iv_clone.this =
3495                                    Some(Expression::Literal(Box::new(Literal::Number(s.clone()))));
3496                            }
3497                        }
3498                    }
3499                    Expression::Interval(Box::new(iv_clone))
3500                } else {
3501                    expr.clone()
3502                }
3503            };
3504
3505            // Build recursive case: DateAdd(date_value, count, unit) from CTE where result <= end
3506            // Extract interval unit and count from step expression
3507            let normalized_step = step.map(|s| normalize_interval(s)).unwrap_or_else(|| {
3508                Expression::Interval(Box::new(crate::expressions::Interval {
3509                    this: Some(Expression::Literal(Box::new(Literal::Number(
3510                        "1".to_string(),
3511                    )))),
3512                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
3513                        unit: crate::expressions::IntervalUnit::Day,
3514                        use_plural: false,
3515                    }),
3516                }))
3517            });
3518
3519            // Extract unit and count from interval expression to build DateAddFunc
3520            let (add_unit, add_count) = extract_interval_unit_and_count(&normalized_step);
3521
3522            let date_add_expr = Expression::DateAdd(Box::new(crate::expressions::DateAddFunc {
3523                this: Expression::Column(Box::new(crate::expressions::Column {
3524                    name: column_name.clone(),
3525                    table: None,
3526                    join_mark: false,
3527                    trailing_comments: vec![],
3528                    span: None,
3529                    inferred_type: None,
3530                })),
3531                interval: add_count,
3532                unit: add_unit,
3533            }));
3534
3535            let cast_date_add = Expression::Cast(Box::new(Cast {
3536                this: date_add_expr.clone(),
3537                to: DataType::Date,
3538                trailing_comments: vec![],
3539                double_colon_syntax: false,
3540                format: None,
3541                default: None,
3542                inferred_type: None,
3543            }));
3544
3545            let recursive_select = Select {
3546                vertica: None,
3547                expressions: vec![cast_date_add.clone()],
3548                from: Some(From {
3549                    expressions: vec![Expression::Table(Box::new(
3550                        crate::expressions::TableRef::new(&cte_name),
3551                    ))],
3552                }),
3553                where_clause: Some(Where {
3554                    this: Expression::Lte(Box::new(BinaryOp {
3555                        left: cast_date_add,
3556                        right: cast_to_date(end),
3557                        left_comments: vec![],
3558                        operator_comments: vec![],
3559                        trailing_comments: vec![],
3560                        inferred_type: None,
3561                    })),
3562                }),
3563                ..Select::new()
3564            };
3565
3566            // Build UNION ALL of base and recursive
3567            let union = crate::expressions::Union {
3568                left: Expression::Select(Box::new(base_select)),
3569                right: Expression::Select(Box::new(recursive_select)),
3570                all: true, // UNION ALL
3571                distinct: false,
3572                with: None,
3573                order_by: None,
3574                limit: None,
3575                offset: None,
3576                distribute_by: None,
3577                sort_by: None,
3578                cluster_by: None,
3579                by_name: false,
3580                side: None,
3581                kind: None,
3582                corresponding: false,
3583                strict: false,
3584                on_columns: Vec::new(),
3585            };
3586
3587            // Create CTE
3588            let cte = crate::expressions::Cte {
3589                this: Expression::Union(Box::new(union)),
3590                alias: Identifier::new(cte_name.clone()),
3591                columns: vec![column_name.clone()],
3592                materialized: None,
3593                key_expressions: Vec::new(),
3594                alias_first: true,
3595                comments: Vec::new(),
3596            };
3597
3598            // Create replacement: SELECT date_value FROM cte_name
3599            let replacement_select = Select {
3600                vertica: None,
3601                expressions: vec![Expression::Column(Box::new(crate::expressions::Column {
3602                    name: column_name,
3603                    table: None,
3604                    join_mark: false,
3605                    trailing_comments: vec![],
3606                    span: None,
3607                    inferred_type: None,
3608                }))],
3609                from: Some(From {
3610                    expressions: vec![Expression::Table(Box::new(
3611                        crate::expressions::TableRef::new(&cte_name),
3612                    ))],
3613                }),
3614                ..Select::new()
3615            };
3616
3617            let replacement = Expression::Subquery(Box::new(Subquery {
3618                this: Expression::Select(Box::new(replacement_select)),
3619                alias: Some(Identifier::new(cte_name)),
3620                column_aliases: vec![],
3621                alias_explicit_as: false,
3622                alias_keyword: None,
3623                order_by: None,
3624                limit: None,
3625                offset: None,
3626                distribute_by: None,
3627                sort_by: None,
3628                cluster_by: None,
3629                lateral: false,
3630                modifiers_inside: false,
3631                trailing_comments: vec![],
3632                inferred_type: None,
3633            }));
3634
3635            return Some((cte, replacement));
3636        }
3637    }
3638
3639    // Also check for aliased UNNEST like UNNEST(...) AS _q(date_week)
3640    if let Expression::Alias(alias) = expr {
3641        // Extract column name from alias column_aliases if present
3642        let col_name = alias.column_aliases.first().map(|id| id.name.as_str());
3643        if let Some((cte, replacement)) =
3644            try_convert_generate_date_array_with_name(&alias.this, cte_count, col_name)
3645        {
3646            // If we extracted a column name from the alias, don't preserve the outer alias
3647            // since the CTE now uses that column name directly
3648            if col_name.is_some() {
3649                return Some((cte, replacement));
3650            }
3651            let new_alias = Expression::Alias(Box::new(crate::expressions::Alias {
3652                this: replacement,
3653                alias: alias.alias.clone(),
3654                column_aliases: alias.column_aliases.clone(),
3655                alias_explicit_as: false,
3656                alias_keyword: None,
3657                pre_alias_comments: alias.pre_alias_comments.clone(),
3658                trailing_comments: alias.trailing_comments.clone(),
3659                inferred_type: None,
3660            }));
3661            return Some((cte, new_alias));
3662        }
3663    }
3664
3665    None
3666}
3667
3668/// Extract interval unit and count from an interval expression.
3669/// Handles both structured intervals (with separate unit field) and
3670/// string-encoded intervals like `INTERVAL '1 WEEK'` where unit is None
3671/// and the value contains both count and unit.
3672fn extract_interval_unit_and_count(
3673    expr: &Expression,
3674) -> (crate::expressions::IntervalUnit, Expression) {
3675    use crate::expressions::{IntervalUnit, IntervalUnitSpec, Literal};
3676
3677    if let Expression::Interval(ref iv) = expr {
3678        // First try: structured unit field
3679        if let Some(ref unit_spec) = iv.unit {
3680            if let IntervalUnitSpec::Simple { unit, .. } = unit_spec {
3681                let count = match &iv.this {
3682                    Some(e) => e.clone(),
3683                    None => Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3684                };
3685                return (unit.clone(), count);
3686            }
3687        }
3688
3689        // Second try: parse from string value like "1 WEEK" or "1"
3690        if let Some(ref val_expr) = iv.this {
3691            match val_expr {
3692                Expression::Literal(lit)
3693                    if matches!(lit.as_ref(), Literal::String(_) | Literal::Number(_)) =>
3694                {
3695                    let s = match lit.as_ref() {
3696                        Literal::String(s) | Literal::Number(s) => s,
3697                        _ => unreachable!(),
3698                    };
3699                    // Try to parse "count unit" format like "1 WEEK", "1 MONTH"
3700                    let parts: Vec<&str> = s.trim().splitn(2, char::is_whitespace).collect();
3701                    if parts.len() == 2 {
3702                        let count_str = parts[0].trim();
3703                        let unit_str = parts[1].trim().to_uppercase();
3704                        let unit = match unit_str.as_str() {
3705                            "YEAR" | "YEARS" => IntervalUnit::Year,
3706                            "QUARTER" | "QUARTERS" => IntervalUnit::Quarter,
3707                            "MONTH" | "MONTHS" => IntervalUnit::Month,
3708                            "WEEK" | "WEEKS" => IntervalUnit::Week,
3709                            "DAY" | "DAYS" => IntervalUnit::Day,
3710                            "HOUR" | "HOURS" => IntervalUnit::Hour,
3711                            "MINUTE" | "MINUTES" => IntervalUnit::Minute,
3712                            "SECOND" | "SECONDS" => IntervalUnit::Second,
3713                            "MILLISECOND" | "MILLISECONDS" => IntervalUnit::Millisecond,
3714                            "MICROSECOND" | "MICROSECONDS" => IntervalUnit::Microsecond,
3715                            _ => IntervalUnit::Day,
3716                        };
3717                        return (
3718                            unit,
3719                            Expression::Literal(Box::new(Literal::Number(count_str.to_string()))),
3720                        );
3721                    }
3722                    // Just a number with no unit - default to Day
3723                    if s.parse::<f64>().is_ok() {
3724                        return (
3725                            IntervalUnit::Day,
3726                            Expression::Literal(Box::new(Literal::Number(s.clone()))),
3727                        );
3728                    }
3729                }
3730                _ => {}
3731            }
3732        }
3733
3734        // Fallback
3735        (
3736            IntervalUnit::Day,
3737            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3738        )
3739    } else {
3740        (
3741            IntervalUnit::Day,
3742            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
3743        )
3744    }
3745}
3746
3747/// Convert ILIKE to LOWER(x) LIKE LOWER(y).
3748///
3749/// For dialects that don't support ILIKE (case-insensitive LIKE), this converts:
3750/// ```sql
3751/// SELECT * FROM t WHERE x ILIKE '%pattern%'
3752/// ```
3753/// To:
3754/// ```sql
3755/// SELECT * FROM t WHERE LOWER(x) LIKE LOWER('%pattern%')
3756/// ```
3757///
3758/// Reference: `generator.py:no_ilike_sql()`
3759pub fn no_ilike_sql(expr: Expression) -> Result<Expression> {
3760    match expr {
3761        Expression::ILike(ilike) => {
3762            // Create LOWER(left) LIKE LOWER(right)
3763            let lower_left = Expression::Function(Box::new(crate::expressions::Function {
3764                on_error: None,
3765                qualified_name: Vec::new(),
3766                source_dialect: None,
3767
3768                name: "LOWER".to_string(),
3769                args: vec![ilike.left],
3770                distinct: false,
3771                trailing_comments: vec![],
3772                use_bracket_syntax: false,
3773                no_parens: false,
3774                quoted: false,
3775                span: None,
3776                inferred_type: None,
3777            }));
3778
3779            let lower_right = Expression::Function(Box::new(crate::expressions::Function {
3780                on_error: None,
3781                qualified_name: Vec::new(),
3782                source_dialect: None,
3783
3784                name: "LOWER".to_string(),
3785                args: vec![ilike.right],
3786                distinct: false,
3787                trailing_comments: vec![],
3788                use_bracket_syntax: false,
3789                no_parens: false,
3790                quoted: false,
3791                span: None,
3792                inferred_type: None,
3793            }));
3794
3795            Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
3796                left: lower_left,
3797                right: lower_right,
3798                escape: ilike.escape,
3799                quantifier: ilike.quantifier,
3800                inferred_type: None,
3801            })))
3802        }
3803        other => Ok(other),
3804    }
3805}
3806
3807/// Convert TryCast to Cast.
3808///
3809/// For dialects that don't support TRY_CAST (safe cast that returns NULL on error),
3810/// this converts TRY_CAST to regular CAST. Note: This may cause runtime errors
3811/// for invalid casts that TRY_CAST would handle gracefully.
3812///
3813/// Reference: `generator.py:no_trycast_sql()`
3814pub fn no_trycast_sql(expr: Expression) -> Result<Expression> {
3815    match expr {
3816        Expression::TryCast(try_cast) => Ok(Expression::Cast(try_cast)),
3817        other => Ok(other),
3818    }
3819}
3820
3821/// Convert SafeCast to Cast.
3822///
3823/// For dialects that don't support SAFE_CAST (BigQuery's safe cast syntax),
3824/// this converts SAFE_CAST to regular CAST.
3825pub fn no_safe_cast_sql(expr: Expression) -> Result<Expression> {
3826    match expr {
3827        Expression::SafeCast(safe_cast) => Ok(Expression::Cast(safe_cast)),
3828        other => Ok(other),
3829    }
3830}
3831
3832/// Convert COMMENT ON statements to inline comments.
3833///
3834/// For dialects that don't support COMMENT ON syntax, this can transform
3835/// comment statements into inline comments or skip them entirely.
3836///
3837/// Reference: `generator.py:no_comment_column_constraint_sql()`
3838pub fn no_comment_column_constraint(expr: Expression) -> Result<Expression> {
3839    // For now, just pass through - comment handling is done in generator
3840    Ok(expr)
3841}
3842
3843/// Convert TABLE GENERATE_SERIES to UNNEST(GENERATE_SERIES(...)).
3844///
3845/// Some dialects use GENERATE_SERIES as a table-valued function, while others
3846/// prefer the UNNEST syntax. This converts:
3847/// ```sql
3848/// SELECT * FROM GENERATE_SERIES(1, 10) AS t(n)
3849/// ```
3850/// To:
3851/// ```sql
3852/// SELECT * FROM UNNEST(GENERATE_SERIES(1, 10)) AS _u(n)
3853/// ```
3854///
3855/// Reference: `transforms.py:125-135`
3856pub fn unnest_generate_series(expr: Expression) -> Result<Expression> {
3857    // Convert TABLE GENERATE_SERIES to UNNEST(GENERATE_SERIES(...))
3858    // This handles the case where GENERATE_SERIES is used as a table-valued function
3859    match expr {
3860        Expression::Table(ref table) => {
3861            // Check if the table name matches GENERATE_SERIES pattern
3862            // In practice, this would be Expression::GenerateSeries wrapped in a Table context
3863            if table.name.name.to_uppercase() == "GENERATE_SERIES" {
3864                // Create UNNEST wrapper
3865                let unnest = Expression::Unnest(Box::new(UnnestFunc {
3866                    this: expr.clone(),
3867                    expressions: Vec::new(),
3868                    with_ordinality: false,
3869                    alias: None,
3870                    offset_alias: None,
3871                    inferred_type: None,
3872                }));
3873
3874                // If there's an alias, wrap in alias
3875                return Ok(Expression::Alias(Box::new(crate::expressions::Alias {
3876                    this: unnest,
3877                    alias: Identifier::new("_u".to_string()),
3878                    column_aliases: vec![],
3879                    alias_explicit_as: false,
3880                    alias_keyword: None,
3881                    pre_alias_comments: vec![],
3882                    trailing_comments: vec![],
3883                    inferred_type: None,
3884                })));
3885            }
3886            Ok(expr)
3887        }
3888        Expression::GenerateSeries(gs) => {
3889            // Wrap GenerateSeries directly in UNNEST
3890            let unnest = Expression::Unnest(Box::new(UnnestFunc {
3891                this: Expression::GenerateSeries(gs),
3892                expressions: Vec::new(),
3893                with_ordinality: false,
3894                alias: None,
3895                offset_alias: None,
3896                inferred_type: None,
3897            }));
3898            Ok(unnest)
3899        }
3900        other => Ok(other),
3901    }
3902}
3903
3904/// Convert UNNEST(GENERATE_SERIES(start, end, step)) to a subquery for PostgreSQL.
3905///
3906/// PostgreSQL's GENERATE_SERIES returns rows directly, so UNNEST wrapping is unnecessary.
3907/// Instead, convert to:
3908/// ```sql
3909/// (SELECT CAST(value AS DATE) FROM GENERATE_SERIES(start, end, step) AS _t(value)) AS _unnested_generate_series
3910/// ```
3911///
3912/// This handles the case where GENERATE_DATE_ARRAY was converted to GENERATE_SERIES
3913/// during cross-dialect normalization, but the original had UNNEST wrapping.
3914pub fn unwrap_unnest_generate_series_for_postgres(expr: Expression) -> Result<Expression> {
3915    use crate::dialects::transform_recursive;
3916    transform_recursive(expr, &unwrap_unnest_generate_series_single)
3917}
3918
3919fn unwrap_unnest_generate_series_single(expr: Expression) -> Result<Expression> {
3920    use crate::expressions::*;
3921    // Match UNNEST(GENERATE_SERIES(...)) patterns in FROM clauses
3922    match expr {
3923        Expression::Select(mut select) => {
3924            // Process FROM clause
3925            if let Some(ref mut from) = select.from {
3926                for table_expr in &mut from.expressions {
3927                    if let Some(replacement) = try_unwrap_unnest_gen_series(table_expr) {
3928                        *table_expr = replacement;
3929                    }
3930                }
3931            }
3932            // Process JOINs
3933            for join in &mut select.joins {
3934                if let Some(replacement) = try_unwrap_unnest_gen_series(&join.this) {
3935                    join.this = replacement;
3936                }
3937            }
3938            Ok(Expression::Select(select))
3939        }
3940        other => Ok(other),
3941    }
3942}
3943
3944/// Try to convert an UNNEST(GENERATE_SERIES(...)) to a PostgreSQL subquery.
3945/// Returns the replacement expression if applicable.
3946fn try_unwrap_unnest_gen_series(expr: &Expression) -> Option<Expression> {
3947    use crate::expressions::*;
3948
3949    // Match Unnest containing GenerateSeries
3950    let gen_series = match expr {
3951        Expression::Unnest(unnest) => {
3952            if let Expression::GenerateSeries(ref gs) = unnest.this {
3953                Some(gs.as_ref().clone())
3954            } else {
3955                None
3956            }
3957        }
3958        Expression::Alias(alias) => {
3959            if let Expression::Unnest(ref unnest) = alias.this {
3960                if let Expression::GenerateSeries(ref gs) = unnest.this {
3961                    Some(gs.as_ref().clone())
3962                } else {
3963                    None
3964                }
3965            } else {
3966                None
3967            }
3968        }
3969        _ => None,
3970    };
3971
3972    let gs = gen_series?;
3973
3974    // Build: (SELECT CAST(value AS DATE) FROM GENERATE_SERIES(start, end, step) AS _t(value)) AS _unnested_generate_series
3975    let value_col = Expression::boxed_column(Column {
3976        name: Identifier::new("value".to_string()),
3977        table: None,
3978        join_mark: false,
3979        trailing_comments: vec![],
3980        span: None,
3981        inferred_type: None,
3982    });
3983
3984    let cast_value = Expression::Cast(Box::new(Cast {
3985        this: value_col,
3986        to: DataType::Date,
3987        trailing_comments: vec![],
3988        double_colon_syntax: false,
3989        format: None,
3990        default: None,
3991        inferred_type: None,
3992    }));
3993
3994    let gen_series_expr = Expression::GenerateSeries(Box::new(gs));
3995
3996    // GENERATE_SERIES(...) AS _t(value)
3997    let gen_series_aliased = Expression::Alias(Box::new(Alias {
3998        this: gen_series_expr,
3999        alias: Identifier::new("_t".to_string()),
4000        column_aliases: vec![Identifier::new("value".to_string())],
4001        alias_explicit_as: false,
4002        alias_keyword: None,
4003        pre_alias_comments: vec![],
4004        trailing_comments: vec![],
4005        inferred_type: None,
4006    }));
4007
4008    let mut inner_select = Select::new();
4009    inner_select.expressions = vec![cast_value];
4010    inner_select.from = Some(From {
4011        expressions: vec![gen_series_aliased],
4012    });
4013
4014    let inner_select_expr = Expression::Select(Box::new(inner_select));
4015
4016    let subquery = Expression::Subquery(Box::new(Subquery {
4017        this: inner_select_expr,
4018        alias: None,
4019        column_aliases: vec![],
4020        alias_explicit_as: false,
4021        alias_keyword: None,
4022        order_by: None,
4023        limit: None,
4024        offset: None,
4025        distribute_by: None,
4026        sort_by: None,
4027        cluster_by: None,
4028        lateral: false,
4029        modifiers_inside: false,
4030        trailing_comments: vec![],
4031        inferred_type: None,
4032    }));
4033
4034    // Wrap in alias AS _unnested_generate_series
4035    Some(Expression::Alias(Box::new(Alias {
4036        this: subquery,
4037        alias: Identifier::new("_unnested_generate_series".to_string()),
4038        column_aliases: vec![],
4039        alias_explicit_as: false,
4040        alias_keyword: None,
4041        pre_alias_comments: vec![],
4042        trailing_comments: vec![],
4043        inferred_type: None,
4044    })))
4045}
4046
4047/// Expand BETWEEN expressions in DELETE statements to >= AND <=
4048///
4049/// Some dialects (like StarRocks) don't support BETWEEN in DELETE statements
4050/// or prefer the expanded form. This transforms:
4051///   `DELETE FROM t WHERE a BETWEEN b AND c`
4052/// to:
4053///   `DELETE FROM t WHERE a >= b AND a <= c`
4054pub fn expand_between_in_delete(expr: Expression) -> Result<Expression> {
4055    match expr {
4056        Expression::Delete(mut delete) => {
4057            // If there's a WHERE clause, expand any BETWEEN expressions in it
4058            if let Some(ref mut where_clause) = delete.where_clause {
4059                where_clause.this = expand_between_recursive(where_clause.this.clone());
4060            }
4061            Ok(Expression::Delete(delete))
4062        }
4063        other => Ok(other),
4064    }
4065}
4066
4067/// Recursively expand BETWEEN expressions to >= AND <=
4068fn expand_between_recursive(expr: Expression) -> Expression {
4069    match expr {
4070        // Expand: a BETWEEN b AND c -> a >= b AND a <= c
4071        // Expand: a NOT BETWEEN b AND c -> a < b OR a > c
4072        Expression::Between(between) => {
4073            let this = expand_between_recursive(between.this.clone());
4074            let low = expand_between_recursive(between.low);
4075            let high = expand_between_recursive(between.high);
4076
4077            if between.not {
4078                // NOT BETWEEN: a < b OR a > c
4079                Expression::Or(Box::new(BinaryOp::new(
4080                    Expression::Lt(Box::new(BinaryOp::new(this.clone(), low))),
4081                    Expression::Gt(Box::new(BinaryOp::new(this, high))),
4082                )))
4083            } else {
4084                // BETWEEN: a >= b AND a <= c
4085                Expression::And(Box::new(BinaryOp::new(
4086                    Expression::Gte(Box::new(BinaryOp::new(this.clone(), low))),
4087                    Expression::Lte(Box::new(BinaryOp::new(this, high))),
4088                )))
4089            }
4090        }
4091
4092        // Recursively process AND/OR expressions
4093        Expression::And(mut op) => {
4094            op.left = expand_between_recursive(op.left);
4095            op.right = expand_between_recursive(op.right);
4096            Expression::And(op)
4097        }
4098        Expression::Or(mut op) => {
4099            op.left = expand_between_recursive(op.left);
4100            op.right = expand_between_recursive(op.right);
4101            Expression::Or(op)
4102        }
4103        Expression::Not(mut op) => {
4104            op.this = expand_between_recursive(op.this);
4105            Expression::Not(op)
4106        }
4107
4108        // Recursively process parenthesized expressions
4109        Expression::Paren(mut paren) => {
4110            paren.this = expand_between_recursive(paren.this);
4111            Expression::Paren(paren)
4112        }
4113
4114        // Pass through everything else unchanged
4115        other => other,
4116    }
4117}
4118
4119/// Push down CTE column names into SELECT expressions.
4120///
4121/// BigQuery doesn't support column names when defining a CTE, e.g.:
4122/// `WITH vartab(v) AS (SELECT ...)` is not valid.
4123/// Instead, it expects: `WITH vartab AS (SELECT ... AS v)`.
4124///
4125/// This transform removes the CTE column aliases and adds them as
4126/// aliases on the SELECT expressions.
4127pub fn pushdown_cte_column_names(expr: Expression) -> Result<Expression> {
4128    match expr {
4129        Expression::Select(mut select) => {
4130            if let Some(ref mut with) = select.with {
4131                for cte in &mut with.ctes {
4132                    if !cte.columns.is_empty() {
4133                        // Check if the CTE body is a star query - if so, just strip column names
4134                        let is_star = matches!(&cte.this, Expression::Select(s) if
4135                            s.expressions.len() == 1 && matches!(&s.expressions[0], Expression::Star(_)));
4136
4137                        if is_star {
4138                            // Can't push down column names for star queries, just remove them
4139                            cte.columns.clear();
4140                            continue;
4141                        }
4142
4143                        // Extract column names
4144                        let column_names: Vec<Identifier> = cte.columns.drain(..).collect();
4145
4146                        // Push column names down into the SELECT expressions
4147                        if let Expression::Select(ref mut inner_select) = cte.this {
4148                            let new_exprs: Vec<Expression> = inner_select
4149                                .expressions
4150                                .drain(..)
4151                                .zip(
4152                                    column_names
4153                                        .into_iter()
4154                                        .chain(std::iter::repeat_with(|| Identifier::new(""))),
4155                                )
4156                                .map(|(expr, col_name)| {
4157                                    if col_name.name.is_empty() {
4158                                        return expr;
4159                                    }
4160                                    // If already aliased, replace the alias
4161                                    match expr {
4162                                        Expression::Alias(mut a) => {
4163                                            a.alias = col_name;
4164                                            Expression::Alias(a)
4165                                        }
4166                                        other => {
4167                                            Expression::Alias(Box::new(crate::expressions::Alias {
4168                                                this: other,
4169                                                alias: col_name,
4170                                                column_aliases: Vec::new(),
4171                                                alias_explicit_as: false,
4172                                                alias_keyword: None,
4173                                                pre_alias_comments: Vec::new(),
4174                                                trailing_comments: Vec::new(),
4175                                                inferred_type: None,
4176                                            }))
4177                                        }
4178                                    }
4179                                })
4180                                .collect();
4181                            inner_select.expressions = new_exprs;
4182                        }
4183                    }
4184                }
4185            }
4186            Ok(Expression::Select(select))
4187        }
4188        other => Ok(other),
4189    }
4190}
4191
4192/// Simplify nested parentheses around VALUES in FROM clause.
4193/// Converts `FROM ((VALUES (1)))` to `FROM (VALUES (1))` by stripping redundant wrapping.
4194/// Handles various nesting patterns: Subquery(Paren(Values)), Paren(Paren(Values)), etc.
4195pub fn simplify_nested_paren_values(expr: Expression) -> Result<Expression> {
4196    match expr {
4197        Expression::Select(mut select) => {
4198            if let Some(ref mut from) = select.from {
4199                for from_item in from.expressions.iter_mut() {
4200                    simplify_paren_values_in_from(from_item);
4201                }
4202            }
4203            Ok(Expression::Select(select))
4204        }
4205        other => Ok(other),
4206    }
4207}
4208
4209fn simplify_paren_values_in_from(expr: &mut Expression) {
4210    // Check various patterns and build replacement if needed
4211    let replacement = match expr {
4212        // Subquery(Paren(Values)) -> Subquery with Values directly
4213        Expression::Subquery(ref subquery) => {
4214            if let Expression::Paren(ref paren) = subquery.this {
4215                if matches!(&paren.this, Expression::Values(_)) {
4216                    let mut new_sub = subquery.as_ref().clone();
4217                    new_sub.this = paren.this.clone();
4218                    Some(Expression::Subquery(Box::new(new_sub)))
4219                } else {
4220                    None
4221                }
4222            } else {
4223                None
4224            }
4225        }
4226        // Paren(Subquery(Values)) -> Subquery(Values) - strip the Paren wrapper
4227        // Paren(Paren(Values)) -> Paren(Values) - strip one layer
4228        Expression::Paren(ref outer_paren) => {
4229            if let Expression::Subquery(ref subquery) = outer_paren.this {
4230                // Paren(Subquery(Values)) -> Subquery(Values) - strip outer Paren
4231                if matches!(&subquery.this, Expression::Values(_)) {
4232                    Some(outer_paren.this.clone())
4233                }
4234                // Paren(Subquery(Paren(Values))) -> Subquery(Values)
4235                else if let Expression::Paren(ref paren) = subquery.this {
4236                    if matches!(&paren.this, Expression::Values(_)) {
4237                        let mut new_sub = subquery.as_ref().clone();
4238                        new_sub.this = paren.this.clone();
4239                        Some(Expression::Subquery(Box::new(new_sub)))
4240                    } else {
4241                        None
4242                    }
4243                } else {
4244                    None
4245                }
4246            } else if let Expression::Paren(ref inner_paren) = outer_paren.this {
4247                if matches!(&inner_paren.this, Expression::Values(_)) {
4248                    Some(outer_paren.this.clone())
4249                } else {
4250                    None
4251                }
4252            } else {
4253                None
4254            }
4255        }
4256        _ => None,
4257    };
4258    if let Some(new_expr) = replacement {
4259        *expr = new_expr;
4260    }
4261}
4262
4263/// Add auto-generated table aliases (like `_t0`) for POSEXPLODE/EXPLODE in FROM clause
4264/// when the alias has column_aliases but no alias name.
4265/// This is needed for Spark target: `FROM POSEXPLODE(x) AS (a, b)` -> `FROM POSEXPLODE(x) AS _t0(a, b)`
4266pub fn add_auto_table_alias(expr: Expression) -> Result<Expression> {
4267    match expr {
4268        Expression::Select(mut select) => {
4269            // Process FROM expressions
4270            if let Some(ref mut from) = select.from {
4271                let mut counter = 0usize;
4272                for from_item in from.expressions.iter_mut() {
4273                    add_auto_alias_to_from_item(from_item, &mut counter);
4274                }
4275            }
4276            Ok(Expression::Select(select))
4277        }
4278        other => Ok(other),
4279    }
4280}
4281
4282fn add_auto_alias_to_from_item(expr: &mut Expression, counter: &mut usize) {
4283    use crate::expressions::Identifier;
4284
4285    match expr {
4286        Expression::Alias(ref mut alias) => {
4287            // If the alias name is empty and there are column_aliases, add auto-generated name
4288            if alias.alias.name.is_empty() && !alias.column_aliases.is_empty() {
4289                alias.alias = Identifier::new(format!("_t{}", counter));
4290                *counter += 1;
4291            }
4292        }
4293        _ => {}
4294    }
4295}
4296
4297/// Convert BigQuery-style UNNEST aliases to column-alias format for DuckDB/Presto/Spark.
4298///
4299/// BigQuery uses: `UNNEST(arr) AS x` where x is a column alias.
4300/// DuckDB/Presto/Spark need: `UNNEST(arr) AS _t0(x)` where _t0 is a table alias and x is the column alias.
4301///
4302/// Propagate struct field names from the first named struct in an array to subsequent unnamed structs.
4303///
4304/// In BigQuery, `[STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob', 92)]` means the second struct
4305/// should inherit field names from the first: `[STRUCT('Alice' AS name, 85 AS score), STRUCT('Bob' AS name, 92 AS score)]`.
4306pub fn propagate_struct_field_names(expr: Expression) -> Result<Expression> {
4307    use crate::dialects::transform_recursive;
4308    transform_recursive(expr, &propagate_struct_names_in_expr)
4309}
4310
4311fn propagate_struct_names_in_expr(expr: Expression) -> Result<Expression> {
4312    use crate::expressions::{Alias, ArrayConstructor, Function, Identifier};
4313
4314    /// Helper to propagate struct field names within an array of expressions
4315    fn propagate_in_elements(elements: &[Expression]) -> Option<Vec<Expression>> {
4316        if elements.len() <= 1 {
4317            return None;
4318        }
4319        // Check if first element is a named STRUCT function
4320        if let Some(Expression::Function(ref first_struct)) = elements.first() {
4321            if first_struct.name.eq_ignore_ascii_case("STRUCT") {
4322                // Extract field names from first struct
4323                let field_names: Vec<Option<String>> = first_struct
4324                    .args
4325                    .iter()
4326                    .map(|arg| {
4327                        if let Expression::Alias(a) = arg {
4328                            Some(a.alias.name.clone())
4329                        } else {
4330                            None
4331                        }
4332                    })
4333                    .collect();
4334
4335                // Only propagate if first struct has at least one named field
4336                if field_names.iter().any(|n| n.is_some()) {
4337                    let mut new_elements = Vec::with_capacity(elements.len());
4338                    new_elements.push(elements[0].clone());
4339
4340                    for elem in &elements[1..] {
4341                        if let Expression::Function(ref s) = elem {
4342                            if s.name.eq_ignore_ascii_case("STRUCT")
4343                                && s.args.len() == field_names.len()
4344                            {
4345                                // Check if this struct has NO names (all unnamed)
4346                                let all_unnamed =
4347                                    s.args.iter().all(|a| !matches!(a, Expression::Alias(_)));
4348                                if all_unnamed {
4349                                    // Apply names from first struct
4350                                    let new_args: Vec<Expression> = s
4351                                        .args
4352                                        .iter()
4353                                        .zip(field_names.iter())
4354                                        .map(|(val, name)| {
4355                                            if let Some(n) = name {
4356                                                Expression::Alias(Box::new(Alias::new(
4357                                                    val.clone(),
4358                                                    Identifier::new(n.clone()),
4359                                                )))
4360                                            } else {
4361                                                val.clone()
4362                                            }
4363                                        })
4364                                        .collect();
4365                                    new_elements.push(Expression::Function(Box::new(
4366                                        Function::new("STRUCT".to_string(), new_args),
4367                                    )));
4368                                    continue;
4369                                }
4370                            }
4371                        }
4372                        new_elements.push(elem.clone());
4373                    }
4374
4375                    return Some(new_elements);
4376                }
4377            }
4378        }
4379        None
4380    }
4381
4382    // Look for Array expressions containing STRUCT function calls
4383    if let Expression::Array(ref arr) = expr {
4384        if let Some(new_elements) = propagate_in_elements(&arr.expressions) {
4385            return Ok(Expression::Array(Box::new(crate::expressions::Array {
4386                expressions: new_elements,
4387                inferred_type: arr.inferred_type.clone(),
4388            })));
4389        }
4390    }
4391
4392    // Also handle ArrayFunc (ArrayConstructor) - bracket notation [STRUCT(...), ...]
4393    if let Expression::ArrayFunc(ref arr) = expr {
4394        if let Some(new_elements) = propagate_in_elements(&arr.expressions) {
4395            return Ok(Expression::ArrayFunc(Box::new(ArrayConstructor {
4396                expressions: new_elements,
4397                bracket_notation: arr.bracket_notation,
4398                use_list_keyword: arr.use_list_keyword,
4399                inferred_type: arr.inferred_type.clone(),
4400            })));
4401        }
4402    }
4403
4404    Ok(expr)
4405}
4406
4407/// This walks the entire expression tree to find SELECT statements and converts UNNEST aliases
4408/// in their FROM clauses and JOINs.
4409pub fn unnest_alias_to_column_alias(expr: Expression) -> Result<Expression> {
4410    use crate::dialects::transform_recursive;
4411    transform_recursive(expr, &unnest_alias_transform_single_select)
4412}
4413
4414/// Move UNNEST items from FROM clause to CROSS JOINs without changing alias format.
4415/// Used for BigQuery -> BigQuery/Redshift where we want CROSS JOIN but not _t0(col) aliases.
4416pub fn unnest_from_to_cross_join(expr: Expression) -> Result<Expression> {
4417    use crate::dialects::transform_recursive;
4418    transform_recursive(expr, &unnest_from_to_cross_join_single_select)
4419}
4420
4421fn unnest_from_to_cross_join_single_select(expr: Expression) -> Result<Expression> {
4422    if let Expression::Select(mut select) = expr {
4423        if let Some(ref mut from) = select.from {
4424            if from.expressions.len() > 1 {
4425                let mut new_from_exprs = Vec::new();
4426                let mut new_cross_joins = Vec::new();
4427
4428                for (idx, from_item) in from.expressions.drain(..).enumerate() {
4429                    if idx == 0 {
4430                        new_from_exprs.push(from_item);
4431                    } else {
4432                        let is_unnest = match &from_item {
4433                            Expression::Unnest(_) => true,
4434                            Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
4435                            _ => false,
4436                        };
4437
4438                        if is_unnest {
4439                            new_cross_joins.push(crate::expressions::Join {
4440                                this: from_item,
4441                                on: None,
4442                                using: Vec::new(),
4443                                kind: JoinKind::Cross,
4444                                use_inner_keyword: false,
4445                                use_outer_keyword: false,
4446                                deferred_condition: false,
4447                                join_hint: None,
4448                                match_condition: None,
4449                                pivots: Vec::new(),
4450                                comments: Vec::new(),
4451                                nesting_group: 0,
4452                                directed: false,
4453                            });
4454                        } else {
4455                            new_from_exprs.push(from_item);
4456                        }
4457                    }
4458                }
4459
4460                from.expressions = new_from_exprs;
4461                new_cross_joins.append(&mut select.joins);
4462                select.joins = new_cross_joins;
4463            }
4464        }
4465
4466        Ok(Expression::Select(select))
4467    } else {
4468        Ok(expr)
4469    }
4470}
4471
4472/// Wrap UNNEST function aliases in JOIN items from `AS name` to `AS _u(name)`
4473/// Used for PostgreSQL → Presto/Trino transpilation where GENERATE_SERIES is
4474/// converted to UNNEST(SEQUENCE) and the alias needs the column-alias format.
4475pub fn wrap_unnest_join_aliases(expr: Expression) -> Result<Expression> {
4476    use crate::dialects::transform_recursive;
4477    transform_recursive(expr, &wrap_unnest_join_aliases_single)
4478}
4479
4480fn wrap_unnest_join_aliases_single(expr: Expression) -> Result<Expression> {
4481    if let Expression::Select(mut select) = expr {
4482        // Process JOIN items
4483        for join in &mut select.joins {
4484            wrap_unnest_alias_in_join_item(&mut join.this);
4485        }
4486        Ok(Expression::Select(select))
4487    } else {
4488        Ok(expr)
4489    }
4490}
4491
4492/// If a join item is an Alias wrapping an UNNEST function, convert alias to _u(alias_name) format
4493fn wrap_unnest_alias_in_join_item(expr: &mut Expression) {
4494    use crate::expressions::Identifier;
4495    if let Expression::Alias(alias) = expr {
4496        // Check if the inner expression is a function call to UNNEST
4497        let is_unnest = match &alias.this {
4498            Expression::Function(f) => f.name.eq_ignore_ascii_case("UNNEST"),
4499            _ => false,
4500        };
4501
4502        if is_unnest && alias.column_aliases.is_empty() {
4503            // Simple alias like `AS s` -> wrap to `AS _u(s)`
4504            let original_alias_name = alias.alias.name.clone();
4505            alias.alias = Identifier {
4506                name: "_u".to_string(),
4507                quoted: false,
4508                trailing_comments: Vec::new(),
4509                span: None,
4510            };
4511            alias.column_aliases = vec![Identifier {
4512                name: original_alias_name,
4513                quoted: false,
4514                trailing_comments: Vec::new(),
4515                span: None,
4516            }];
4517        }
4518    }
4519}
4520
4521fn unnest_alias_transform_single_select(expr: Expression) -> Result<Expression> {
4522    if let Expression::Select(mut select) = expr {
4523        let mut counter = 0usize;
4524
4525        // Process FROM expressions: convert aliases AND move UNNEST items to CROSS JOIN
4526        if let Some(ref mut from) = select.from {
4527            // First pass: convert aliases in-place
4528            for from_item in from.expressions.iter_mut() {
4529                convert_unnest_alias_in_from(from_item, &mut counter);
4530            }
4531
4532            // Second pass: move UNNEST items from FROM to CROSS JOINs
4533            if from.expressions.len() > 1 {
4534                let mut new_from_exprs = Vec::new();
4535                let mut new_cross_joins = Vec::new();
4536
4537                for (idx, from_item) in from.expressions.drain(..).enumerate() {
4538                    if idx == 0 {
4539                        // First expression always stays in FROM
4540                        new_from_exprs.push(from_item);
4541                    } else {
4542                        // Check if this is UNNEST or Alias(UNNEST)
4543                        let is_unnest = match &from_item {
4544                            Expression::Unnest(_) => true,
4545                            Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
4546                            _ => false,
4547                        };
4548
4549                        if is_unnest {
4550                            // Convert to CROSS JOIN
4551                            new_cross_joins.push(crate::expressions::Join {
4552                                this: from_item,
4553                                on: None,
4554                                using: Vec::new(),
4555                                kind: JoinKind::Cross,
4556                                use_inner_keyword: false,
4557                                use_outer_keyword: false,
4558                                deferred_condition: false,
4559                                join_hint: None,
4560                                match_condition: None,
4561                                pivots: Vec::new(),
4562                                comments: Vec::new(),
4563                                nesting_group: 0,
4564                                directed: false,
4565                            });
4566                        } else {
4567                            // Keep non-UNNEST items in FROM
4568                            new_from_exprs.push(from_item);
4569                        }
4570                    }
4571                }
4572
4573                from.expressions = new_from_exprs;
4574                // Prepend cross joins before existing joins
4575                new_cross_joins.append(&mut select.joins);
4576                select.joins = new_cross_joins;
4577            }
4578        }
4579
4580        // Process JOINs (existing joins that may have UNNEST aliases)
4581        for join in select.joins.iter_mut() {
4582            convert_unnest_alias_in_from(&mut join.this, &mut counter);
4583        }
4584
4585        Ok(Expression::Select(select))
4586    } else {
4587        Ok(expr)
4588    }
4589}
4590
4591fn convert_unnest_alias_in_from(expr: &mut Expression, counter: &mut usize) {
4592    use crate::expressions::Identifier;
4593
4594    if let Expression::Alias(ref mut alias) = expr {
4595        // Check if the inner expression is UNNEST (or EXPLODE)
4596        let is_unnest = matches!(&alias.this, Expression::Unnest(_))
4597            || matches!(&alias.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("EXPLODE"));
4598
4599        if is_unnest && alias.column_aliases.is_empty() {
4600            // Convert: UNNEST(arr) AS x -> UNNEST(arr) AS _tN(x)
4601            let col_alias = alias.alias.clone();
4602            alias.column_aliases = vec![col_alias];
4603            alias.alias = Identifier::new(format!("_t{}", counter));
4604            *counter += 1;
4605        }
4606    }
4607}
4608
4609/// Expand POSEXPLODE in SELECT expressions for DuckDB.
4610///
4611/// Converts `SELECT POSEXPLODE(x)` to `SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos, UNNEST(x) AS col`
4612/// Handles both aliased and unaliased forms:
4613/// - `SELECT POSEXPLODE(x) AS (a, b)` -> `SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b`
4614/// - `SELECT * FROM POSEXPLODE(x) AS (a, b)` -> `SELECT * FROM (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b)`
4615pub fn expand_posexplode_duckdb(expr: Expression) -> Result<Expression> {
4616    use crate::expressions::{Alias, Function};
4617
4618    match expr {
4619        Expression::Select(mut select) => {
4620            // Check if any SELECT expression is a POSEXPLODE function
4621            let mut new_expressions = Vec::new();
4622            let mut changed = false;
4623
4624            for sel_expr in select.expressions.drain(..) {
4625                // Check for POSEXPLODE(x) AS (a, b) - aliased form
4626                if let Expression::Alias(ref alias_box) = sel_expr {
4627                    if let Expression::Function(ref func) = alias_box.this {
4628                        if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
4629                            let arg = func.args[0].clone();
4630                            // Get alias names: default pos, col
4631                            let (pos_name, col_name) = if alias_box.column_aliases.len() == 2 {
4632                                (
4633                                    alias_box.column_aliases[0].name.clone(),
4634                                    alias_box.column_aliases[1].name.clone(),
4635                                )
4636                            } else if !alias_box.alias.is_empty() {
4637                                // Single alias like AS x - use as col name, "pos" for position
4638                                ("pos".to_string(), alias_box.alias.name.clone())
4639                            } else {
4640                                ("pos".to_string(), "col".to_string())
4641                            };
4642
4643                            // GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos_name
4644                            let gen_subscripts = Expression::Function(Box::new(Function::new(
4645                                "GENERATE_SUBSCRIPTS".to_string(),
4646                                vec![
4647                                    arg.clone(),
4648                                    Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4649                                ],
4650                            )));
4651                            let sub_one = Expression::Sub(Box::new(BinaryOp::new(
4652                                gen_subscripts,
4653                                Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4654                            )));
4655                            let pos_alias = Expression::Alias(Box::new(Alias {
4656                                this: sub_one,
4657                                alias: Identifier::new(pos_name),
4658                                column_aliases: Vec::new(),
4659                                alias_explicit_as: false,
4660                                alias_keyword: None,
4661                                pre_alias_comments: Vec::new(),
4662                                trailing_comments: Vec::new(),
4663                                inferred_type: None,
4664                            }));
4665
4666                            // UNNEST(x) AS col_name
4667                            let unnest = Expression::Unnest(Box::new(UnnestFunc {
4668                                this: arg,
4669                                expressions: Vec::new(),
4670                                with_ordinality: false,
4671                                alias: None,
4672                                offset_alias: None,
4673                                inferred_type: None,
4674                            }));
4675                            let col_alias = Expression::Alias(Box::new(Alias {
4676                                this: unnest,
4677                                alias: Identifier::new(col_name),
4678                                column_aliases: Vec::new(),
4679                                alias_explicit_as: false,
4680                                alias_keyword: None,
4681                                pre_alias_comments: Vec::new(),
4682                                trailing_comments: Vec::new(),
4683                                inferred_type: None,
4684                            }));
4685
4686                            new_expressions.push(pos_alias);
4687                            new_expressions.push(col_alias);
4688                            changed = true;
4689                            continue;
4690                        }
4691                    }
4692                }
4693
4694                // Check for bare POSEXPLODE(x) - unaliased form
4695                if let Expression::Function(ref func) = sel_expr {
4696                    if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
4697                        let arg = func.args[0].clone();
4698                        let pos_name = "pos";
4699                        let col_name = "col";
4700
4701                        // GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos
4702                        let gen_subscripts = Expression::Function(Box::new(Function::new(
4703                            "GENERATE_SUBSCRIPTS".to_string(),
4704                            vec![
4705                                arg.clone(),
4706                                Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4707                            ],
4708                        )));
4709                        let sub_one = Expression::Sub(Box::new(BinaryOp::new(
4710                            gen_subscripts,
4711                            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4712                        )));
4713                        let pos_alias = Expression::Alias(Box::new(Alias {
4714                            this: sub_one,
4715                            alias: Identifier::new(pos_name),
4716                            column_aliases: Vec::new(),
4717                            alias_explicit_as: false,
4718                            alias_keyword: None,
4719                            pre_alias_comments: Vec::new(),
4720                            trailing_comments: Vec::new(),
4721                            inferred_type: None,
4722                        }));
4723
4724                        // UNNEST(x) AS col
4725                        let unnest = Expression::Unnest(Box::new(UnnestFunc {
4726                            this: arg,
4727                            expressions: Vec::new(),
4728                            with_ordinality: false,
4729                            alias: None,
4730                            offset_alias: None,
4731                            inferred_type: None,
4732                        }));
4733                        let col_alias = Expression::Alias(Box::new(Alias {
4734                            this: unnest,
4735                            alias: Identifier::new(col_name),
4736                            column_aliases: Vec::new(),
4737                            alias_explicit_as: false,
4738                            alias_keyword: None,
4739                            pre_alias_comments: Vec::new(),
4740                            trailing_comments: Vec::new(),
4741                            inferred_type: None,
4742                        }));
4743
4744                        new_expressions.push(pos_alias);
4745                        new_expressions.push(col_alias);
4746                        changed = true;
4747                        continue;
4748                    }
4749                }
4750
4751                // Not a POSEXPLODE, keep as-is
4752                new_expressions.push(sel_expr);
4753            }
4754
4755            if changed {
4756                select.expressions = new_expressions;
4757            } else {
4758                select.expressions = new_expressions;
4759            }
4760
4761            // Also handle POSEXPLODE in FROM clause:
4762            // SELECT * FROM POSEXPLODE(x) AS (a, b) -> SELECT * FROM (SELECT ...)
4763            if let Some(ref mut from) = select.from {
4764                expand_posexplode_in_from_duckdb(from)?;
4765            }
4766
4767            Ok(Expression::Select(select))
4768        }
4769        other => Ok(other),
4770    }
4771}
4772
4773/// Helper to expand POSEXPLODE in FROM clause for DuckDB
4774fn expand_posexplode_in_from_duckdb(from: &mut From) -> Result<()> {
4775    use crate::expressions::{Alias, Function};
4776
4777    let mut new_expressions = Vec::new();
4778    let mut _changed = false;
4779
4780    for table_expr in from.expressions.drain(..) {
4781        // Check for POSEXPLODE(x) AS (a, b) in FROM
4782        if let Expression::Alias(ref alias_box) = table_expr {
4783            if let Expression::Function(ref func) = alias_box.this {
4784                if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
4785                    let arg = func.args[0].clone();
4786                    let (pos_name, col_name) = if alias_box.column_aliases.len() == 2 {
4787                        (
4788                            alias_box.column_aliases[0].name.clone(),
4789                            alias_box.column_aliases[1].name.clone(),
4790                        )
4791                    } else {
4792                        ("pos".to_string(), "col".to_string())
4793                    };
4794
4795                    // Create subquery: (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS a, UNNEST(x) AS b)
4796                    let gen_subscripts = Expression::Function(Box::new(Function::new(
4797                        "GENERATE_SUBSCRIPTS".to_string(),
4798                        vec![
4799                            arg.clone(),
4800                            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4801                        ],
4802                    )));
4803                    let sub_one = Expression::Sub(Box::new(BinaryOp::new(
4804                        gen_subscripts,
4805                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4806                    )));
4807                    let pos_alias = Expression::Alias(Box::new(Alias {
4808                        this: sub_one,
4809                        alias: Identifier::new(&pos_name),
4810                        column_aliases: Vec::new(),
4811                        alias_explicit_as: false,
4812                        alias_keyword: None,
4813                        pre_alias_comments: Vec::new(),
4814                        trailing_comments: Vec::new(),
4815                        inferred_type: None,
4816                    }));
4817                    let unnest = Expression::Unnest(Box::new(UnnestFunc {
4818                        this: arg,
4819                        expressions: Vec::new(),
4820                        with_ordinality: false,
4821                        alias: None,
4822                        offset_alias: None,
4823                        inferred_type: None,
4824                    }));
4825                    let col_alias = Expression::Alias(Box::new(Alias {
4826                        this: unnest,
4827                        alias: Identifier::new(&col_name),
4828                        column_aliases: Vec::new(),
4829                        alias_explicit_as: false,
4830                        alias_keyword: None,
4831                        pre_alias_comments: Vec::new(),
4832                        trailing_comments: Vec::new(),
4833                        inferred_type: None,
4834                    }));
4835
4836                    let mut inner_select = Select::new();
4837                    inner_select.expressions = vec![pos_alias, col_alias];
4838
4839                    let subquery = Expression::Subquery(Box::new(Subquery {
4840                        this: Expression::Select(Box::new(inner_select)),
4841                        alias: None,
4842                        column_aliases: Vec::new(),
4843                        alias_explicit_as: false,
4844                        alias_keyword: None,
4845                        order_by: None,
4846                        limit: None,
4847                        offset: None,
4848                        distribute_by: None,
4849                        sort_by: None,
4850                        cluster_by: None,
4851                        lateral: false,
4852                        modifiers_inside: false,
4853                        trailing_comments: Vec::new(),
4854                        inferred_type: None,
4855                    }));
4856                    new_expressions.push(subquery);
4857                    _changed = true;
4858                    continue;
4859                }
4860            }
4861        }
4862
4863        // Also check for bare POSEXPLODE(x) in FROM (no alias)
4864        if let Expression::Function(ref func) = table_expr {
4865            if func.name.eq_ignore_ascii_case("POSEXPLODE") && func.args.len() == 1 {
4866                let arg = func.args[0].clone();
4867
4868                // Create subquery: (SELECT GENERATE_SUBSCRIPTS(x, 1) - 1 AS pos, UNNEST(x) AS col)
4869                let gen_subscripts = Expression::Function(Box::new(Function::new(
4870                    "GENERATE_SUBSCRIPTS".to_string(),
4871                    vec![
4872                        arg.clone(),
4873                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4874                    ],
4875                )));
4876                let sub_one = Expression::Sub(Box::new(BinaryOp::new(
4877                    gen_subscripts,
4878                    Expression::Literal(Box::new(Literal::Number("1".to_string()))),
4879                )));
4880                let pos_alias = Expression::Alias(Box::new(Alias {
4881                    this: sub_one,
4882                    alias: Identifier::new("pos"),
4883                    column_aliases: Vec::new(),
4884                    alias_explicit_as: false,
4885                    alias_keyword: None,
4886                    pre_alias_comments: Vec::new(),
4887                    trailing_comments: Vec::new(),
4888                    inferred_type: None,
4889                }));
4890                let unnest = Expression::Unnest(Box::new(UnnestFunc {
4891                    this: arg,
4892                    expressions: Vec::new(),
4893                    with_ordinality: false,
4894                    alias: None,
4895                    offset_alias: None,
4896                    inferred_type: None,
4897                }));
4898                let col_alias = Expression::Alias(Box::new(Alias {
4899                    this: unnest,
4900                    alias: Identifier::new("col"),
4901                    column_aliases: Vec::new(),
4902                    alias_explicit_as: false,
4903                    alias_keyword: None,
4904                    pre_alias_comments: Vec::new(),
4905                    trailing_comments: Vec::new(),
4906                    inferred_type: None,
4907                }));
4908
4909                let mut inner_select = Select::new();
4910                inner_select.expressions = vec![pos_alias, col_alias];
4911
4912                let subquery = Expression::Subquery(Box::new(Subquery {
4913                    this: Expression::Select(Box::new(inner_select)),
4914                    alias: None,
4915                    column_aliases: Vec::new(),
4916                    alias_explicit_as: false,
4917                    alias_keyword: None,
4918                    order_by: None,
4919                    limit: None,
4920                    offset: None,
4921                    distribute_by: None,
4922                    sort_by: None,
4923                    cluster_by: None,
4924                    lateral: false,
4925                    modifiers_inside: false,
4926                    trailing_comments: Vec::new(),
4927                    inferred_type: None,
4928                }));
4929                new_expressions.push(subquery);
4930                _changed = true;
4931                continue;
4932            }
4933        }
4934
4935        new_expressions.push(table_expr);
4936    }
4937
4938    from.expressions = new_expressions;
4939    Ok(())
4940}
4941
4942/// Convert EXPLODE/POSEXPLODE in SELECT projections into CROSS JOIN UNNEST patterns.
4943///
4944/// This implements the `explode_projection_to_unnest` transform from Python sqlglot.
4945/// It restructures queries like:
4946///   `SELECT EXPLODE(x) FROM tbl`
4947/// into:
4948///   `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 ...`
4949///
4950/// The transform handles:
4951/// - EXPLODE(x) and POSEXPLODE(x) functions
4952/// - Name collision avoidance (_u, _u_2, ... and col, col_2, ...)
4953/// - Multiple EXPLODE/POSEXPLODE in one SELECT
4954/// - Queries with or without FROM clause
4955/// - Presto (index_offset=1) and BigQuery (index_offset=0) variants
4956pub fn explode_projection_to_unnest(expr: Expression, target: DialectType) -> Result<Expression> {
4957    match expr {
4958        Expression::Select(select) => explode_projection_to_unnest_impl(*select, target),
4959        other => Ok(other),
4960    }
4961}
4962
4963/// Snowflake-specific rewrite to mirror Python sqlglot's explode_projection_to_unnest behavior
4964/// when FLATTEN appears in a nested LATERAL within a SELECT projection.
4965///
4966/// This intentionally rewrites:
4967/// - `LATERAL FLATTEN(INPUT => x) alias`
4968/// into:
4969/// - `LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS alias(SEQ, KEY, PATH, INDEX, VALUE, THIS)`
4970/// and appends CROSS JOIN TABLE(FLATTEN(...)) range/entity joins plus alignment predicates
4971/// to the containing SELECT.
4972pub fn snowflake_flatten_projection_to_unnest(expr: Expression) -> Result<Expression> {
4973    match expr {
4974        Expression::Select(select) => snowflake_flatten_projection_to_unnest_impl(*select),
4975        other => Ok(other),
4976    }
4977}
4978
4979fn snowflake_flatten_projection_to_unnest_impl(mut select: Select) -> Result<Expression> {
4980    let mut flattened_inputs: Vec<Expression> = Vec::new();
4981    let mut new_selects: Vec<Expression> = Vec::with_capacity(select.expressions.len());
4982
4983    for sel_expr in select.expressions.into_iter() {
4984        let found_input: RefCell<Option<Expression>> = RefCell::new(None);
4985
4986        let rewritten = transform_recursive(sel_expr, &|e| {
4987            if let Expression::Lateral(lat) = e {
4988                if let Some(input_expr) = extract_flatten_input(&lat) {
4989                    if found_input.borrow().is_none() {
4990                        *found_input.borrow_mut() = Some(input_expr);
4991                    }
4992                    return Ok(Expression::Lateral(Box::new(rewrite_flatten_lateral(*lat))));
4993                }
4994                return Ok(Expression::Lateral(lat));
4995            }
4996            Ok(e)
4997        })?;
4998
4999        if let Some(input) = found_input.into_inner() {
5000            flattened_inputs.push(input);
5001        }
5002        new_selects.push(rewritten);
5003    }
5004
5005    if flattened_inputs.is_empty() {
5006        select.expressions = new_selects;
5007        return Ok(Expression::Select(Box::new(select)));
5008    }
5009
5010    select.expressions = new_selects;
5011
5012    for (idx, input_expr) in flattened_inputs.into_iter().enumerate() {
5013        // Match sqlglot naming: first pair is _u/_u_2 with pos/pos_2 and entity.
5014        let is_first = idx == 0;
5015        let series_alias = if is_first {
5016            "pos".to_string()
5017        } else {
5018            format!("pos_{}", idx + 1)
5019        };
5020        let series_source_alias = if is_first {
5021            "_u".to_string()
5022        } else {
5023            format!("_u_{}", idx * 2 + 1)
5024        };
5025        let unnest_source_alias = if is_first {
5026            "_u_2".to_string()
5027        } else {
5028            format!("_u_{}", idx * 2 + 2)
5029        };
5030        let pos2_alias = if is_first {
5031            "pos_2".to_string()
5032        } else {
5033            format!("{}_2", series_alias)
5034        };
5035        let entity_alias = if is_first {
5036            "entity".to_string()
5037        } else {
5038            format!("entity_{}", idx + 1)
5039        };
5040
5041        let array_size_call = Expression::Function(Box::new(Function::new(
5042            "ARRAY_SIZE".to_string(),
5043            vec![Expression::NamedArgument(Box::new(NamedArgument {
5044                name: Identifier::new("INPUT"),
5045                value: input_expr.clone(),
5046                separator: NamedArgSeparator::DArrow,
5047            }))],
5048        )));
5049
5050        let greatest = Expression::Function(Box::new(Function::new(
5051            "GREATEST".to_string(),
5052            vec![array_size_call.clone()],
5053        )));
5054
5055        let series_end = Expression::Add(Box::new(BinaryOp::new(
5056            Expression::Paren(Box::new(crate::expressions::Paren {
5057                this: Expression::Sub(Box::new(BinaryOp::new(
5058                    greatest,
5059                    Expression::Literal(Box::new(Literal::Number("1".to_string()))),
5060                ))),
5061                trailing_comments: Vec::new(),
5062            })),
5063            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
5064        )));
5065
5066        let series_range = Expression::Function(Box::new(Function::new(
5067            "ARRAY_GENERATE_RANGE".to_string(),
5068            vec![
5069                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
5070                series_end,
5071            ],
5072        )));
5073
5074        let series_flatten = Expression::Function(Box::new(Function::new(
5075            "FLATTEN".to_string(),
5076            vec![Expression::NamedArgument(Box::new(NamedArgument {
5077                name: Identifier::new("INPUT"),
5078                value: series_range,
5079                separator: NamedArgSeparator::DArrow,
5080            }))],
5081        )));
5082
5083        let series_table = Expression::Function(Box::new(Function::new(
5084            "TABLE".to_string(),
5085            vec![series_flatten],
5086        )));
5087
5088        let series_alias_expr = Expression::Alias(Box::new(Alias {
5089            this: series_table,
5090            alias: Identifier::new(series_source_alias.clone()),
5091            column_aliases: vec![
5092                Identifier::new("seq"),
5093                Identifier::new("key"),
5094                Identifier::new("path"),
5095                Identifier::new("index"),
5096                Identifier::new(series_alias.clone()),
5097                Identifier::new("this"),
5098            ],
5099            alias_explicit_as: false,
5100            alias_keyword: None,
5101            pre_alias_comments: Vec::new(),
5102            trailing_comments: Vec::new(),
5103            inferred_type: None,
5104        }));
5105
5106        select.joins.push(Join {
5107            this: series_alias_expr,
5108            on: None,
5109            using: Vec::new(),
5110            kind: JoinKind::Cross,
5111            use_inner_keyword: false,
5112            use_outer_keyword: false,
5113            deferred_condition: false,
5114            join_hint: None,
5115            match_condition: None,
5116            pivots: Vec::new(),
5117            comments: Vec::new(),
5118            nesting_group: 0,
5119            directed: false,
5120        });
5121
5122        let entity_flatten = Expression::Function(Box::new(Function::new(
5123            "FLATTEN".to_string(),
5124            vec![Expression::NamedArgument(Box::new(NamedArgument {
5125                name: Identifier::new("INPUT"),
5126                value: input_expr.clone(),
5127                separator: NamedArgSeparator::DArrow,
5128            }))],
5129        )));
5130
5131        let entity_table = Expression::Function(Box::new(Function::new(
5132            "TABLE".to_string(),
5133            vec![entity_flatten],
5134        )));
5135
5136        let entity_alias_expr = Expression::Alias(Box::new(Alias {
5137            this: entity_table,
5138            alias: Identifier::new(unnest_source_alias.clone()),
5139            column_aliases: vec![
5140                Identifier::new("seq"),
5141                Identifier::new("key"),
5142                Identifier::new("path"),
5143                Identifier::new(pos2_alias.clone()),
5144                Identifier::new(entity_alias.clone()),
5145                Identifier::new("this"),
5146            ],
5147            alias_explicit_as: false,
5148            alias_keyword: None,
5149            pre_alias_comments: Vec::new(),
5150            trailing_comments: Vec::new(),
5151            inferred_type: None,
5152        }));
5153
5154        select.joins.push(Join {
5155            this: entity_alias_expr,
5156            on: None,
5157            using: Vec::new(),
5158            kind: JoinKind::Cross,
5159            use_inner_keyword: false,
5160            use_outer_keyword: false,
5161            deferred_condition: false,
5162            join_hint: None,
5163            match_condition: None,
5164            pivots: Vec::new(),
5165            comments: Vec::new(),
5166            nesting_group: 0,
5167            directed: false,
5168        });
5169
5170        let pos_col =
5171            Expression::qualified_column(series_source_alias.clone(), series_alias.clone());
5172        let pos2_col =
5173            Expression::qualified_column(unnest_source_alias.clone(), pos2_alias.clone());
5174
5175        let eq = Expression::Eq(Box::new(BinaryOp::new(pos_col.clone(), pos2_col.clone())));
5176        let size_minus_1 = Expression::Paren(Box::new(crate::expressions::Paren {
5177            this: Expression::Sub(Box::new(BinaryOp::new(
5178                array_size_call,
5179                Expression::Literal(Box::new(Literal::Number("1".to_string()))),
5180            ))),
5181            trailing_comments: Vec::new(),
5182        }));
5183        let gt = Expression::Gt(Box::new(BinaryOp::new(pos_col, size_minus_1.clone())));
5184        let pos2_eq_size = Expression::Eq(Box::new(BinaryOp::new(pos2_col, size_minus_1)));
5185        let and_cond = Expression::And(Box::new(BinaryOp::new(gt, pos2_eq_size)));
5186        let or_cond = Expression::Or(Box::new(BinaryOp::new(
5187            eq,
5188            Expression::Paren(Box::new(crate::expressions::Paren {
5189                this: and_cond,
5190                trailing_comments: Vec::new(),
5191            })),
5192        )));
5193
5194        select.where_clause = Some(match select.where_clause.take() {
5195            Some(existing) => Where {
5196                this: Expression::And(Box::new(BinaryOp::new(existing.this, or_cond))),
5197            },
5198            None => Where { this: or_cond },
5199        });
5200    }
5201
5202    Ok(Expression::Select(Box::new(select)))
5203}
5204
5205fn extract_flatten_input(lat: &Lateral) -> Option<Expression> {
5206    let Expression::Function(f) = lat.this.as_ref() else {
5207        return None;
5208    };
5209    if !f.name.eq_ignore_ascii_case("FLATTEN") {
5210        return None;
5211    }
5212
5213    for arg in &f.args {
5214        if let Expression::NamedArgument(na) = arg {
5215            if na.name.name.eq_ignore_ascii_case("INPUT") {
5216                return Some(na.value.clone());
5217            }
5218        }
5219    }
5220    f.args.first().cloned()
5221}
5222
5223fn rewrite_flatten_lateral(mut lat: Lateral) -> Lateral {
5224    let cond = Expression::Eq(Box::new(BinaryOp::new(
5225        Expression::qualified_column("_u", "pos"),
5226        Expression::qualified_column("_u_2", "pos_2"),
5227    )));
5228    let true_expr = Expression::qualified_column("_u_2", "entity");
5229    let iff_expr = Expression::Function(Box::new(Function::new(
5230        "IFF".to_string(),
5231        vec![cond, true_expr, Expression::Null(crate::expressions::Null)],
5232    )));
5233
5234    lat.this = Box::new(iff_expr);
5235    if lat.column_aliases.is_empty() {
5236        lat.column_aliases = vec![
5237            "SEQ".to_string(),
5238            "KEY".to_string(),
5239            "PATH".to_string(),
5240            "INDEX".to_string(),
5241            "VALUE".to_string(),
5242            "THIS".to_string(),
5243        ];
5244    }
5245    lat
5246}
5247
5248/// Info about an EXPLODE/POSEXPLODE found in a SELECT projection
5249struct ExplodeInfo {
5250    /// The argument to EXPLODE/POSEXPLODE (the array expression)
5251    arg_sql: String,
5252    /// The alias for the exploded column
5253    explode_alias: String,
5254    /// The alias for the position column
5255    pos_alias: String,
5256    /// Source alias for this unnest (e.g., _u_2)
5257    unnest_source_alias: String,
5258}
5259
5260fn explode_projection_to_unnest_impl(select: Select, target: DialectType) -> Result<Expression> {
5261    let is_presto = matches!(
5262        target,
5263        DialectType::Presto | DialectType::Trino | DialectType::Athena
5264    );
5265    let is_bigquery = matches!(target, DialectType::BigQuery);
5266
5267    if !is_presto && !is_bigquery {
5268        return Ok(Expression::Select(Box::new(select)));
5269    }
5270
5271    // Check if any SELECT projection contains EXPLODE or POSEXPLODE
5272    let has_explode = select.expressions.iter().any(|e| expr_contains_explode(e));
5273    if !has_explode {
5274        return Ok(Expression::Select(Box::new(select)));
5275    }
5276
5277    // Collect taken names from existing SELECT expressions and FROM sources
5278    let mut taken_select_names = std::collections::HashSet::new();
5279    let mut taken_source_names = std::collections::HashSet::new();
5280
5281    // Collect names from existing SELECT expressions (output names)
5282    for sel in &select.expressions {
5283        if let Some(name) = get_output_name(sel) {
5284            taken_select_names.insert(name);
5285        }
5286    }
5287
5288    // Also add the explode arg name if it's a column reference
5289    for sel in &select.expressions {
5290        let explode_expr = find_explode_in_expr(sel);
5291        if let Some(arg) = explode_expr {
5292            if let Some(name) = get_output_name(&arg) {
5293                taken_select_names.insert(name);
5294            }
5295        }
5296    }
5297
5298    // Collect source names from FROM clause
5299    if let Some(ref from) = select.from {
5300        for from_expr in &from.expressions {
5301            collect_source_names(from_expr, &mut taken_source_names);
5302        }
5303    }
5304    // Also collect from JOINs
5305    for join in &select.joins {
5306        collect_source_names(&join.this, &mut taken_source_names);
5307    }
5308
5309    // Generate series alias
5310    let series_alias = new_name(&mut taken_select_names, "pos");
5311
5312    // Generate series source alias
5313    let series_source_alias = new_name(&mut taken_source_names, "_u");
5314
5315    // Get the target dialect for generating expression SQL
5316    let target_dialect = Dialect::get(target);
5317
5318    // Process each SELECT expression, collecting explode info
5319    let mut explode_infos: Vec<ExplodeInfo> = Vec::new();
5320    let mut new_projections: Vec<String> = Vec::new();
5321
5322    for (_idx, sel_expr) in select.expressions.iter().enumerate() {
5323        let explode_data = extract_explode_data(sel_expr);
5324
5325        if let Some((is_posexplode, arg_expr, explicit_alias, explicit_pos_alias)) = explode_data {
5326            // Generate the argument SQL in target dialect
5327            let arg_sql = target_dialect
5328                .generate(&arg_expr)
5329                .unwrap_or_else(|_| "NULL".to_string());
5330
5331            let unnest_source_alias = new_name(&mut taken_source_names, "_u");
5332
5333            let explode_alias = if let Some(ref ea) = explicit_alias {
5334                // Use the explicit alias directly (it was explicitly specified by the user)
5335                // Remove from taken_select_names first to avoid false collision with itself
5336                taken_select_names.remove(ea.as_str());
5337                // Now check for collision with other names
5338                let name = new_name(&mut taken_select_names, ea);
5339                name
5340            } else {
5341                new_name(&mut taken_select_names, "col")
5342            };
5343
5344            let pos_alias = if let Some(ref pa) = explicit_pos_alias {
5345                // Use the explicit pos alias directly
5346                taken_select_names.remove(pa.as_str());
5347                let name = new_name(&mut taken_select_names, pa);
5348                name
5349            } else {
5350                new_name(&mut taken_select_names, "pos")
5351            };
5352
5353            // Build the IF projection
5354            if is_presto {
5355                // Presto: IF(_u.pos = _u_2.pos_2, _u_2.col) AS col
5356                let if_col = format!(
5357                    "IF({}.{} = {}.{}, {}.{}) AS {}",
5358                    series_source_alias,
5359                    series_alias,
5360                    unnest_source_alias,
5361                    pos_alias,
5362                    unnest_source_alias,
5363                    explode_alias,
5364                    explode_alias
5365                );
5366                new_projections.push(if_col);
5367
5368                // For POSEXPLODE, also add the position projection
5369                if is_posexplode {
5370                    let if_pos = format!(
5371                        "IF({}.{} = {}.{}, {}.{}) AS {}",
5372                        series_source_alias,
5373                        series_alias,
5374                        unnest_source_alias,
5375                        pos_alias,
5376                        unnest_source_alias,
5377                        pos_alias,
5378                        pos_alias
5379                    );
5380                    new_projections.push(if_pos);
5381                }
5382            } else {
5383                // BigQuery: IF(pos = pos_2, col, NULL) AS col
5384                let if_col = format!(
5385                    "IF({} = {}, {}, NULL) AS {}",
5386                    series_alias, pos_alias, explode_alias, explode_alias
5387                );
5388                new_projections.push(if_col);
5389
5390                // For POSEXPLODE, also add the position projection
5391                if is_posexplode {
5392                    let if_pos = format!(
5393                        "IF({} = {}, {}, NULL) AS {}",
5394                        series_alias, pos_alias, pos_alias, pos_alias
5395                    );
5396                    new_projections.push(if_pos);
5397                }
5398            }
5399
5400            explode_infos.push(ExplodeInfo {
5401                arg_sql,
5402                explode_alias,
5403                pos_alias,
5404                unnest_source_alias,
5405            });
5406        } else {
5407            // Not an EXPLODE expression, generate as-is
5408            let sel_sql = target_dialect
5409                .generate(sel_expr)
5410                .unwrap_or_else(|_| "*".to_string());
5411            new_projections.push(sel_sql);
5412        }
5413    }
5414
5415    if explode_infos.is_empty() {
5416        return Ok(Expression::Select(Box::new(select)));
5417    }
5418
5419    // Build the FROM clause
5420    let mut from_parts: Vec<String> = Vec::new();
5421
5422    // Existing FROM sources
5423    if let Some(ref from) = select.from {
5424        for from_expr in &from.expressions {
5425            let from_sql = target_dialect.generate(from_expr).unwrap_or_default();
5426            from_parts.push(from_sql);
5427        }
5428    }
5429
5430    // Build the size expressions for the series generator
5431    let size_exprs: Vec<String> = explode_infos
5432        .iter()
5433        .map(|info| {
5434            if is_presto {
5435                format!("CARDINALITY({})", info.arg_sql)
5436            } else {
5437                format!("ARRAY_LENGTH({})", info.arg_sql)
5438            }
5439        })
5440        .collect();
5441
5442    let greatest_arg = if size_exprs.len() == 1 {
5443        size_exprs[0].clone()
5444    } else {
5445        format!("GREATEST({})", size_exprs.join(", "))
5446    };
5447
5448    // Build the series source
5449    // greatest_arg is already "GREATEST(...)" when multiple, or "CARDINALITY(x)" / "ARRAY_LENGTH(x)" when single
5450    let series_sql = if is_presto {
5451        // SEQUENCE(1, GREATEST(CARDINALITY(x))) for single, SEQUENCE(1, GREATEST(C(a), C(b))) for multiple
5452        if size_exprs.len() == 1 {
5453            format!(
5454                "UNNEST(SEQUENCE(1, GREATEST({}))) AS {}({})",
5455                greatest_arg, series_source_alias, series_alias
5456            )
5457        } else {
5458            // greatest_arg already has GREATEST(...) wrapper
5459            format!(
5460                "UNNEST(SEQUENCE(1, {})) AS {}({})",
5461                greatest_arg, series_source_alias, series_alias
5462            )
5463        }
5464    } else {
5465        // GENERATE_ARRAY(0, GREATEST(ARRAY_LENGTH(x)) - 1) for single
5466        if size_exprs.len() == 1 {
5467            format!(
5468                "UNNEST(GENERATE_ARRAY(0, GREATEST({}) - 1)) AS {}",
5469                greatest_arg, series_alias
5470            )
5471        } else {
5472            // greatest_arg already has GREATEST(...) wrapper
5473            format!(
5474                "UNNEST(GENERATE_ARRAY(0, {} - 1)) AS {}",
5475                greatest_arg, series_alias
5476            )
5477        }
5478    };
5479
5480    // Build CROSS JOIN UNNEST clauses
5481    // Always use Presto-style (WITH ORDINALITY) for the SQL string to parse,
5482    // then convert to BigQuery-style AST after parsing if needed
5483    let mut cross_joins: Vec<String> = Vec::new();
5484
5485    for info in &explode_infos {
5486        // Always use WITH ORDINALITY syntax (which our parser handles)
5487        cross_joins.push(format!(
5488            "CROSS JOIN UNNEST({}) WITH ORDINALITY AS {}({}, {})",
5489            info.arg_sql, info.unnest_source_alias, info.explode_alias, info.pos_alias
5490        ));
5491    }
5492
5493    // Build WHERE clause
5494    let mut where_conditions: Vec<String> = Vec::new();
5495
5496    for info in &explode_infos {
5497        let size_expr = if is_presto {
5498            format!("CARDINALITY({})", info.arg_sql)
5499        } else {
5500            format!("ARRAY_LENGTH({})", info.arg_sql)
5501        };
5502
5503        let cond = if is_presto {
5504            format!(
5505                "{series_src}.{series_al} = {unnest_src}.{pos_al} OR ({series_src}.{series_al} > {size} AND {unnest_src}.{pos_al} = {size})",
5506                series_src = series_source_alias,
5507                series_al = series_alias,
5508                unnest_src = info.unnest_source_alias,
5509                pos_al = info.pos_alias,
5510                size = size_expr
5511            )
5512        } else {
5513            format!(
5514                "{series_al} = {pos_al} OR ({series_al} > ({size} - 1) AND {pos_al} = ({size} - 1))",
5515                series_al = series_alias,
5516                pos_al = info.pos_alias,
5517                size = size_expr
5518            )
5519        };
5520
5521        where_conditions.push(cond);
5522    }
5523
5524    // Combine WHERE conditions with AND (wrapped in parens if multiple)
5525    let where_sql = if where_conditions.len() == 1 {
5526        where_conditions[0].clone()
5527    } else {
5528        where_conditions
5529            .iter()
5530            .map(|c| format!("({})", c))
5531            .collect::<Vec<_>>()
5532            .join(" AND ")
5533    };
5534
5535    // Build the complete SQL
5536    let select_part = new_projections.join(", ");
5537
5538    // FROM part: if there was no original FROM, the series becomes the FROM source
5539    let from_and_joins = if from_parts.is_empty() {
5540        // No original FROM: series is the FROM source, everything else is CROSS JOIN
5541        format!("FROM {} {}", series_sql, cross_joins.join(" "))
5542    } else {
5543        format!(
5544            "FROM {} {} {}",
5545            from_parts.join(", "),
5546            format!("CROSS JOIN {}", series_sql),
5547            cross_joins.join(" ")
5548        )
5549    };
5550
5551    let full_sql = format!(
5552        "SELECT {} {} WHERE {}",
5553        select_part, from_and_joins, where_sql
5554    );
5555
5556    // Parse the constructed SQL using the Generic dialect (which handles all SQL syntax)
5557    // We use Generic instead of the target dialect to avoid parser limitations
5558    let generic_dialect = Dialect::get(DialectType::Generic);
5559    let parsed = generic_dialect.parse(&full_sql);
5560    match parsed {
5561        Ok(mut stmts) if !stmts.is_empty() => {
5562            let mut result = stmts.remove(0);
5563
5564            // For BigQuery, convert Presto-style UNNEST AST to BigQuery-style
5565            // Presto: Alias(Unnest(with_ordinality=true), alias=_u_N, column_aliases=[col, pos])
5566            // BigQuery: Unnest(with_ordinality=true, alias=col, offset_alias=pos) [no outer Alias]
5567            if is_bigquery {
5568                convert_unnest_presto_to_bigquery(&mut result);
5569            }
5570
5571            Ok(result)
5572        }
5573        _ => {
5574            // If parsing fails, return the original expression unchanged
5575            Ok(Expression::Select(Box::new(select)))
5576        }
5577    }
5578}
5579
5580/// Convert Presto-style UNNEST WITH ORDINALITY to BigQuery-style UNNEST WITH OFFSET in the AST.
5581/// Presto: Alias(Unnest(with_ordinality=true), alias=_u_N, column_aliases=[col, pos_N])
5582/// BigQuery: Unnest(with_ordinality=true, alias=col, offset_alias=pos_N)
5583fn convert_unnest_presto_to_bigquery(expr: &mut Expression) {
5584    match expr {
5585        Expression::Select(ref mut select) => {
5586            // Convert in FROM clause
5587            if let Some(ref mut from) = select.from {
5588                for from_item in from.expressions.iter_mut() {
5589                    convert_unnest_presto_to_bigquery(from_item);
5590                }
5591            }
5592            // Convert in JOINs
5593            for join in select.joins.iter_mut() {
5594                convert_unnest_presto_to_bigquery(&mut join.this);
5595            }
5596        }
5597        Expression::Alias(ref alias) => {
5598            // Check if this is Alias(Unnest(with_ordinality=true), ..., column_aliases=[col, pos])
5599            if let Expression::Unnest(ref unnest) = alias.this {
5600                if unnest.with_ordinality && alias.column_aliases.len() >= 2 {
5601                    let col_alias = alias.column_aliases[0].clone();
5602                    let pos_alias = alias.column_aliases[1].clone();
5603                    let mut new_unnest = unnest.as_ref().clone();
5604                    new_unnest.alias = Some(col_alias);
5605                    new_unnest.offset_alias = Some(pos_alias);
5606                    // Replace the Alias(Unnest) with just Unnest
5607                    *expr = Expression::Unnest(Box::new(new_unnest));
5608                }
5609            }
5610        }
5611        _ => {}
5612    }
5613}
5614
5615/// Find a new name that doesn't conflict with existing names.
5616/// Tries `base`, then `base_2`, `base_3`, etc.
5617fn new_name(names: &mut std::collections::HashSet<String>, base: &str) -> String {
5618    if !names.contains(base) {
5619        names.insert(base.to_string());
5620        return base.to_string();
5621    }
5622    let mut i = 2;
5623    loop {
5624        let candidate = format!("{}_{}", base, i);
5625        if !names.contains(&candidate) {
5626            names.insert(candidate.clone());
5627            return candidate;
5628        }
5629        i += 1;
5630    }
5631}
5632
5633/// Check if an expression contains EXPLODE or POSEXPLODE
5634fn expr_contains_explode(expr: &Expression) -> bool {
5635    match expr {
5636        Expression::Explode(_) => true,
5637        Expression::ExplodeOuter(_) => true,
5638        Expression::Function(f) => {
5639            let name = f.name.to_uppercase();
5640            name == "POSEXPLODE" || name == "POSEXPLODE_OUTER"
5641        }
5642        Expression::Alias(a) => expr_contains_explode(&a.this),
5643        _ => false,
5644    }
5645}
5646
5647/// Find the EXPLODE/POSEXPLODE expression within a select item, return the arg
5648fn find_explode_in_expr(expr: &Expression) -> Option<Expression> {
5649    match expr {
5650        Expression::Explode(uf) => Some(uf.this.clone()),
5651        Expression::ExplodeOuter(uf) => Some(uf.this.clone()),
5652        Expression::Function(f) => {
5653            let name = f.name.to_uppercase();
5654            if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
5655                Some(f.args[0].clone())
5656            } else {
5657                None
5658            }
5659        }
5660        Expression::Alias(a) => find_explode_in_expr(&a.this),
5661        _ => None,
5662    }
5663}
5664
5665/// Extract explode data from a SELECT expression.
5666/// Returns (is_posexplode, arg_expression, explicit_col_alias, explicit_pos_alias)
5667fn extract_explode_data(
5668    expr: &Expression,
5669) -> Option<(bool, Expression, Option<String>, Option<String>)> {
5670    match expr {
5671        // Bare EXPLODE(x) without alias
5672        Expression::Explode(uf) => Some((false, uf.this.clone(), None, None)),
5673        Expression::ExplodeOuter(uf) => Some((false, uf.this.clone(), None, None)),
5674        // Bare POSEXPLODE(x) without alias
5675        Expression::Function(f) => {
5676            let name = f.name.to_uppercase();
5677            if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
5678                Some((true, f.args[0].clone(), None, None))
5679            } else {
5680                None
5681            }
5682        }
5683        // Aliased: EXPLODE(x) AS col, or POSEXPLODE(x) AS (a, b)
5684        Expression::Alias(a) => {
5685            match &a.this {
5686                Expression::Explode(uf) => {
5687                    let alias = if !a.alias.is_empty() {
5688                        Some(a.alias.name.clone())
5689                    } else {
5690                        None
5691                    };
5692                    Some((false, uf.this.clone(), alias, None))
5693                }
5694                Expression::ExplodeOuter(uf) => {
5695                    let alias = if !a.alias.is_empty() {
5696                        Some(a.alias.name.clone())
5697                    } else {
5698                        None
5699                    };
5700                    Some((false, uf.this.clone(), alias, None))
5701                }
5702                Expression::Function(f) => {
5703                    let name = f.name.to_uppercase();
5704                    if (name == "POSEXPLODE" || name == "POSEXPLODE_OUTER") && !f.args.is_empty() {
5705                        // Check for column aliases: AS (a, b)
5706                        if a.column_aliases.len() == 2 {
5707                            let pos_alias = a.column_aliases[0].name.clone();
5708                            let col_alias = a.column_aliases[1].name.clone();
5709                            Some((true, f.args[0].clone(), Some(col_alias), Some(pos_alias)))
5710                        } else if !a.alias.is_empty() {
5711                            // Single alias: AS x
5712                            Some((true, f.args[0].clone(), Some(a.alias.name.clone()), None))
5713                        } else {
5714                            Some((true, f.args[0].clone(), None, None))
5715                        }
5716                    } else {
5717                        None
5718                    }
5719                }
5720                _ => None,
5721            }
5722        }
5723        _ => None,
5724    }
5725}
5726
5727/// Get the output name of a SELECT expression
5728fn get_output_name(expr: &Expression) -> Option<String> {
5729    match expr {
5730        Expression::Alias(a) => {
5731            if !a.alias.is_empty() {
5732                Some(a.alias.name.clone())
5733            } else {
5734                None
5735            }
5736        }
5737        Expression::Column(c) => Some(c.name.name.clone()),
5738        Expression::Identifier(id) => Some(id.name.clone()),
5739        _ => None,
5740    }
5741}
5742
5743/// Collect source names from a FROM/JOIN expression
5744fn collect_source_names(expr: &Expression, names: &mut std::collections::HashSet<String>) {
5745    match expr {
5746        Expression::Alias(a) => {
5747            if !a.alias.is_empty() {
5748                names.insert(a.alias.name.clone());
5749            }
5750        }
5751        Expression::Subquery(s) => {
5752            if let Some(ref alias) = s.alias {
5753                names.insert(alias.name.clone());
5754            }
5755        }
5756        Expression::Table(t) => {
5757            if let Some(ref alias) = t.alias {
5758                names.insert(alias.name.clone());
5759            } else {
5760                names.insert(t.name.name.clone());
5761            }
5762        }
5763        Expression::Column(c) => {
5764            names.insert(c.name.name.clone());
5765        }
5766        Expression::Identifier(id) => {
5767            names.insert(id.name.clone());
5768        }
5769        _ => {}
5770    }
5771}
5772
5773/// Strip UNNEST wrapping from column reference arguments for Redshift target.
5774/// BigQuery UNNEST(column_ref) -> Redshift: just column_ref
5775pub fn strip_unnest_column_refs(expr: Expression) -> Result<Expression> {
5776    use crate::dialects::transform_recursive;
5777    transform_recursive(expr, &strip_unnest_column_refs_single)
5778}
5779
5780fn strip_unnest_column_refs_single(expr: Expression) -> Result<Expression> {
5781    if let Expression::Select(mut select) = expr {
5782        // Process JOINs (UNNEST items have been moved to joins by unnest_from_to_cross_join)
5783        for join in select.joins.iter_mut() {
5784            strip_unnest_from_expr(&mut join.this);
5785        }
5786        // Process FROM items too
5787        if let Some(ref mut from) = select.from {
5788            for from_item in from.expressions.iter_mut() {
5789                strip_unnest_from_expr(from_item);
5790            }
5791        }
5792        Ok(Expression::Select(select))
5793    } else {
5794        Ok(expr)
5795    }
5796}
5797
5798/// If expr is Alias(UNNEST(column_ref), alias) where UNNEST arg is a column/dot path,
5799/// replace with Alias(column_ref, alias) to strip the UNNEST.
5800fn strip_unnest_from_expr(expr: &mut Expression) {
5801    if let Expression::Alias(ref mut alias) = expr {
5802        if let Expression::Unnest(ref unnest) = alias.this {
5803            let is_column_ref = matches!(&unnest.this, Expression::Column(_) | Expression::Dot(_));
5804            if is_column_ref {
5805                // Replace UNNEST(col_ref) with just col_ref
5806                let inner = unnest.this.clone();
5807                alias.this = inner;
5808            }
5809        }
5810    }
5811}
5812
5813/// Wrap DuckDB UNNEST of struct arrays in (SELECT UNNEST(..., max_depth => 2)) subquery.
5814/// BigQuery UNNEST of struct arrays needs this wrapping for DuckDB to properly expand struct fields.
5815pub fn wrap_duckdb_unnest_struct(expr: Expression) -> Result<Expression> {
5816    use crate::dialects::transform_recursive;
5817    transform_recursive(expr, &wrap_duckdb_unnest_struct_single)
5818}
5819
5820fn wrap_duckdb_unnest_struct_single(expr: Expression) -> Result<Expression> {
5821    if let Expression::Select(mut select) = expr {
5822        // Process FROM items
5823        if let Some(ref mut from) = select.from {
5824            for from_item in from.expressions.iter_mut() {
5825                try_wrap_unnest_in_subquery(from_item);
5826            }
5827        }
5828
5829        // Process JOINs
5830        for join in select.joins.iter_mut() {
5831            try_wrap_unnest_in_subquery(&mut join.this);
5832        }
5833
5834        Ok(Expression::Select(select))
5835    } else {
5836        Ok(expr)
5837    }
5838}
5839
5840/// Check if an expression contains struct array elements that need DuckDB UNNEST wrapping.
5841fn is_struct_array_unnest_arg(expr: &Expression) -> bool {
5842    match expr {
5843        // Array literal containing struct elements
5844        Expression::Array(arr) => arr
5845            .expressions
5846            .iter()
5847            .any(|e| matches!(e, Expression::Struct(_))),
5848        Expression::ArrayFunc(arr) => arr
5849            .expressions
5850            .iter()
5851            .any(|e| matches!(e, Expression::Struct(_))),
5852        // CAST to struct array type, e.g. CAST([] AS STRUCT(x BIGINT)[])
5853        Expression::Cast(c) => {
5854            matches!(&c.to, DataType::Array { element_type, .. } if matches!(**element_type, DataType::Struct { .. }))
5855        }
5856        _ => false,
5857    }
5858}
5859
5860/// Try to wrap an UNNEST expression in a (SELECT UNNEST(..., max_depth => 2)) subquery.
5861/// Handles both bare UNNEST and Alias(UNNEST).
5862fn try_wrap_unnest_in_subquery(expr: &mut Expression) {
5863    // Check for Alias wrapping UNNEST
5864    if let Expression::Alias(ref alias) = expr {
5865        if let Expression::Unnest(ref unnest) = alias.this {
5866            if is_struct_array_unnest_arg(&unnest.this) {
5867                let unnest_clone = (**unnest).clone();
5868                let alias_name = alias.alias.clone();
5869                let new_expr = make_unnest_subquery(unnest_clone, Some(alias_name));
5870                *expr = new_expr;
5871                return;
5872            }
5873        }
5874    }
5875
5876    // Check for bare UNNEST
5877    if let Expression::Unnest(ref unnest) = expr {
5878        if is_struct_array_unnest_arg(&unnest.this) {
5879            let unnest_clone = (**unnest).clone();
5880            let new_expr = make_unnest_subquery(unnest_clone, None);
5881            *expr = new_expr;
5882        }
5883    }
5884}
5885
5886/// Create (SELECT UNNEST(arg, max_depth => 2)) [AS alias] subquery.
5887fn make_unnest_subquery(unnest: UnnestFunc, alias: Option<Identifier>) -> Expression {
5888    // Build UNNEST function call with max_depth => 2 named argument
5889    let max_depth_arg = Expression::NamedArgument(Box::new(NamedArgument {
5890        name: Identifier::new("max_depth".to_string()),
5891        value: Expression::Literal(Box::new(Literal::Number("2".to_string()))),
5892        separator: NamedArgSeparator::DArrow,
5893    }));
5894
5895    let mut unnest_args = vec![unnest.this];
5896    unnest_args.extend(unnest.expressions);
5897    unnest_args.push(max_depth_arg);
5898
5899    let unnest_func =
5900        Expression::Function(Box::new(Function::new("UNNEST".to_string(), unnest_args)));
5901
5902    // Build SELECT UNNEST(...)
5903    let mut inner_select = Select::new();
5904    inner_select.expressions = vec![unnest_func];
5905    let inner_select = Expression::Select(Box::new(inner_select));
5906
5907    // Wrap in subquery
5908    let subquery = Subquery {
5909        this: inner_select,
5910        alias,
5911        column_aliases: Vec::new(),
5912        alias_explicit_as: false,
5913        alias_keyword: None,
5914        order_by: None,
5915        limit: None,
5916        offset: None,
5917        distribute_by: None,
5918        sort_by: None,
5919        cluster_by: None,
5920        lateral: false,
5921        modifiers_inside: false,
5922        trailing_comments: Vec::new(),
5923        inferred_type: None,
5924    };
5925
5926    Expression::Subquery(Box::new(subquery))
5927}
5928
5929/// Wrap UNION with ORDER BY/LIMIT in a subquery.
5930///
5931/// Some dialects (ClickHouse, TSQL) don't support ORDER BY/LIMIT directly on UNION.
5932/// This transform converts:
5933///   SELECT ... UNION SELECT ... ORDER BY x LIMIT n
5934/// to:
5935///   SELECT * FROM (SELECT ... UNION SELECT ...) AS _l_0 ORDER BY x LIMIT n
5936///
5937/// NOTE: Our parser may place ORDER BY/LIMIT on the right-hand SELECT rather than
5938/// the Union (unlike Python sqlglot). This function handles both cases by checking
5939/// the right-hand SELECT for trailing ORDER BY/LIMIT and moving them to the Union.
5940pub fn no_limit_order_by_union(expr: Expression) -> Result<Expression> {
5941    use crate::expressions::{split_set_limit, Limit, Offset as OffsetClause, OrderBy, Star};
5942
5943    match expr {
5944        Expression::Union(mut u) => {
5945            // Check if ORDER BY/LIMIT are on the rightmost Select instead of the Union
5946            // (our parser may attach them to the right SELECT)
5947            if u.order_by.is_none() && u.limit.is_none() && u.offset.is_none() {
5948                // Find the rightmost Select and check for ORDER BY/LIMIT
5949                if let Expression::Select(ref mut right_select) = u.right {
5950                    if right_select.order_by.is_some()
5951                        || right_select.limit.is_some()
5952                        || right_select.offset.is_some()
5953                    {
5954                        // Move ORDER BY/LIMIT from right Select to Union
5955                        u.order_by = right_select.order_by.take();
5956                        u.limit = right_select.limit.take().map(Limit::into_set_limit);
5957                        u.offset = right_select.offset.take().map(|o| Box::new(o.this));
5958                    }
5959                }
5960            }
5961
5962            let has_order_or_limit =
5963                u.order_by.is_some() || u.limit.is_some() || u.offset.is_some();
5964            if has_order_or_limit {
5965                // Extract ORDER BY, LIMIT, OFFSET from the Union
5966                let order_by: Option<OrderBy> = u.order_by.take();
5967                let union_limit: Option<Box<Expression>> = u.limit.take();
5968                let union_offset: Option<Box<Expression>> = u.offset.take();
5969
5970                let (select_limit, select_fetch) = split_set_limit(union_limit);
5971
5972                // Convert Union's offset (Box<Expression>) to Select's offset (Offset struct)
5973                let select_offset: Option<OffsetClause> = union_offset.map(|o| OffsetClause {
5974                    this: *o,
5975                    rows: None,
5976                });
5977
5978                // Create a subquery from the Union
5979                let subquery = Subquery {
5980                    this: Expression::Union(u),
5981                    alias: Some(Identifier::new("_l_0")),
5982                    column_aliases: Vec::new(),
5983                    alias_explicit_as: true,
5984                    alias_keyword: None,
5985                    lateral: false,
5986                    modifiers_inside: false,
5987                    order_by: None,
5988                    limit: None,
5989                    offset: None,
5990                    distribute_by: None,
5991                    sort_by: None,
5992                    cluster_by: None,
5993                    trailing_comments: Vec::new(),
5994                    inferred_type: None,
5995                };
5996
5997                // Build SELECT * FROM (UNION) AS _l_0 ORDER BY ... LIMIT ...
5998                let mut select = Select::default();
5999                select.expressions = vec![Expression::Star(Star {
6000                    table: None,
6001                    except: None,
6002                    replace: None,
6003                    rename: None,
6004                    trailing_comments: Vec::new(),
6005                    span: None,
6006                })];
6007                select.from = Some(From {
6008                    expressions: vec![Expression::Subquery(Box::new(subquery))],
6009                });
6010                select.order_by = order_by;
6011                select.limit = select_limit;
6012                select.fetch = select_fetch;
6013                select.offset = select_offset;
6014
6015                Ok(Expression::Select(Box::new(select)))
6016            } else {
6017                Ok(Expression::Union(u))
6018            }
6019        }
6020        _ => Ok(expr),
6021    }
6022}
6023
6024/// Expand LIKE ANY / ILIKE ANY to OR chains.
6025///
6026/// For dialects that don't support quantifiers on LIKE/ILIKE (e.g. DuckDB),
6027/// expand `x LIKE ANY (('a', 'b'))` to `x LIKE 'a' OR x LIKE 'b'`.
6028///
6029/// Handles precedence: when LIKE ANY (→OR) is inside AND, wraps in parens.
6030/// When LIKE ALL (→AND) is inside OR, wraps in parens for readability.
6031pub fn expand_like_any(expr: Expression) -> Result<Expression> {
6032    use crate::expressions::{BinaryOp, LikeOp, Paren};
6033
6034    /// Sentinel comment used to mark Paren nodes created by LIKE ALL expansion.
6035    /// These markers are stripped in a cleanup pass unless they end up inside an OR parent.
6036    const LIKE_ALL_MARKER: &str = "__LIKE_ALL_EXPANSION__";
6037
6038    fn unwrap_parens(e: &Expression) -> &Expression {
6039        match e {
6040            Expression::Paren(p) => unwrap_parens(&p.this),
6041            _ => e,
6042        }
6043    }
6044
6045    fn extract_tuple_values(e: &Expression) -> Option<Vec<Expression>> {
6046        let inner = unwrap_parens(e);
6047        match inner {
6048            Expression::Tuple(t) => Some(t.expressions.clone()),
6049            // Single value in parens: treat as single-element list
6050            _ if !matches!(e, Expression::Tuple(_)) => Some(vec![inner.clone()]),
6051            _ => None,
6052        }
6053    }
6054
6055    /// Build a chain of LIKE/ILIKE conditions joined by a combiner (OR for ANY, AND for ALL).
6056    fn expand_like_quantifier(
6057        op: &LikeOp,
6058        values: Vec<Expression>,
6059        is_ilike: bool,
6060        combiner: fn(Expression, Expression) -> Expression,
6061        wrap_marker: bool,
6062    ) -> Expression {
6063        let num_values = values.len();
6064        let mut result: Option<Expression> = None;
6065        for val in values {
6066            let like = if is_ilike {
6067                Expression::ILike(Box::new(LikeOp {
6068                    left: op.left.clone(),
6069                    right: val,
6070                    escape: op.escape.clone(),
6071                    quantifier: None,
6072                    inferred_type: None,
6073                }))
6074            } else {
6075                Expression::Like(Box::new(LikeOp {
6076                    left: op.left.clone(),
6077                    right: val,
6078                    escape: op.escape.clone(),
6079                    quantifier: None,
6080                    inferred_type: None,
6081                }))
6082            };
6083            result = Some(match result {
6084                None => like,
6085                Some(prev) => combiner(prev, like),
6086            });
6087        }
6088        let expanded = result.unwrap_or_else(|| unreachable!("values is non-empty"));
6089        // For LIKE ALL (AND chain) with multiple values, wrap in a marker Paren.
6090        // The marker lets us distinguish expansion-created AND from parser-created AND
6091        // when deciding whether to keep parens inside OR.
6092        if wrap_marker && num_values > 1 {
6093            Expression::Paren(Box::new(Paren {
6094                this: expanded,
6095                trailing_comments: vec![LIKE_ALL_MARKER.to_string()],
6096            }))
6097        } else {
6098            expanded
6099        }
6100    }
6101
6102    fn or_combiner(a: Expression, b: Expression) -> Expression {
6103        Expression::Or(Box::new(BinaryOp::new(a, b)))
6104    }
6105
6106    fn and_combiner(a: Expression, b: Expression) -> Expression {
6107        Expression::And(Box::new(BinaryOp::new(a, b)))
6108    }
6109
6110    fn is_like_all_marker(p: &Paren) -> bool {
6111        p.trailing_comments.len() == 1 && p.trailing_comments[0] == LIKE_ALL_MARKER
6112    }
6113
6114    // Phase 1: Expand LIKE ANY/ALL and fix precedence in a single bottom-up pass.
6115    //
6116    // - LIKE ANY → bare Or chain
6117    // - LIKE ALL → Paren(And chain) with marker comment
6118    // - And handler: wraps bare Or children in Paren (from LIKE ANY expansion;
6119    //   parser never creates bare Or inside And)
6120    // - Or handler: converts marker Paren to clean Paren (keeps the wrapping)
6121    let result = transform_recursive(expr, &|e| {
6122        match e {
6123            // LIKE ANY -> OR chain (bare)
6124            Expression::Like(ref op) if op.quantifier.as_deref() == Some("ANY") => {
6125                if let Some(values) = extract_tuple_values(&op.right) {
6126                    if values.is_empty() {
6127                        return Ok(e);
6128                    }
6129                    Ok(expand_like_quantifier(
6130                        op,
6131                        values,
6132                        false,
6133                        or_combiner,
6134                        false,
6135                    ))
6136                } else {
6137                    Ok(e)
6138                }
6139            }
6140            // LIKE ALL -> AND chain (with marker Paren)
6141            Expression::Like(ref op) if op.quantifier.as_deref() == Some("ALL") => {
6142                if let Some(values) = extract_tuple_values(&op.right) {
6143                    if values.is_empty() {
6144                        return Ok(e);
6145                    }
6146                    Ok(expand_like_quantifier(
6147                        op,
6148                        values,
6149                        false,
6150                        and_combiner,
6151                        true,
6152                    ))
6153                } else {
6154                    Ok(e)
6155                }
6156            }
6157            // ILIKE ANY -> OR chain (bare)
6158            Expression::ILike(ref op) if op.quantifier.as_deref() == Some("ANY") => {
6159                if let Some(values) = extract_tuple_values(&op.right) {
6160                    if values.is_empty() {
6161                        return Ok(e);
6162                    }
6163                    Ok(expand_like_quantifier(op, values, true, or_combiner, false))
6164                } else {
6165                    Ok(e)
6166                }
6167            }
6168            // ILIKE ALL -> AND chain (with marker Paren)
6169            Expression::ILike(ref op) if op.quantifier.as_deref() == Some("ALL") => {
6170                if let Some(values) = extract_tuple_values(&op.right) {
6171                    if values.is_empty() {
6172                        return Ok(e);
6173                    }
6174                    Ok(expand_like_quantifier(op, values, true, and_combiner, true))
6175                } else {
6176                    Ok(e)
6177                }
6178            }
6179            // After children are expanded (bottom-up), fix And nodes:
6180            // Wrap bare Or children in Paren (from LIKE ANY expansion).
6181            // The parser never produces bare Or inside And (AND binds tighter than OR,
6182            // so explicit parens in SQL like "(a OR b) AND c" create Paren(Or(...)) in the AST).
6183            Expression::And(mut op) => {
6184                if matches!(&op.left, Expression::Or(_)) {
6185                    op.left = Expression::Paren(Box::new(Paren {
6186                        this: op.left,
6187                        trailing_comments: vec![],
6188                    }));
6189                }
6190                if matches!(&op.right, Expression::Or(_)) {
6191                    op.right = Expression::Paren(Box::new(Paren {
6192                        this: op.right,
6193                        trailing_comments: vec![],
6194                    }));
6195                }
6196                Ok(Expression::And(op))
6197            }
6198            // After children are expanded (bottom-up), fix Or nodes:
6199            // Convert marker Paren(And) to clean Paren(And) so the cleanup pass won't strip it.
6200            Expression::Or(mut op) => {
6201                if let Expression::Paren(ref mut p) = op.left {
6202                    if is_like_all_marker(p) {
6203                        p.trailing_comments.clear();
6204                    }
6205                }
6206                if let Expression::Paren(ref mut p) = op.right {
6207                    if is_like_all_marker(p) {
6208                        p.trailing_comments.clear();
6209                    }
6210                }
6211                Ok(Expression::Or(op))
6212            }
6213            _ => Ok(e),
6214        }
6215    })?;
6216
6217    // Phase 2: Strip remaining marker Paren(And) nodes that weren't inside an Or parent.
6218    // These are standalone LIKE ALL expansions (e.g., in SELECT expressions) that don't
6219    // need parentheses.
6220    transform_recursive(result, &|e| {
6221        if let Expression::Paren(p) = &e {
6222            if is_like_all_marker(p) {
6223                let Expression::Paren(p) = e else {
6224                    unreachable!()
6225                };
6226                return Ok(p.this);
6227            }
6228        }
6229        Ok(e)
6230    })
6231}
6232
6233/// Ensures all unaliased column outputs in subqueries and CTEs get self-aliases.
6234///
6235/// This is needed for TSQL which requires derived table outputs to be aliased.
6236/// For example: `SELECT c FROM t` inside a subquery becomes `SELECT c AS c FROM t`.
6237///
6238/// Mirrors Python sqlglot's `qualify_derived_table_outputs` function which is applied
6239/// as a TRANSFORMS preprocessor for Subquery and CTE expressions in the TSQL dialect.
6240pub fn qualify_derived_table_outputs(expr: Expression) -> Result<Expression> {
6241    use crate::expressions::Alias;
6242
6243    fn add_self_aliases_to_select(select: &mut Select) {
6244        let new_expressions: Vec<Expression> = select
6245            .expressions
6246            .iter()
6247            .map(|e| {
6248                match e {
6249                    // Column reference without alias -> add self-alias
6250                    Expression::Column(col) => {
6251                        let alias_name = col.name.clone();
6252                        Expression::Alias(Box::new(Alias {
6253                            this: e.clone(),
6254                            alias: alias_name,
6255                            column_aliases: Vec::new(),
6256                            alias_explicit_as: false,
6257                            alias_keyword: None,
6258                            pre_alias_comments: Vec::new(),
6259                            trailing_comments: Vec::new(),
6260                            inferred_type: None,
6261                        }))
6262                    }
6263                    // Already aliased or star or other -> keep as is
6264                    _ => e.clone(),
6265                }
6266            })
6267            .collect();
6268        select.expressions = new_expressions;
6269    }
6270
6271    fn walk_and_qualify(expr: &mut Expression) {
6272        match expr {
6273            Expression::Select(ref mut select) => {
6274                // Qualify subqueries in FROM
6275                if let Some(ref mut from) = select.from {
6276                    for e in from.expressions.iter_mut() {
6277                        qualify_subquery_expr(e);
6278                        walk_and_qualify(e);
6279                    }
6280                }
6281                // Qualify subqueries in JOINs
6282                for join in select.joins.iter_mut() {
6283                    qualify_subquery_expr(&mut join.this);
6284                    walk_and_qualify(&mut join.this);
6285                }
6286                // Recurse into expressions (for correlated subqueries etc.)
6287                for e in select.expressions.iter_mut() {
6288                    walk_and_qualify(e);
6289                }
6290                // Recurse into WHERE
6291                if let Some(ref mut w) = select.where_clause {
6292                    walk_and_qualify(&mut w.this);
6293                }
6294            }
6295            Expression::Subquery(ref mut subquery) => {
6296                walk_and_qualify(&mut subquery.this);
6297            }
6298            Expression::Union(ref mut u) => {
6299                walk_and_qualify(&mut u.left);
6300                walk_and_qualify(&mut u.right);
6301            }
6302            Expression::Intersect(ref mut i) => {
6303                walk_and_qualify(&mut i.left);
6304                walk_and_qualify(&mut i.right);
6305            }
6306            Expression::Except(ref mut e) => {
6307                walk_and_qualify(&mut e.left);
6308                walk_and_qualify(&mut e.right);
6309            }
6310            Expression::Cte(ref mut cte) => {
6311                walk_and_qualify(&mut cte.this);
6312            }
6313            _ => {}
6314        }
6315    }
6316
6317    fn qualify_subquery_expr(expr: &mut Expression) {
6318        match expr {
6319            Expression::Subquery(ref mut subquery) => {
6320                // Only qualify if the subquery has a table alias but no column aliases
6321                if subquery.alias.is_some() && subquery.column_aliases.is_empty() {
6322                    if let Expression::Select(ref mut inner_select) = subquery.this {
6323                        // Check the inner select doesn't use *
6324                        let has_star = inner_select
6325                            .expressions
6326                            .iter()
6327                            .any(|e| matches!(e, Expression::Star(_)));
6328                        if !has_star {
6329                            add_self_aliases_to_select(inner_select);
6330                        }
6331                    }
6332                }
6333                // Recurse into the subquery's inner query
6334                walk_and_qualify(&mut subquery.this);
6335            }
6336            Expression::Alias(ref mut alias) => {
6337                qualify_subquery_expr(&mut alias.this);
6338            }
6339            _ => {}
6340        }
6341    }
6342
6343    let mut result = expr;
6344    walk_and_qualify(&mut result);
6345
6346    // Also qualify CTE inner queries at the top level
6347    if let Expression::Select(ref mut select) = result {
6348        if let Some(ref mut with) = select.with {
6349            for cte in with.ctes.iter_mut() {
6350                // CTE with column names -> no need to qualify
6351                if cte.columns.is_empty() {
6352                    // Walk into the CTE's inner query for nested subqueries
6353                    walk_and_qualify(&mut cte.this);
6354                }
6355            }
6356        }
6357    }
6358
6359    Ok(result)
6360}
6361
6362#[cfg(test)]
6363mod tests {
6364    use super::*;
6365    use crate::dialects::{Dialect, DialectType};
6366    use crate::expressions::Column;
6367
6368    fn gen(expr: &Expression) -> String {
6369        let dialect = Dialect::get(DialectType::Generic);
6370        dialect.generate(expr).unwrap()
6371    }
6372
6373    #[test]
6374    fn test_preprocess() {
6375        let expr = Expression::Boolean(BooleanLiteral { value: true });
6376        let result = preprocess(expr, &[replace_bool_with_int]).unwrap();
6377        assert!(
6378            matches!(result, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)))
6379        );
6380    }
6381
6382    #[test]
6383    fn test_preprocess_chain() {
6384        // Test chaining multiple transforms using function pointers
6385        let expr = Expression::Boolean(BooleanLiteral { value: true });
6386        // Create array of function pointers (all same type)
6387        let transforms: Vec<fn(Expression) -> Result<Expression>> =
6388            vec![replace_bool_with_int, replace_int_with_bool];
6389        let result = preprocess(expr, &transforms).unwrap();
6390        // After replace_bool_with_int: 1
6391        // After replace_int_with_bool: true
6392        if let Expression::Boolean(b) = result {
6393            assert!(b.value);
6394        } else {
6395            panic!("Expected boolean literal");
6396        }
6397    }
6398
6399    #[test]
6400    fn test_unnest_to_explode() {
6401        let unnest = Expression::Unnest(Box::new(UnnestFunc {
6402            this: Expression::boxed_column(Column {
6403                name: Identifier::new("arr".to_string()),
6404                table: None,
6405                join_mark: false,
6406                trailing_comments: vec![],
6407                span: None,
6408                inferred_type: None,
6409            }),
6410            expressions: Vec::new(),
6411            with_ordinality: false,
6412            alias: None,
6413            offset_alias: None,
6414            inferred_type: None,
6415        }));
6416
6417        let result = unnest_to_explode(unnest).unwrap();
6418        assert!(matches!(result, Expression::Explode(_)));
6419    }
6420
6421    #[test]
6422    fn test_explode_to_unnest() {
6423        let explode = Expression::Explode(Box::new(UnaryFunc {
6424            this: Expression::boxed_column(Column {
6425                name: Identifier::new("arr".to_string()),
6426                table: None,
6427                join_mark: false,
6428                trailing_comments: vec![],
6429                span: None,
6430                inferred_type: None,
6431            }),
6432            original_name: None,
6433            inferred_type: None,
6434        }));
6435
6436        let result = explode_to_unnest(explode).unwrap();
6437        assert!(matches!(result, Expression::Unnest(_)));
6438    }
6439
6440    #[test]
6441    fn test_replace_bool_with_int() {
6442        let true_expr = Expression::Boolean(BooleanLiteral { value: true });
6443        let result = replace_bool_with_int(true_expr).unwrap();
6444        if let Expression::Literal(lit) = result {
6445            if let Literal::Number(n) = lit.as_ref() {
6446                assert_eq!(n, "1");
6447            }
6448        } else {
6449            panic!("Expected number literal");
6450        }
6451
6452        let false_expr = Expression::Boolean(BooleanLiteral { value: false });
6453        let result = replace_bool_with_int(false_expr).unwrap();
6454        if let Expression::Literal(lit) = result {
6455            if let Literal::Number(n) = lit.as_ref() {
6456                assert_eq!(n, "0");
6457            }
6458        } else {
6459            panic!("Expected number literal");
6460        }
6461    }
6462
6463    #[test]
6464    fn test_replace_int_with_bool() {
6465        let one_expr = Expression::Literal(Box::new(Literal::Number("1".to_string())));
6466        let result = replace_int_with_bool(one_expr).unwrap();
6467        if let Expression::Boolean(b) = result {
6468            assert!(b.value);
6469        } else {
6470            panic!("Expected boolean true");
6471        }
6472
6473        let zero_expr = Expression::Literal(Box::new(Literal::Number("0".to_string())));
6474        let result = replace_int_with_bool(zero_expr).unwrap();
6475        if let Expression::Boolean(b) = result {
6476            assert!(!b.value);
6477        } else {
6478            panic!("Expected boolean false");
6479        }
6480
6481        // Test that other numbers are not converted
6482        let two_expr = Expression::Literal(Box::new(Literal::Number("2".to_string())));
6483        let result = replace_int_with_bool(two_expr).unwrap();
6484        assert!(
6485            matches!(result, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)))
6486        );
6487    }
6488
6489    #[test]
6490    fn test_strip_data_type_params() {
6491        // Test Decimal
6492        let decimal = DataType::Decimal {
6493            precision: Some(10),
6494            scale: Some(2),
6495        };
6496        let stripped = strip_data_type_params(decimal);
6497        assert_eq!(
6498            stripped,
6499            DataType::Decimal {
6500                precision: None,
6501                scale: None
6502            }
6503        );
6504
6505        // Test VarChar
6506        let varchar = DataType::VarChar {
6507            length: Some(255),
6508            parenthesized_length: false,
6509        };
6510        let stripped = strip_data_type_params(varchar);
6511        assert_eq!(
6512            stripped,
6513            DataType::VarChar {
6514                length: None,
6515                parenthesized_length: false
6516            }
6517        );
6518
6519        // Test Char
6520        let char_type = DataType::Char { length: Some(10) };
6521        let stripped = strip_data_type_params(char_type);
6522        assert_eq!(stripped, DataType::Char { length: None });
6523
6524        // Test Timestamp (preserve timezone)
6525        let timestamp = DataType::Timestamp {
6526            precision: Some(6),
6527            timezone: true,
6528        };
6529        let stripped = strip_data_type_params(timestamp);
6530        assert_eq!(
6531            stripped,
6532            DataType::Timestamp {
6533                precision: None,
6534                timezone: true
6535            }
6536        );
6537
6538        // Test Array (recursive)
6539        let array = DataType::Array {
6540            element_type: Box::new(DataType::VarChar {
6541                length: Some(100),
6542                parenthesized_length: false,
6543            }),
6544            dimension: None,
6545        };
6546        let stripped = strip_data_type_params(array);
6547        assert_eq!(
6548            stripped,
6549            DataType::Array {
6550                element_type: Box::new(DataType::VarChar {
6551                    length: None,
6552                    parenthesized_length: false
6553                }),
6554                dimension: None,
6555            }
6556        );
6557
6558        // Test types without params are unchanged
6559        let text = DataType::Text;
6560        let stripped = strip_data_type_params(text);
6561        assert_eq!(stripped, DataType::Text);
6562    }
6563
6564    #[test]
6565    fn test_remove_precision_parameterized_types_cast() {
6566        // Create a CAST(1 AS DECIMAL(10, 2)) expression
6567        let cast_expr = Expression::Cast(Box::new(Cast {
6568            this: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
6569            to: DataType::Decimal {
6570                precision: Some(10),
6571                scale: Some(2),
6572            },
6573            trailing_comments: vec![],
6574            double_colon_syntax: false,
6575            format: None,
6576            default: None,
6577            inferred_type: None,
6578        }));
6579
6580        let result = remove_precision_parameterized_types(cast_expr).unwrap();
6581        if let Expression::Cast(cast) = result {
6582            assert_eq!(
6583                cast.to,
6584                DataType::Decimal {
6585                    precision: None,
6586                    scale: None
6587                }
6588            );
6589        } else {
6590            panic!("Expected Cast expression");
6591        }
6592    }
6593
6594    #[test]
6595    fn test_remove_precision_parameterized_types_varchar() {
6596        // Create a CAST('hello' AS VARCHAR(10)) expression
6597        let cast_expr = Expression::Cast(Box::new(Cast {
6598            this: Expression::Literal(Box::new(Literal::String("hello".to_string()))),
6599            to: DataType::VarChar {
6600                length: Some(10),
6601                parenthesized_length: false,
6602            },
6603            trailing_comments: vec![],
6604            double_colon_syntax: false,
6605            format: None,
6606            default: None,
6607            inferred_type: None,
6608        }));
6609
6610        let result = remove_precision_parameterized_types(cast_expr).unwrap();
6611        if let Expression::Cast(cast) = result {
6612            assert_eq!(
6613                cast.to,
6614                DataType::VarChar {
6615                    length: None,
6616                    parenthesized_length: false
6617                }
6618            );
6619        } else {
6620            panic!("Expected Cast expression");
6621        }
6622    }
6623
6624    #[test]
6625    fn test_remove_precision_direct_cast() {
6626        // Test transform on a direct Cast expression (not nested in Select)
6627        // The current implementation handles top-level Cast expressions;
6628        // a full implementation would need recursive AST traversal
6629        let cast = Expression::Cast(Box::new(Cast {
6630            this: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
6631            to: DataType::Decimal {
6632                precision: Some(10),
6633                scale: Some(2),
6634            },
6635            trailing_comments: vec![],
6636            double_colon_syntax: false,
6637            format: None,
6638            default: None,
6639            inferred_type: None,
6640        }));
6641
6642        let transformed = remove_precision_parameterized_types(cast).unwrap();
6643        let generated = gen(&transformed);
6644
6645        // Should now be DECIMAL without precision
6646        assert!(generated.contains("DECIMAL"));
6647        assert!(!generated.contains("(10"));
6648    }
6649
6650    #[test]
6651    fn test_epoch_cast_to_ts() {
6652        // Test CAST('epoch' AS TIMESTAMP) → CAST('1970-01-01 00:00:00' AS TIMESTAMP)
6653        let cast_expr = Expression::Cast(Box::new(Cast {
6654            this: Expression::Literal(Box::new(Literal::String("epoch".to_string()))),
6655            to: DataType::Timestamp {
6656                precision: None,
6657                timezone: false,
6658            },
6659            trailing_comments: vec![],
6660            double_colon_syntax: false,
6661            format: None,
6662            default: None,
6663            inferred_type: None,
6664        }));
6665
6666        let result = epoch_cast_to_ts(cast_expr).unwrap();
6667        if let Expression::Cast(cast) = result {
6668            if let Expression::Literal(lit) = cast.this {
6669                if let Literal::String(s) = lit.as_ref() {
6670                    assert_eq!(s, "1970-01-01 00:00:00");
6671                }
6672            } else {
6673                panic!("Expected string literal");
6674            }
6675        } else {
6676            panic!("Expected Cast expression");
6677        }
6678    }
6679
6680    #[test]
6681    fn test_epoch_cast_to_ts_preserves_non_epoch() {
6682        // Test that non-epoch strings are preserved
6683        let cast_expr = Expression::Cast(Box::new(Cast {
6684            this: Expression::Literal(Box::new(Literal::String("2024-01-15".to_string()))),
6685            to: DataType::Timestamp {
6686                precision: None,
6687                timezone: false,
6688            },
6689            trailing_comments: vec![],
6690            double_colon_syntax: false,
6691            format: None,
6692            default: None,
6693            inferred_type: None,
6694        }));
6695
6696        let result = epoch_cast_to_ts(cast_expr).unwrap();
6697        if let Expression::Cast(cast) = result {
6698            if let Expression::Literal(lit) = cast.this {
6699                if let Literal::String(s) = lit.as_ref() {
6700                    assert_eq!(s, "2024-01-15");
6701                }
6702            } else {
6703                panic!("Expected string literal");
6704            }
6705        } else {
6706            panic!("Expected Cast expression");
6707        }
6708    }
6709
6710    #[test]
6711    fn test_unqualify_columns() {
6712        // Test that table qualifiers are removed
6713        let col = Expression::boxed_column(Column {
6714            name: Identifier::new("id".to_string()),
6715            table: Some(Identifier::new("users".to_string())),
6716            join_mark: false,
6717            trailing_comments: vec![],
6718            span: None,
6719            inferred_type: None,
6720        });
6721
6722        let result = unqualify_columns(col).unwrap();
6723        if let Expression::Column(c) = result {
6724            assert!(c.table.is_none());
6725            assert_eq!(c.name.name, "id");
6726        } else {
6727            panic!("Expected Column expression");
6728        }
6729    }
6730
6731    #[test]
6732    fn test_is_temporal_type() {
6733        assert!(is_temporal_type(&DataType::Date));
6734        assert!(is_temporal_type(&DataType::Timestamp {
6735            precision: None,
6736            timezone: false
6737        }));
6738        assert!(is_temporal_type(&DataType::Time {
6739            precision: None,
6740            timezone: false
6741        }));
6742        assert!(!is_temporal_type(&DataType::Int {
6743            length: None,
6744            integer_spelling: false
6745        }));
6746        assert!(!is_temporal_type(&DataType::VarChar {
6747            length: None,
6748            parenthesized_length: false
6749        }));
6750    }
6751
6752    #[test]
6753    fn test_eliminate_semi_join_basic() {
6754        use crate::expressions::{Join, TableRef};
6755
6756        // Test that semi joins are converted to EXISTS
6757        let select = Expression::Select(Box::new(Select {
6758            vertica: None,
6759            expressions: vec![Expression::boxed_column(Column {
6760                name: Identifier::new("a".to_string()),
6761                table: None,
6762                join_mark: false,
6763                trailing_comments: vec![],
6764                span: None,
6765                inferred_type: None,
6766            })],
6767            from: Some(From {
6768                expressions: vec![Expression::Table(Box::new(TableRef::new("t1")))],
6769            }),
6770            joins: vec![Join {
6771                this: Expression::Table(Box::new(TableRef::new("t2"))),
6772                kind: JoinKind::Semi,
6773                on: Some(Expression::Eq(Box::new(BinaryOp {
6774                    left: Expression::boxed_column(Column {
6775                        name: Identifier::new("x".to_string()),
6776                        table: None,
6777                        join_mark: false,
6778                        trailing_comments: vec![],
6779                        span: None,
6780                        inferred_type: None,
6781                    }),
6782                    right: Expression::boxed_column(Column {
6783                        name: Identifier::new("y".to_string()),
6784                        table: None,
6785                        join_mark: false,
6786                        trailing_comments: vec![],
6787                        span: None,
6788                        inferred_type: None,
6789                    }),
6790                    left_comments: vec![],
6791                    operator_comments: vec![],
6792                    trailing_comments: vec![],
6793                    inferred_type: None,
6794                }))),
6795                using: vec![],
6796                use_inner_keyword: false,
6797                use_outer_keyword: false,
6798                deferred_condition: false,
6799                join_hint: None,
6800                match_condition: None,
6801                pivots: Vec::new(),
6802                comments: Vec::new(),
6803                nesting_group: 0,
6804                directed: false,
6805            }],
6806            ..Select::new()
6807        }));
6808
6809        let result = eliminate_semi_and_anti_joins(select).unwrap();
6810        if let Expression::Select(s) = result {
6811            // Semi join should be removed
6812            assert!(s.joins.is_empty());
6813            // WHERE clause should have EXISTS
6814            assert!(s.where_clause.is_some());
6815        } else {
6816            panic!("Expected Select expression");
6817        }
6818    }
6819
6820    #[test]
6821    fn test_no_ilike_sql() {
6822        use crate::expressions::LikeOp;
6823
6824        // Test ILIKE conversion to LOWER+LIKE
6825        let ilike_expr = Expression::ILike(Box::new(LikeOp {
6826            left: Expression::boxed_column(Column {
6827                name: Identifier::new("name".to_string()),
6828                table: None,
6829                join_mark: false,
6830                trailing_comments: vec![],
6831                span: None,
6832                inferred_type: None,
6833            }),
6834            right: Expression::Literal(Box::new(Literal::String("%test%".to_string()))),
6835            escape: None,
6836            quantifier: None,
6837            inferred_type: None,
6838        }));
6839
6840        let result = no_ilike_sql(ilike_expr).unwrap();
6841        if let Expression::Like(like) = result {
6842            // Left should be LOWER(name)
6843            if let Expression::Function(f) = &like.left {
6844                assert_eq!(f.name, "LOWER");
6845            } else {
6846                panic!("Expected LOWER function on left");
6847            }
6848            // Right should be LOWER('%test%')
6849            if let Expression::Function(f) = &like.right {
6850                assert_eq!(f.name, "LOWER");
6851            } else {
6852                panic!("Expected LOWER function on right");
6853            }
6854        } else {
6855            panic!("Expected Like expression");
6856        }
6857    }
6858
6859    #[test]
6860    fn test_no_trycast_sql() {
6861        // Test TryCast conversion to Cast
6862        let trycast_expr = Expression::TryCast(Box::new(Cast {
6863            this: Expression::Literal(Box::new(Literal::String("123".to_string()))),
6864            to: DataType::Int {
6865                length: None,
6866                integer_spelling: false,
6867            },
6868            trailing_comments: vec![],
6869            double_colon_syntax: false,
6870            format: None,
6871            default: None,
6872            inferred_type: None,
6873        }));
6874
6875        let result = no_trycast_sql(trycast_expr).unwrap();
6876        assert!(matches!(result, Expression::Cast(_)));
6877    }
6878
6879    #[test]
6880    fn test_no_safe_cast_sql() {
6881        // Test SafeCast conversion to Cast
6882        let safe_cast_expr = Expression::SafeCast(Box::new(Cast {
6883            this: Expression::Literal(Box::new(Literal::String("123".to_string()))),
6884            to: DataType::Int {
6885                length: None,
6886                integer_spelling: false,
6887            },
6888            trailing_comments: vec![],
6889            double_colon_syntax: false,
6890            format: None,
6891            default: None,
6892            inferred_type: None,
6893        }));
6894
6895        let result = no_safe_cast_sql(safe_cast_expr).unwrap();
6896        assert!(matches!(result, Expression::Cast(_)));
6897    }
6898
6899    #[test]
6900    fn test_explode_to_unnest_presto() {
6901        let spark = Dialect::get(DialectType::Spark);
6902        let result = spark
6903            .transpile("SELECT EXPLODE(x) FROM tbl", DialectType::Presto)
6904            .unwrap();
6905        assert_eq!(
6906            result[0],
6907            "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))"
6908        );
6909    }
6910
6911    #[test]
6912    fn test_explode_to_unnest_bigquery() {
6913        let spark = Dialect::get(DialectType::Spark);
6914        let result = spark
6915            .transpile("SELECT EXPLODE(x) FROM tbl", DialectType::BigQuery)
6916            .unwrap();
6917        assert_eq!(
6918            result[0],
6919            "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))"
6920        );
6921    }
6922}