Skip to main content

polyglot_sql/dialects/
duckdb.rs

1//! DuckDB Dialect
2//!
3//! DuckDB-specific transformations based on sqlglot patterns.
4//! Key features:
5//! - Modern SQL analytics database with PostgreSQL-like syntax
6//! - LIST type for arrays
7//! - STRUCT support with dot access
8//! - EPOCH_MS / EPOCH for timestamps
9//! - EXCLUDE / REPLACE in SELECT
10//! - Rich array/list functions
11
12use super::{DialectImpl, DialectType};
13use crate::error::Result;
14use crate::expressions::{
15    AggFunc, Alias, BinaryOp, Case, Cast, CeilFunc, Column, DataType, Expression, Function,
16    Identifier, Interval, IntervalUnit, IntervalUnitSpec, IsNull, JSONPath, JSONPathKey,
17    JSONPathRoot, JSONPathSubscript, JsonExtractFunc, Literal, Null, Paren, Struct, Subquery,
18    SubstringFunc, UnaryFunc, UnaryOp, Unhex, VarArgFunc, WindowFunction,
19};
20#[cfg(feature = "generate")]
21use crate::generator::GeneratorConfig;
22use crate::tokens::TokenizerConfig;
23
24/// Normalize a JSON path for DuckDB arrow syntax.
25/// Converts string keys like 'foo' to '$.foo' and numeric indexes like 0 to '$[0]'.
26/// This matches Python sqlglot's to_json_path() behavior.
27fn normalize_json_path(path: Expression) -> Expression {
28    match &path {
29        // String literal: 'foo' -> JSONPath with $.foo
30        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
31            let Literal::String(s) = lit.as_ref() else {
32                unreachable!()
33            };
34            // Skip paths that are already normalized (start with $ or /)
35            // Also skip JSON pointer syntax and back-of-list syntax [#-i]
36            if s.starts_with('$') || s.starts_with('/') || s.contains("[#") {
37                return path;
38            }
39            // Create JSONPath expression: $.key
40            Expression::JSONPath(Box::new(JSONPath {
41                expressions: vec![
42                    Expression::JSONPathRoot(JSONPathRoot),
43                    Expression::JSONPathKey(Box::new(JSONPathKey {
44                        this: Box::new(Expression::Literal(Box::new(Literal::String(s.clone())))),
45                    })),
46                ],
47                escape: None,
48            }))
49        }
50        // Number literal: 0 -> JSONPath with $[0]
51        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
52            let Literal::Number(n) = lit.as_ref() else {
53                unreachable!()
54            };
55            // Create JSONPath expression: $[n]
56            Expression::JSONPath(Box::new(JSONPath {
57                expressions: vec![
58                    Expression::JSONPathRoot(JSONPathRoot),
59                    Expression::JSONPathSubscript(Box::new(JSONPathSubscript {
60                        this: Box::new(Expression::Literal(Box::new(Literal::Number(n.clone())))),
61                    })),
62                ],
63                escape: None,
64            }))
65        }
66        // Already a JSONPath or other expression - return as is
67        _ => path,
68    }
69}
70
71/// Helper to wrap JSON arrow expressions in parentheses when they appear
72/// in contexts that require it (Binary, In, Not expressions)
73/// This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior
74fn wrap_if_json_arrow(expr: Expression) -> Expression {
75    match &expr {
76        Expression::JsonExtract(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
77            this: expr,
78            trailing_comments: Vec::new(),
79        })),
80        Expression::JsonExtractScalar(f) if f.arrow_syntax => Expression::Paren(Box::new(Paren {
81            this: expr,
82            trailing_comments: Vec::new(),
83        })),
84        _ => expr,
85    }
86}
87
88/// DuckDB dialect
89pub struct DuckDBDialect;
90
91impl DialectImpl for DuckDBDialect {
92    fn dialect_type(&self) -> DialectType {
93        DialectType::DuckDB
94    }
95
96    fn tokenizer_config(&self) -> TokenizerConfig {
97        let mut config = TokenizerConfig::default();
98        // DuckDB uses double quotes for identifiers
99        config.identifiers.insert('"', '"');
100        // A leading escaped double quote belongs to the identifier, not a triple-quoted string.
101        config.quotes.remove("\"\"\"");
102        // DuckDB supports nested comments
103        config.nested_comments = true;
104        // DuckDB allows underscores as digit separators in numeric literals
105        config.numbers_can_be_underscore_separated = true;
106        config
107    }
108
109    #[cfg(feature = "generate")]
110
111    fn generator_config(&self) -> GeneratorConfig {
112        use crate::generator::IdentifierQuoteStyle;
113        GeneratorConfig {
114            identifier_quote: '"',
115            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
116            dialect: Some(DialectType::DuckDB),
117            // DuckDB-specific settings from Python sqlglot
118            parameter_token: "$",
119            named_placeholder_token: "$",
120            join_hints: false,
121            table_hints: false,
122            query_hints: false,
123            limit_fetch_style: crate::generator::LimitFetchStyle::Limit,
124            struct_delimiter: ("(", ")"),
125            rename_table_with_db: false,
126            nvl2_supported: false,
127            semi_anti_join_with_side: false,
128            tablesample_keywords: "TABLESAMPLE",
129            tablesample_seed_keyword: "REPEATABLE",
130            last_day_supports_date_part: false,
131            json_key_value_pair_sep: ",",
132            ignore_nulls_in_func: true,
133            json_path_bracketed_key_supported: false,
134            supports_create_table_like: false,
135            multi_arg_distinct: false,
136            quantified_no_paren_space: false,
137            can_implement_array_any: true,
138            supports_to_number: false,
139            supports_window_exclude: true,
140            copy_has_into_keyword: false,
141            star_except: "EXCLUDE",
142            pad_fill_pattern_is_required: true,
143            array_concat_is_var_len: false,
144            array_size_dim_required: None,
145            normalize_extract_date_parts: true,
146            supports_like_quantifiers: false,
147            // DuckDB supports TRY_CAST
148            try_supported: true,
149            // DuckDB uses curly brace notation for struct literals: {'a': 1}
150            struct_curly_brace_notation: true,
151            // DuckDB uses bracket-only notation for arrays: [1, 2, 3]
152            array_bracket_only: true,
153            ..Default::default()
154        }
155    }
156
157    #[cfg(feature = "transpile")]
158
159    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
160        match expr {
161            // ===== Data Type Mappings =====
162            Expression::DataType(dt) => self.transform_data_type(dt),
163
164            // ===== Operator transformations =====
165            // BitwiseXor -> XOR() function in DuckDB
166            Expression::BitwiseXor(op) => Ok(Expression::Function(Box::new(
167                crate::expressions::Function::new("XOR", vec![op.left, op.right]),
168            ))),
169
170            // ===== Array/List syntax =====
171            // ARRAY[1, 2, 3] -> [1, 2, 3] in DuckDB (bracket notation preferred)
172            Expression::ArrayFunc(mut f) => {
173                f.bracket_notation = true;
174                Ok(Expression::ArrayFunc(f))
175            }
176
177            // IFNULL -> COALESCE in DuckDB
178            Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
179                original_name: None,
180                expressions: vec![f.this, f.expression],
181                inferred_type: None,
182            }))),
183
184            // NVL -> COALESCE in DuckDB
185            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
186                original_name: None,
187                expressions: vec![f.this, f.expression],
188                inferred_type: None,
189            }))),
190
191            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
192            Expression::Coalesce(mut f) => {
193                f.original_name = None;
194                Ok(Expression::Coalesce(f))
195            }
196
197            // GROUP_CONCAT -> LISTAGG in DuckDB
198            Expression::GroupConcat(f) => Ok(Expression::ListAgg(Box::new(
199                crate::expressions::ListAggFunc {
200                    source_dialect: None,
201                    this: f.this,
202                    separator: f.separator,
203                    on_overflow: None,
204                    max_length: None,
205                    order_by: f.order_by,
206                    distinct: f.distinct,
207                    filter: f.filter,
208                    inferred_type: None,
209                },
210            ))),
211
212            // LISTAGG is native in DuckDB - keep as-is
213            Expression::ListAgg(f) => Ok(Expression::ListAgg(f)),
214
215            // STRING_AGG -> LISTAGG in DuckDB (normalize to LISTAGG)
216            Expression::StringAgg(f) => Ok(Expression::ListAgg(Box::new(
217                crate::expressions::ListAggFunc {
218                    source_dialect: None,
219                    this: f.this,
220                    separator: f.separator,
221                    on_overflow: None,
222                    max_length: None,
223                    order_by: f.order_by,
224                    distinct: f.distinct,
225                    filter: f.filter,
226                    inferred_type: None,
227                },
228            ))),
229
230            // TryCast -> TRY_CAST (DuckDB supports TRY_CAST)
231            Expression::TryCast(c) => Ok(Expression::TryCast(c)),
232
233            // SafeCast -> TRY_CAST in DuckDB
234            Expression::SafeCast(c) => Ok(Expression::TryCast(c)),
235
236            // ILIKE is native to DuckDB (PostgreSQL-compatible)
237            Expression::ILike(op) => Ok(Expression::ILike(op)),
238
239            // EXPLODE -> UNNEST in DuckDB
240            Expression::Explode(f) => Ok(Expression::Unnest(Box::new(
241                crate::expressions::UnnestFunc {
242                    this: f.this,
243                    expressions: Vec::new(),
244                    with_ordinality: false,
245                    alias: None,
246                    offset_alias: None,
247                    inferred_type: None,
248                },
249            ))),
250
251            // UNNEST is native to DuckDB
252            Expression::Unnest(f) => Ok(Expression::Unnest(f)),
253
254            // DATE_ADD -> date + INTERVAL in DuckDB
255            Expression::DateAdd(f) => {
256                // Reconstruct INTERVAL expression from value and unit
257                let interval_expr = if matches!(&f.interval, Expression::Interval(_)) {
258                    f.interval
259                } else {
260                    Expression::Interval(Box::new(Interval {
261                        this: Some(f.interval),
262                        unit: Some(IntervalUnitSpec::Simple {
263                            unit: f.unit,
264                            use_plural: false,
265                        }),
266                    }))
267                };
268                Ok(Expression::Add(Box::new(BinaryOp {
269                    left: f.this,
270                    right: interval_expr,
271                    left_comments: Vec::new(),
272                    operator_comments: Vec::new(),
273                    trailing_comments: Vec::new(),
274                    inferred_type: None,
275                })))
276            }
277
278            // DATE_SUB -> date - INTERVAL in DuckDB
279            Expression::DateSub(f) => {
280                // Reconstruct INTERVAL expression from value and unit
281                let interval_expr = if matches!(&f.interval, Expression::Interval(_)) {
282                    f.interval
283                } else {
284                    Expression::Interval(Box::new(Interval {
285                        this: Some(f.interval),
286                        unit: Some(IntervalUnitSpec::Simple {
287                            unit: f.unit,
288                            use_plural: false,
289                        }),
290                    }))
291                };
292                Ok(Expression::Sub(Box::new(BinaryOp {
293                    left: f.this,
294                    right: interval_expr,
295                    left_comments: Vec::new(),
296                    operator_comments: Vec::new(),
297                    trailing_comments: Vec::new(),
298                    inferred_type: None,
299                })))
300            }
301
302            // GenerateSeries with 1 arg -> GENERATE_SERIES(0, n)
303            Expression::GenerateSeries(mut f) => {
304                // If only end is set (no start), add 0 as start
305                if f.start.is_none() && f.end.is_some() {
306                    f.start = Some(Box::new(Expression::number(0)));
307                }
308                Ok(Expression::GenerateSeries(f))
309            }
310
311            // ===== Array/List functions =====
312            // ArrayAppend -> LIST_APPEND
313            Expression::ArrayAppend(f) => Ok(Expression::Function(Box::new(Function::new(
314                "LIST_APPEND".to_string(),
315                vec![f.this, f.expression],
316            )))),
317
318            // ArrayPrepend -> LIST_PREPEND(element, array) - note arg swap
319            Expression::ArrayPrepend(f) => Ok(Expression::Function(Box::new(Function::new(
320                "LIST_PREPEND".to_string(),
321                vec![f.expression, f.this],
322            )))),
323
324            // ArrayUniqueAgg -> LIST(DISTINCT col) FILTER(WHERE NOT col IS NULL)
325            Expression::ArrayUniqueAgg(f) => {
326                let col = f.this;
327                // NOT col IS NULL
328                let filter_expr = Expression::Not(Box::new(UnaryOp {
329                    this: Expression::IsNull(Box::new(IsNull {
330                        this: col.clone(),
331                        not: false,
332                        postfix_form: false,
333                    })),
334                    inferred_type: None,
335                }));
336                Ok(Expression::ArrayAgg(Box::new(AggFunc {
337                    this: col,
338                    distinct: true,
339                    filter: Some(filter_expr),
340                    order_by: Vec::new(),
341                    name: Some("LIST".to_string()),
342                    ignore_nulls: None,
343                    having_max: None,
344                    limit: None,
345                    inferred_type: None,
346                })))
347            }
348
349            // Split -> STR_SPLIT
350            Expression::Split(f) => Ok(Expression::Function(Box::new(Function::new(
351                "STR_SPLIT".to_string(),
352                vec![f.this, f.delimiter],
353            )))),
354
355            // RANDOM is native to DuckDB
356            Expression::Random(_) => Ok(Expression::Random(crate::expressions::Random)),
357
358            // Rand with seed -> keep as Rand so NORMAL/UNIFORM handlers can extract the seed
359            // Rand without seed -> Random
360            Expression::Rand(r) => {
361                if r.seed.is_some() {
362                    Ok(Expression::Rand(r))
363                } else {
364                    Ok(Expression::Random(crate::expressions::Random))
365                }
366            }
367
368            // ===== Boolean aggregates =====
369            // LogicalAnd -> BOOL_AND with CAST to BOOLEAN
370            Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
371                "BOOL_AND".to_string(),
372                vec![Expression::Cast(Box::new(crate::expressions::Cast {
373                    this: f.this,
374                    to: crate::expressions::DataType::Boolean,
375                    trailing_comments: Vec::new(),
376                    double_colon_syntax: false,
377                    format: None,
378                    default: None,
379                    inferred_type: None,
380                }))],
381            )))),
382
383            // LogicalOr -> BOOL_OR with CAST to BOOLEAN
384            Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
385                "BOOL_OR".to_string(),
386                vec![Expression::Cast(Box::new(crate::expressions::Cast {
387                    this: f.this,
388                    to: crate::expressions::DataType::Boolean,
389                    trailing_comments: Vec::new(),
390                    double_colon_syntax: false,
391                    format: None,
392                    default: None,
393                    inferred_type: None,
394                }))],
395            )))),
396
397            // ===== Approximate functions =====
398            // ApproxDistinct -> APPROX_COUNT_DISTINCT
399            Expression::ApproxDistinct(f) => Ok(Expression::Function(Box::new(Function::new(
400                "APPROX_COUNT_DISTINCT".to_string(),
401                vec![f.this],
402            )))),
403
404            // ===== Variance =====
405            // VarPop -> VAR_POP
406            Expression::VarPop(f) => Ok(Expression::Function(Box::new(Function::new(
407                "VAR_POP".to_string(),
408                vec![f.this],
409            )))),
410
411            // ===== Date/time functions =====
412            // DayOfMonth -> DAYOFMONTH
413            Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
414                "DAYOFMONTH".to_string(),
415                vec![f.this],
416            )))),
417
418            // DayOfWeek -> DAYOFWEEK
419            Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
420                "DAYOFWEEK".to_string(),
421                vec![f.this],
422            )))),
423
424            // DayOfWeekIso -> ISODOW
425            Expression::DayOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
426                "ISODOW".to_string(),
427                vec![f.this],
428            )))),
429
430            // DayOfYear -> DAYOFYEAR
431            Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
432                "DAYOFYEAR".to_string(),
433                vec![f.this],
434            )))),
435
436            // WeekOfYear -> WEEKOFYEAR
437            Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
438                "WEEKOFYEAR".to_string(),
439                vec![f.this],
440            )))),
441
442            // ===== Time conversion functions =====
443            // TimeStrToUnix -> EPOCH
444            Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
445                "EPOCH".to_string(),
446                vec![f.this],
447            )))),
448
449            // TimeToUnix -> EPOCH
450            Expression::TimeToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
451                "EPOCH".to_string(),
452                vec![f.this],
453            )))),
454
455            // UnixMicros -> EPOCH_US
456            Expression::UnixMicros(f) => Ok(Expression::Function(Box::new(Function::new(
457                "EPOCH_US".to_string(),
458                vec![f.this],
459            )))),
460
461            // UnixMillis -> EPOCH_MS
462            Expression::UnixMillis(f) => Ok(Expression::Function(Box::new(Function::new(
463                "EPOCH_MS".to_string(),
464                vec![f.this],
465            )))),
466
467            // TimestampDiff -> DATE_DIFF
468            Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
469                "DATE_DIFF".to_string(),
470                vec![*f.this, *f.expression],
471            )))),
472
473            // ===== Hash functions =====
474            // SHA -> SHA1
475            Expression::SHA(f) => Ok(Expression::Function(Box::new(Function::new(
476                "SHA1".to_string(),
477                vec![f.this],
478            )))),
479
480            // MD5Digest -> UNHEX(MD5(...))
481            Expression::MD5Digest(f) => Ok(Expression::Function(Box::new(Function::new(
482                "UNHEX".to_string(),
483                vec![*f.this],
484            )))),
485
486            // SHA1Digest -> UNHEX
487            Expression::SHA1Digest(f) => Ok(Expression::Function(Box::new(Function::new(
488                "UNHEX".to_string(),
489                vec![f.this],
490            )))),
491
492            // SHA2Digest -> UNHEX
493            Expression::SHA2Digest(f) => Ok(Expression::Function(Box::new(Function::new(
494                "UNHEX".to_string(),
495                vec![*f.this],
496            )))),
497
498            // ===== Vector/Distance functions =====
499            // CosineDistance -> LIST_COSINE_DISTANCE
500            Expression::CosineDistance(f) => Ok(Expression::Function(Box::new(Function::new(
501                "LIST_COSINE_DISTANCE".to_string(),
502                vec![*f.this, *f.expression],
503            )))),
504
505            // EuclideanDistance -> LIST_DISTANCE
506            Expression::EuclideanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
507                "LIST_DISTANCE".to_string(),
508                vec![*f.this, *f.expression],
509            )))),
510
511            // ===== Numeric checks =====
512            // IsInf -> ISINF
513            Expression::IsInf(f) => Ok(Expression::Function(Box::new(Function::new(
514                "ISINF".to_string(),
515                vec![f.this],
516            )))),
517
518            // IsNan -> ISNAN
519            Expression::IsNan(f) => Ok(Expression::Function(Box::new(Function::new(
520                "ISNAN".to_string(),
521                vec![f.this],
522            )))),
523
524            // ===== Pattern matching =====
525            // RegexpLike (~) -> REGEXP_FULL_MATCH in DuckDB
526            Expression::RegexpLike(f) => Ok(Expression::Function(Box::new(Function::new(
527                "REGEXP_FULL_MATCH".to_string(),
528                vec![f.this, f.pattern],
529            )))),
530
531            // ===== Time functions =====
532            // CurrentTime -> CURRENT_TIME (no parens in DuckDB)
533            Expression::CurrentTime(_) => Ok(Expression::Function(Box::new(Function {
534                on_error: None,
535                qualified_name: Vec::new(),
536                source_dialect: None,
537
538                name: "CURRENT_TIME".to_string(),
539                args: vec![],
540                distinct: false,
541                trailing_comments: vec![],
542                use_bracket_syntax: false,
543                no_parens: true,
544                quoted: false,
545                span: None,
546                inferred_type: None,
547            }))),
548
549            // ===== Return statement =====
550            // ReturnStmt -> just output the inner expression
551            Expression::ReturnStmt(e) => Ok(*e),
552
553            // ===== DDL Column Constraints =====
554            // CommentColumnConstraint -> ignored (DuckDB doesn't support column comments this way)
555            Expression::CommentColumnConstraint(_) => Ok(Expression::Literal(Box::new(
556                crate::expressions::Literal::String(String::new()),
557            ))),
558
559            // JsonExtract -> use arrow syntax (->) in DuckDB with normalized JSON path
560            Expression::JsonExtract(mut f) => {
561                f.arrow_syntax = true;
562                f.path = normalize_json_path(f.path);
563                Ok(Expression::JsonExtract(f))
564            }
565
566            // JsonExtractScalar -> use arrow syntax (->>) in DuckDB with normalized JSON path
567            Expression::JsonExtractScalar(mut f) => {
568                f.arrow_syntax = true;
569                f.path = normalize_json_path(f.path);
570                Ok(Expression::JsonExtractScalar(f))
571            }
572
573            // CARDINALITY: keep as Expression::Cardinality - cross_dialect_normalize handles
574            // the conversion to target-specific form. For DuckDB->DuckDB, CARDINALITY is preserved
575            // (used for maps), for DuckDB->other it goes through ArrayLengthConvert.
576
577            // ADD_MONTHS(date, n) -> convert to Function and handle in transform_function
578            Expression::AddMonths(f) => {
579                let func = Function::new("ADD_MONTHS".to_string(), vec![f.this, f.expression]);
580                self.transform_function(func)
581            }
582
583            // NEXT_DAY(date, day) -> convert to Function and handle in transform_function
584            Expression::NextDay(f) => {
585                let func = Function::new("NEXT_DAY".to_string(), vec![f.this, f.expression]);
586                self.transform_function(func)
587            }
588
589            // LAST_DAY(date, unit) -> convert to Function and handle in transform_function
590            Expression::LastDay(f) => {
591                if let Some(unit) = f.unit {
592                    let unit_str = match unit {
593                        crate::expressions::DateTimeField::Year => "YEAR",
594                        crate::expressions::DateTimeField::Month => "MONTH",
595                        crate::expressions::DateTimeField::Quarter => "QUARTER",
596                        crate::expressions::DateTimeField::Week => "WEEK",
597                        crate::expressions::DateTimeField::Day => "DAY",
598                        _ => "MONTH",
599                    };
600                    let func = Function::new(
601                        "LAST_DAY".to_string(),
602                        vec![
603                            f.this,
604                            Expression::Identifier(Identifier {
605                                name: unit_str.to_string(),
606                                quoted: false,
607                                trailing_comments: Vec::new(),
608                                span: None,
609                            }),
610                        ],
611                    );
612                    self.transform_function(func)
613                } else {
614                    // Single arg LAST_DAY - pass through
615                    Ok(Expression::Function(Box::new(Function::new(
616                        "LAST_DAY".to_string(),
617                        vec![f.this],
618                    ))))
619                }
620            }
621
622            // DAYNAME(expr) -> STRFTIME(expr, '%a')
623            Expression::Dayname(d) => Ok(Expression::Function(Box::new(Function::new(
624                "STRFTIME".to_string(),
625                vec![
626                    *d.this,
627                    Expression::Literal(Box::new(Literal::String("%a".to_string()))),
628                ],
629            )))),
630
631            // MONTHNAME(expr) -> STRFTIME(expr, '%b')
632            Expression::Monthname(d) => Ok(Expression::Function(Box::new(Function::new(
633                "STRFTIME".to_string(),
634                vec![
635                    *d.this,
636                    Expression::Literal(Box::new(Literal::String("%b".to_string()))),
637                ],
638            )))),
639
640            // FLOOR(x, scale) -> ROUND(FLOOR(x * POWER(10, scale)) / POWER(10, scale), scale)
641            Expression::Floor(f) if f.scale.is_some() => {
642                let x = f.this;
643                let scale = f.scale.unwrap();
644                let needs_cast = match &scale {
645                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
646                        let Literal::Number(n) = lit.as_ref() else {
647                            unreachable!()
648                        };
649                        n.contains('.')
650                    }
651                    _ => false,
652                };
653                let int_scale = if needs_cast {
654                    Expression::Cast(Box::new(Cast {
655                        this: scale.clone(),
656                        to: DataType::Int {
657                            length: None,
658                            integer_spelling: false,
659                        },
660                        trailing_comments: Vec::new(),
661                        double_colon_syntax: false,
662                        format: None,
663                        default: None,
664                        inferred_type: None,
665                    }))
666                } else {
667                    scale.clone()
668                };
669                let power_10 = Expression::Function(Box::new(Function::new(
670                    "POWER".to_string(),
671                    vec![Expression::number(10), int_scale.clone()],
672                )));
673                let x_paren = match &x {
674                    Expression::Add(_)
675                    | Expression::Sub(_)
676                    | Expression::Mul(_)
677                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
678                        this: x,
679                        trailing_comments: Vec::new(),
680                    })),
681                    _ => x,
682                };
683                let multiplied = Expression::Mul(Box::new(BinaryOp {
684                    left: x_paren,
685                    right: power_10.clone(),
686                    left_comments: Vec::new(),
687                    operator_comments: Vec::new(),
688                    trailing_comments: Vec::new(),
689                    inferred_type: None,
690                }));
691                let floored = Expression::Function(Box::new(Function::new(
692                    "FLOOR".to_string(),
693                    vec![multiplied],
694                )));
695                let divided = Expression::Div(Box::new(BinaryOp {
696                    left: floored,
697                    right: power_10,
698                    left_comments: Vec::new(),
699                    operator_comments: Vec::new(),
700                    trailing_comments: Vec::new(),
701                    inferred_type: None,
702                }));
703                Ok(Expression::Function(Box::new(Function::new(
704                    "ROUND".to_string(),
705                    vec![divided, int_scale],
706                ))))
707            }
708
709            // CEIL(x, scale) -> ROUND(CEIL(x * POWER(10, scale)) / POWER(10, scale), scale)
710            Expression::Ceil(f) if f.decimals.is_some() => {
711                let x = f.this;
712                let scale = f.decimals.unwrap();
713                let needs_cast = match &scale {
714                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
715                        let Literal::Number(n) = lit.as_ref() else {
716                            unreachable!()
717                        };
718                        n.contains('.')
719                    }
720                    _ => false,
721                };
722                let int_scale = if needs_cast {
723                    Expression::Cast(Box::new(Cast {
724                        this: scale.clone(),
725                        to: DataType::Int {
726                            length: None,
727                            integer_spelling: false,
728                        },
729                        trailing_comments: Vec::new(),
730                        double_colon_syntax: false,
731                        format: None,
732                        default: None,
733                        inferred_type: None,
734                    }))
735                } else {
736                    scale.clone()
737                };
738                let power_10 = Expression::Function(Box::new(Function::new(
739                    "POWER".to_string(),
740                    vec![Expression::number(10), int_scale.clone()],
741                )));
742                let x_paren = match &x {
743                    Expression::Add(_)
744                    | Expression::Sub(_)
745                    | Expression::Mul(_)
746                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
747                        this: x,
748                        trailing_comments: Vec::new(),
749                    })),
750                    _ => x,
751                };
752                let multiplied = Expression::Mul(Box::new(BinaryOp {
753                    left: x_paren,
754                    right: power_10.clone(),
755                    left_comments: Vec::new(),
756                    operator_comments: Vec::new(),
757                    trailing_comments: Vec::new(),
758                    inferred_type: None,
759                }));
760                let ceiled = Expression::Function(Box::new(Function::new(
761                    "CEIL".to_string(),
762                    vec![multiplied],
763                )));
764                let divided = Expression::Div(Box::new(BinaryOp {
765                    left: ceiled,
766                    right: power_10,
767                    left_comments: Vec::new(),
768                    operator_comments: Vec::new(),
769                    trailing_comments: Vec::new(),
770                    inferred_type: None,
771                }));
772                Ok(Expression::Function(Box::new(Function::new(
773                    "ROUND".to_string(),
774                    vec![divided, int_scale],
775                ))))
776            }
777
778            // ParseJson: handled by generator (outputs JSON() for DuckDB)
779
780            // TABLE(GENERATOR(ROWCOUNT => n)) -> RANGE(n) in DuckDB
781            // The TABLE() wrapper around GENERATOR is parsed as TableArgument
782            Expression::TableArgument(ta) if ta.prefix.to_uppercase() == "TABLE" => {
783                // Check if inner is a GENERATOR or RANGE function
784                match ta.this {
785                    Expression::Function(ref f) if f.name.to_uppercase() == "RANGE" => {
786                        // Already converted to RANGE, unwrap TABLE()
787                        Ok(ta.this)
788                    }
789                    Expression::Function(ref f) if f.name.to_uppercase() == "GENERATOR" => {
790                        // GENERATOR(ROWCOUNT => n) -> RANGE(n)
791                        let mut rowcount = None;
792                        for arg in &f.args {
793                            if let Expression::NamedArgument(na) = arg {
794                                if na.name.name.to_uppercase() == "ROWCOUNT" {
795                                    rowcount = Some(na.value.clone());
796                                }
797                            }
798                        }
799                        if let Some(n) = rowcount {
800                            Ok(Expression::Function(Box::new(Function::new(
801                                "RANGE".to_string(),
802                                vec![n],
803                            ))))
804                        } else {
805                            Ok(Expression::TableArgument(ta))
806                        }
807                    }
808                    _ => Ok(Expression::TableArgument(ta)),
809                }
810            }
811
812            // JSONExtract (variant_extract/colon accessor) -> arrow syntax in DuckDB
813            Expression::JSONExtract(e) if e.variant_extract.is_some() => {
814                let path = match *e.expression {
815                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
816                        let Literal::String(s) = lit.as_ref() else {
817                            unreachable!()
818                        };
819                        // Convert bracket notation ["key"] to quoted dot notation ."key"
820                        let s = Self::convert_bracket_to_quoted_path(&s);
821                        let normalized = if s.starts_with('$') {
822                            s
823                        } else if s.starts_with('[') {
824                            format!("${}", s)
825                        } else {
826                            format!("$.{}", s)
827                        };
828                        Expression::Literal(Box::new(Literal::String(normalized)))
829                    }
830                    other => other,
831                };
832                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
833                    options: None,
834                    source_dialect: None,
835
836                    this: *e.this,
837                    path,
838                    returning: None,
839                    arrow_syntax: true,
840                    hash_arrow_syntax: false,
841                    wrapper_option: None,
842                    quotes_option: None,
843                    on_scalar_string: false,
844                    on_error: None,
845                })))
846            }
847
848            // X'ABCD' -> UNHEX('ABCD') in DuckDB
849            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::HexString(_)) => {
850                let Literal::HexString(s) = lit.as_ref() else {
851                    unreachable!()
852                };
853                Ok(Expression::Function(Box::new(Function::new(
854                    "UNHEX".to_string(),
855                    vec![Expression::Literal(Box::new(Literal::String(s.clone())))],
856                ))))
857            }
858
859            // b'a' -> CAST(e'a' AS BLOB) in DuckDB
860            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::ByteString(_)) => {
861                let Literal::ByteString(s) = lit.as_ref() else {
862                    unreachable!()
863                };
864                Ok(Expression::Cast(Box::new(Cast {
865                    this: Expression::Literal(Box::new(Literal::EscapeString(s.clone()))),
866                    to: DataType::VarBinary { length: None },
867                    trailing_comments: Vec::new(),
868                    double_colon_syntax: false,
869                    format: None,
870                    default: None,
871                    inferred_type: None,
872                })))
873            }
874
875            // Keep omitted decimal parameters until generation, where the source
876            // dialect is available to distinguish DuckDB and BigQuery defaults.
877            Expression::Cast(mut c) => {
878                let transformed_this = self.transform_expr(c.this)?;
879                c.this = transformed_this;
880                Ok(Expression::Cast(c))
881            }
882
883            // Generic function transformations
884            Expression::Function(f) => self.transform_function(*f),
885
886            // Generic aggregate function transformations
887            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
888
889            // WindowFunction with CASE-wrapped CORR: re-wrap so OVER is inside CASE
890            // Pattern: WindowFunction { this: CASE(ISNAN(CORR), NULL, CORR), over }
891            // Expected: CASE(ISNAN(WindowFunction(CORR, over)), NULL, WindowFunction(CORR, over))
892            Expression::WindowFunction(wf) => {
893                if let Expression::Case(case_box) = wf.this {
894                    let case = *case_box;
895                    // Detect the ISNAN(CORR) -> NULL pattern
896                    if case.whens.len() == 1
897                        && matches!(&case.else_, Some(Expression::AggregateFunction(ref af)) if af.name.to_uppercase() == "CORR")
898                    {
899                        // Re-wrap: put the OVER on each CORR inside the CASE
900                        let over = wf.over;
901                        let new_else = case.else_.map(|e| {
902                            Expression::WindowFunction(Box::new(WindowFunction {
903                                this: e,
904                                over: over.clone(),
905                                keep: None,
906                                inferred_type: None,
907                            }))
908                        });
909                        let new_whens = case
910                            .whens
911                            .into_iter()
912                            .map(|(when_cond, when_result)| {
913                                // wrap the ISNAN arg (which is CORR) with OVER
914                                let new_cond = if let Expression::Function(func) = when_cond {
915                                    if func.name.to_uppercase() == "ISNAN" && func.args.len() == 1 {
916                                        let inner = func.args.into_iter().next().unwrap();
917                                        let windowed =
918                                            Expression::WindowFunction(Box::new(WindowFunction {
919                                                this: inner,
920                                                over: over.clone(),
921                                                keep: None,
922                                                inferred_type: None,
923                                            }));
924                                        Expression::Function(Box::new(Function::new(
925                                            "ISNAN".to_string(),
926                                            vec![windowed],
927                                        )))
928                                    } else {
929                                        Expression::Function(func)
930                                    }
931                                } else {
932                                    when_cond
933                                };
934                                (new_cond, when_result)
935                            })
936                            .collect();
937                        Ok(Expression::Case(Box::new(Case {
938                            operand: None,
939                            whens: new_whens,
940                            else_: new_else,
941                            comments: Vec::new(),
942                            inferred_type: None,
943                        })))
944                    } else {
945                        Ok(Expression::WindowFunction(Box::new(WindowFunction {
946                            this: Expression::Case(Box::new(case)),
947                            over: wf.over,
948                            keep: wf.keep,
949                            inferred_type: None,
950                        })))
951                    }
952                } else {
953                    Ok(Expression::WindowFunction(wf))
954                }
955            }
956
957            // ===== Context-aware JSON arrow wrapping =====
958            // When JSON arrow expressions appear in Binary/In/Not contexts,
959            // they need to be wrapped in parentheses for correct precedence.
960            // This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior.
961
962            // Binary operators that need JSON wrapping
963            Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
964                left: wrap_if_json_arrow(op.left),
965                right: wrap_if_json_arrow(op.right),
966                ..*op
967            }))),
968            Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
969                left: wrap_if_json_arrow(op.left),
970                right: wrap_if_json_arrow(op.right),
971                ..*op
972            }))),
973            Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
974                left: wrap_if_json_arrow(op.left),
975                right: wrap_if_json_arrow(op.right),
976                ..*op
977            }))),
978            Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
979                left: wrap_if_json_arrow(op.left),
980                right: wrap_if_json_arrow(op.right),
981                ..*op
982            }))),
983            Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
984                left: wrap_if_json_arrow(op.left),
985                right: wrap_if_json_arrow(op.right),
986                ..*op
987            }))),
988            Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
989                left: wrap_if_json_arrow(op.left),
990                right: wrap_if_json_arrow(op.right),
991                ..*op
992            }))),
993            Expression::And(op) => Ok(Expression::And(Box::new(BinaryOp {
994                left: wrap_if_json_arrow(op.left),
995                right: wrap_if_json_arrow(op.right),
996                ..*op
997            }))),
998            Expression::Or(op) => Ok(Expression::Or(Box::new(BinaryOp {
999                left: wrap_if_json_arrow(op.left),
1000                right: wrap_if_json_arrow(op.right),
1001                ..*op
1002            }))),
1003            Expression::Add(op) => Ok(Expression::Add(Box::new(BinaryOp {
1004                left: wrap_if_json_arrow(op.left),
1005                right: wrap_if_json_arrow(op.right),
1006                ..*op
1007            }))),
1008            Expression::Sub(op) => Ok(Expression::Sub(Box::new(BinaryOp {
1009                left: wrap_if_json_arrow(op.left),
1010                right: wrap_if_json_arrow(op.right),
1011                ..*op
1012            }))),
1013            Expression::Mul(op) => Ok(Expression::Mul(Box::new(BinaryOp {
1014                left: wrap_if_json_arrow(op.left),
1015                right: wrap_if_json_arrow(op.right),
1016                ..*op
1017            }))),
1018            Expression::Div(op) => Ok(Expression::Div(Box::new(BinaryOp {
1019                left: wrap_if_json_arrow(op.left),
1020                right: wrap_if_json_arrow(op.right),
1021                ..*op
1022            }))),
1023            Expression::Mod(op) => Ok(Expression::Mod(Box::new(BinaryOp {
1024                left: wrap_if_json_arrow(op.left),
1025                right: wrap_if_json_arrow(op.right),
1026                ..*op
1027            }))),
1028            Expression::Concat(op) => Ok(Expression::Concat(Box::new(BinaryOp {
1029                left: wrap_if_json_arrow(op.left),
1030                right: wrap_if_json_arrow(op.right),
1031                ..*op
1032            }))),
1033
1034            // In expression - wrap the this part if it's JSON arrow
1035            // Also transform `expr NOT IN (list)` to `NOT (expr) IN (list)` for DuckDB
1036            Expression::In(mut i) => {
1037                i.this = wrap_if_json_arrow(i.this);
1038                if i.not {
1039                    // Transform `expr NOT IN (list)` to `NOT (expr) IN (list)`
1040                    i.not = false;
1041                    Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
1042                        this: Expression::In(i),
1043                        inferred_type: None,
1044                    })))
1045                } else {
1046                    Ok(Expression::In(i))
1047                }
1048            }
1049
1050            // Not expression - wrap the this part if it's JSON arrow
1051            Expression::Not(mut n) => {
1052                n.this = wrap_if_json_arrow(n.this);
1053                Ok(Expression::Not(n))
1054            }
1055
1056            // WithinGroup: PERCENTILE_CONT/DISC WITHIN GROUP (ORDER BY ...) -> QUANTILE_CONT/DISC(col, quantile ORDER BY ...)
1057            Expression::WithinGroup(wg) => {
1058                match &wg.this {
1059                    Expression::ListAgg(listagg) => {
1060                        let mut listagg = listagg.clone();
1061                        listagg.order_by = Some(wg.order_by.clone());
1062                        Ok(Expression::ListAgg(listagg))
1063                    }
1064                    Expression::PercentileCont(p) => {
1065                        let column = wg
1066                            .order_by
1067                            .first()
1068                            .map(|o| o.this.clone())
1069                            .unwrap_or_else(|| p.this.clone());
1070                        let percentile = p.percentile.clone();
1071                        let filter = p.filter.clone();
1072                        Ok(Expression::AggregateFunction(Box::new(
1073                            crate::expressions::AggregateFunction {
1074                                source_dialect: None,
1075
1076                                name: "QUANTILE_CONT".to_string(),
1077                                args: vec![column, percentile],
1078                                distinct: false,
1079                                filter,
1080                                order_by: wg.order_by,
1081                                limit: None,
1082                                ignore_nulls: None,
1083                                inferred_type: None,
1084                            },
1085                        )))
1086                    }
1087                    Expression::PercentileDisc(p) => {
1088                        let column = wg
1089                            .order_by
1090                            .first()
1091                            .map(|o| o.this.clone())
1092                            .unwrap_or_else(|| p.this.clone());
1093                        let percentile = p.percentile.clone();
1094                        let filter = p.filter.clone();
1095                        Ok(Expression::AggregateFunction(Box::new(
1096                            crate::expressions::AggregateFunction {
1097                                source_dialect: None,
1098
1099                                name: "QUANTILE_DISC".to_string(),
1100                                args: vec![column, percentile],
1101                                distinct: false,
1102                                filter,
1103                                order_by: wg.order_by,
1104                                limit: None,
1105                                ignore_nulls: None,
1106                                inferred_type: None,
1107                            },
1108                        )))
1109                    }
1110                    // Handle case where inner is AggregateFunction with PERCENTILE_CONT/DISC name
1111                    Expression::AggregateFunction(af)
1112                        if af.name == "PERCENTILE_CONT" || af.name == "PERCENTILE_DISC" =>
1113                    {
1114                        let new_name = if af.name == "PERCENTILE_CONT" {
1115                            "QUANTILE_CONT"
1116                        } else {
1117                            "QUANTILE_DISC"
1118                        };
1119                        let column = wg.order_by.first().map(|o| o.this.clone());
1120                        let quantile = af.args.first().cloned();
1121                        match (column, quantile) {
1122                            (Some(col), Some(q)) => Ok(Expression::AggregateFunction(Box::new(
1123                                crate::expressions::AggregateFunction {
1124                                    source_dialect: None,
1125
1126                                    name: new_name.to_string(),
1127                                    args: vec![col, q],
1128                                    distinct: false,
1129                                    filter: af.filter.clone(),
1130                                    order_by: wg.order_by,
1131                                    limit: None,
1132                                    ignore_nulls: None,
1133                                    inferred_type: None,
1134                                },
1135                            ))),
1136                            _ => Ok(Expression::WithinGroup(wg)),
1137                        }
1138                    }
1139                    _ => Ok(Expression::WithinGroup(wg)),
1140                }
1141            }
1142
1143            // ===== DuckDB @ prefix operator → ABS() =====
1144            // In DuckDB, @expr means ABS(expr)
1145            // Parser creates Column with name "@col" — strip the @ and wrap in ABS()
1146            Expression::Column(ref c) if c.name.name.starts_with('@') && c.table.is_none() => {
1147                let col_name = &c.name.name[1..]; // strip leading @
1148                Ok(Expression::Abs(Box::new(UnaryFunc {
1149                    this: Expression::boxed_column(Column {
1150                        name: Identifier::new(col_name),
1151                        table: None,
1152                        join_mark: false,
1153                        trailing_comments: Vec::new(),
1154                        span: None,
1155                        inferred_type: None,
1156                    }),
1157                    original_name: None,
1158                    inferred_type: None,
1159                })))
1160            }
1161
1162            // ===== SELECT-level transforms =====
1163            // DuckDB colon alias syntax: `foo: bar` → `bar AS foo`
1164            // Parser creates JSONExtract(this=foo, expression='bar', variant_extract=true)
1165            // which needs to become Alias(this=Column(bar), alias=foo)
1166            Expression::Select(mut select) => {
1167                select.expressions = select
1168                    .expressions
1169                    .into_iter()
1170                    .map(|e| {
1171                        match e {
1172                            Expression::JSONExtract(ref je) if je.variant_extract.is_some() => {
1173                                // JSONExtract(this=alias_name, expression='value', variant_extract=true) → value AS alias_name
1174                                let alias_ident = match je.this.as_ref() {
1175                                    Expression::Identifier(ident) => Some(ident.clone()),
1176                                    Expression::Column(col) if col.table.is_none() => {
1177                                        Some(col.name.clone())
1178                                    }
1179                                    _ => None,
1180                                };
1181                                let value_expr = match je.expression.as_ref() {
1182                                    Expression::Literal(lit)
1183                                        if matches!(lit.as_ref(), Literal::String(_)) =>
1184                                    {
1185                                        let Literal::String(s) = lit.as_ref() else {
1186                                            unreachable!()
1187                                        };
1188                                        // Convert string path to column reference
1189                                        if s.contains('.') {
1190                                            // t.col → Column { name: col, table: t }
1191                                            let parts: Vec<&str> = s.splitn(2, '.').collect();
1192                                            Some(Expression::boxed_column(Column {
1193                                                name: Identifier::new(parts[1]),
1194                                                table: Some(Identifier::new(parts[0])),
1195                                                join_mark: false,
1196                                                trailing_comments: Vec::new(),
1197                                                span: None,
1198                                                inferred_type: None,
1199                                            }))
1200                                        } else {
1201                                            Some(Expression::boxed_column(Column {
1202                                                name: Identifier::new(s.as_str()),
1203                                                table: None,
1204                                                join_mark: false,
1205                                                trailing_comments: Vec::new(),
1206                                                span: None,
1207                                                inferred_type: None,
1208                                            }))
1209                                        }
1210                                    }
1211                                    _ => None,
1212                                };
1213
1214                                if let (Some(alias), Some(value)) = (alias_ident, value_expr) {
1215                                    Expression::Alias(Box::new(Alias {
1216                                        this: value,
1217                                        alias,
1218                                        column_aliases: Vec::new(),
1219                                        alias_explicit_as: false,
1220                                        alias_keyword: None,
1221                                        pre_alias_comments: Vec::new(),
1222                                        trailing_comments: Vec::new(),
1223                                        inferred_type: None,
1224                                    }))
1225                                } else {
1226                                    e
1227                                }
1228                            }
1229                            _ => e,
1230                        }
1231                    })
1232                    .collect();
1233
1234                // ===== DuckDB comma-join with UNNEST → JOIN ON TRUE =====
1235                // Transform FROM t1, UNNEST(...) AS t2 → FROM t1 JOIN UNNEST(...) AS t2 ON TRUE
1236                if let Some(ref mut from) = select.from {
1237                    if from.expressions.len() > 1 {
1238                        // Check if any expression after the first is UNNEST or Alias wrapping UNNEST
1239                        let mut new_from_exprs = Vec::new();
1240                        let mut new_joins = Vec::new();
1241
1242                        for (idx, expr) in from.expressions.drain(..).enumerate() {
1243                            if idx == 0 {
1244                                // First expression stays in FROM
1245                                new_from_exprs.push(expr);
1246                            } else {
1247                                // Check if this is UNNEST or Alias(UNNEST)
1248                                let is_unnest = match &expr {
1249                                    Expression::Unnest(_) => true,
1250                                    Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
1251                                    _ => false,
1252                                };
1253
1254                                if is_unnest {
1255                                    // Convert to JOIN ON TRUE
1256                                    new_joins.push(crate::expressions::Join {
1257                                        this: expr,
1258                                        on: Some(Expression::Boolean(
1259                                            crate::expressions::BooleanLiteral { value: true },
1260                                        )),
1261                                        using: Vec::new(),
1262                                        kind: crate::expressions::JoinKind::Inner,
1263                                        use_inner_keyword: false,
1264                                        use_outer_keyword: false,
1265                                        deferred_condition: false,
1266                                        join_hint: None,
1267                                        match_condition: None,
1268                                        pivots: Vec::new(),
1269                                        comments: Vec::new(),
1270                                        nesting_group: 0,
1271                                        directed: false,
1272                                    });
1273                                } else {
1274                                    // Keep non-UNNEST expressions in FROM (comma-separated)
1275                                    new_from_exprs.push(expr);
1276                                }
1277                            }
1278                        }
1279
1280                        from.expressions = new_from_exprs;
1281
1282                        // Prepend the new joins before any existing joins
1283                        new_joins.append(&mut select.joins);
1284                        select.joins = new_joins;
1285                    }
1286                }
1287
1288                Ok(Expression::Select(select))
1289            }
1290
1291            // ===== INTERVAL splitting =====
1292            // DuckDB requires INTERVAL '1' HOUR format, not INTERVAL '1 hour'
1293            // When we have INTERVAL 'value unit' (single string with embedded unit),
1294            // split it into INTERVAL 'value' UNIT
1295            Expression::Interval(interval) => self.transform_interval(*interval),
1296
1297            // DuckDB CREATE FUNCTION (macro syntax): normalize param types
1298            Expression::CreateFunction(mut cf) => {
1299                // Apply DuckDB type normalization to function parameters (e.g., FLOAT -> REAL)
1300                cf.parameters = cf
1301                    .parameters
1302                    .into_iter()
1303                    .map(|mut p| {
1304                        if let Ok(Expression::DataType(new_dt)) =
1305                            self.transform_data_type(p.data_type.clone())
1306                        {
1307                            p.data_type = new_dt;
1308                        }
1309                        p
1310                    })
1311                    .collect();
1312
1313                // Normalize TABLE return: if returns_table_body is set (from other dialects
1314                // like TSQL/Databricks), convert to return_type = Custom { "TABLE" } marker.
1315                // This is dialect-agnostic and the generator handles DuckDB vs non-DuckDB output.
1316                if cf.returns_table_body.is_some()
1317                    && !matches!(&cf.return_type, Some(DataType::Custom { ref name }) if name == "TABLE")
1318                {
1319                    cf.return_type = Some(DataType::Custom {
1320                        name: "TABLE".to_string(),
1321                    });
1322                    cf.returns_table_body = None;
1323                }
1324
1325                Ok(Expression::CreateFunction(cf))
1326            }
1327
1328            // ===== Snowflake-specific expression type transforms =====
1329
1330            // IFF(cond, true_val, false_val) -> CASE WHEN cond THEN true_val ELSE false_val END
1331            Expression::IfFunc(f) => Ok(Expression::Case(Box::new(Case {
1332                operand: None,
1333                whens: vec![(f.condition, f.true_value)],
1334                else_: f.false_value,
1335                comments: Vec::new(),
1336                inferred_type: None,
1337            }))),
1338
1339            // VAR_SAMP -> VARIANCE in DuckDB
1340            Expression::VarSamp(f) => Ok(Expression::Function(Box::new(Function::new(
1341                "VARIANCE".to_string(),
1342                vec![f.this],
1343            )))),
1344
1345            // NVL2(expr, val_if_not_null, val_if_null) -> CASE WHEN expr IS NOT NULL THEN val_if_not_null ELSE val_if_null END
1346            Expression::Nvl2(f) => {
1347                let condition = Expression::IsNull(Box::new(crate::expressions::IsNull {
1348                    this: f.this,
1349                    not: true,
1350                    postfix_form: false,
1351                }));
1352                Ok(Expression::Case(Box::new(Case {
1353                    operand: None,
1354                    whens: vec![(condition, f.true_value)],
1355                    else_: Some(f.false_value),
1356                    comments: Vec::new(),
1357                    inferred_type: None,
1358                })))
1359            }
1360
1361            // Pass through everything else
1362            _ => Ok(expr),
1363        }
1364    }
1365}
1366
1367#[cfg(feature = "transpile")]
1368impl DuckDBDialect {
1369    /// Extract a numeric value from a literal expression, if possible
1370    fn extract_number_value(expr: &Expression) -> Option<f64> {
1371        match expr {
1372            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1373                let Literal::Number(n) = lit.as_ref() else {
1374                    unreachable!()
1375                };
1376                n.parse::<f64>().ok()
1377            }
1378            _ => None,
1379        }
1380    }
1381
1382    /// Convert an expression to a SQL string for template-based transformations
1383    fn expr_to_sql(expr: &Expression) -> String {
1384        crate::generator::Generator::sql(expr).unwrap_or_default()
1385    }
1386
1387    /// Extract the seed expression for random-based function emulations.
1388    /// Returns (seed_sql, is_random_no_seed) where:
1389    /// - For RANDOM(): ("RANDOM()", true)
1390    /// - For RANDOM(seed): ("seed", false) - extracts the seed
1391    /// - For literal seed: ("seed_value", false)
1392    fn extract_seed_info(gen: &Expression) -> (String, bool) {
1393        match gen {
1394            Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
1395                if func.args.is_empty() {
1396                    ("RANDOM()".to_string(), true)
1397                } else {
1398                    // RANDOM(seed) -> extract the seed
1399                    (Self::expr_to_sql(&func.args[0]), false)
1400                }
1401            }
1402            Expression::Rand(r) => {
1403                if let Some(ref seed) = r.seed {
1404                    // RANDOM(seed) / RAND(seed) -> extract the seed
1405                    (Self::expr_to_sql(seed), false)
1406                } else {
1407                    ("RANDOM()".to_string(), true)
1408                }
1409            }
1410            Expression::Random(_) => ("RANDOM()".to_string(), true),
1411            _ => (Self::expr_to_sql(gen), false),
1412        }
1413    }
1414
1415    /// Parse a SQL template string and wrap it in a Subquery (parenthesized expression).
1416    /// Uses a thread with larger stack to handle deeply nested template SQL in debug builds.
1417    fn parse_as_subquery(sql: &str) -> Result<Expression> {
1418        let sql_owned = sql.to_string();
1419        let handle = std::thread::Builder::new()
1420            .stack_size(16 * 1024 * 1024) // 16MB stack for complex templates
1421            .spawn(move || match crate::parser::Parser::parse_sql(&sql_owned) {
1422                Ok(stmts) => {
1423                    if let Some(stmt) = stmts.into_iter().next() {
1424                        Ok(Expression::Subquery(Box::new(Subquery {
1425                            this: stmt,
1426                            alias: None,
1427                            column_aliases: Vec::new(),
1428                            alias_explicit_as: false,
1429                            alias_keyword: None,
1430                            order_by: None,
1431                            limit: None,
1432                            offset: None,
1433                            distribute_by: None,
1434                            sort_by: None,
1435                            cluster_by: None,
1436                            lateral: false,
1437                            modifiers_inside: false,
1438                            trailing_comments: Vec::new(),
1439                            inferred_type: None,
1440                        })))
1441                    } else {
1442                        Err(crate::error::Error::Generate(
1443                            "Failed to parse template SQL".to_string(),
1444                        ))
1445                    }
1446                }
1447                Err(e) => Err(e),
1448            })
1449            .map_err(|e| {
1450                crate::error::Error::Internal(format!("Failed to spawn parser thread: {}", e))
1451            })?;
1452
1453        handle
1454            .join()
1455            .map_err(|_| crate::error::Error::Internal("Parser thread panicked".to_string()))?
1456    }
1457
1458    /// Normalize CAST({} AS MAP(...)) style expressions to CAST(MAP() AS MAP(...)).
1459    fn normalize_empty_map_expr(expr: Expression) -> Expression {
1460        match expr {
1461            Expression::Cast(mut c) if matches!(&c.to, DataType::Map { .. }) => {
1462                if matches!(&c.this, Expression::Struct(s) if s.fields.is_empty()) {
1463                    c.this =
1464                        Expression::Function(Box::new(Function::new("MAP".to_string(), vec![])));
1465                }
1466                Expression::Cast(c)
1467            }
1468            other => other,
1469        }
1470    }
1471
1472    /// Convert bracket notation ["key with spaces"] to quoted dot notation ."key with spaces"
1473    /// in JSON path strings. This is needed because Snowflake uses bracket notation for keys
1474    /// with special characters, but DuckDB uses quoted dot notation.
1475    fn convert_bracket_to_quoted_path(path: &str) -> String {
1476        let mut result = String::new();
1477        let mut chars = path.chars().peekable();
1478        while let Some(c) = chars.next() {
1479            if c == '[' && chars.peek() == Some(&'"') {
1480                // Found [" - start of bracket notation
1481                chars.next(); // consume "
1482                let mut key = String::new();
1483                while let Some(kc) = chars.next() {
1484                    if kc == '"' && chars.peek() == Some(&']') {
1485                        chars.next(); // consume ]
1486                        break;
1487                    }
1488                    key.push(kc);
1489                }
1490                // Convert to quoted dot notation: ."key"
1491                if !result.is_empty() && !result.ends_with('.') {
1492                    result.push('.');
1493                }
1494                result.push('"');
1495                result.push_str(&key);
1496                result.push('"');
1497            } else {
1498                result.push(c);
1499            }
1500        }
1501        result
1502    }
1503
1504    /// Transform data types according to DuckDB TYPE_MAPPING
1505    fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
1506        use crate::expressions::DataType;
1507        let transformed = match dt {
1508            // BINARY -> VarBinary (DuckDB generator maps VarBinary to BLOB), preserving length
1509            DataType::Binary { length } => DataType::VarBinary { length },
1510            // BLOB -> VarBinary (DuckDB generator maps VarBinary to BLOB)
1511            // This matches Python sqlglot's DuckDB parser mapping BLOB -> VARBINARY
1512            DataType::Blob => DataType::VarBinary { length: None },
1513            // CHAR/VARCHAR: Keep as-is, DuckDB generator maps to TEXT with length
1514            DataType::Char { .. } | DataType::VarChar { .. } => dt,
1515            // FLOAT -> REAL (use real_spelling flag so generator can decide)
1516            DataType::Float {
1517                precision, scale, ..
1518            } => DataType::Float {
1519                precision,
1520                scale,
1521                real_spelling: true,
1522            },
1523            // JSONB -> JSON
1524            DataType::JsonB => DataType::Json,
1525            // Handle Custom type aliases used in DuckDB
1526            DataType::Custom { ref name } => {
1527                let upper = name.to_uppercase();
1528                match upper.as_str() {
1529                    name if DataType::from_unsigned_name(name).is_some() => {
1530                        DataType::from_unsigned_name(name).unwrap()
1531                    }
1532                    // INT64 -> BIGINT
1533                    "INT64" | "INT8" => DataType::BigInt { length: None },
1534                    // INT32, INT4, SIGNED -> INT
1535                    "INT32" | "INT4" | "SIGNED" => DataType::Int {
1536                        length: None,
1537                        integer_spelling: false,
1538                    },
1539                    // INT16 -> SMALLINT
1540                    "INT16" => DataType::SmallInt { length: None },
1541                    // INT1 -> TINYINT
1542                    "INT1" => DataType::TinyInt { length: None },
1543                    // HUGEINT -> INT128
1544                    "HUGEINT" | "INT128" => DataType::Int128,
1545                    // BPCHAR -> TEXT
1546                    "BPCHAR" => DataType::Text,
1547                    // CHARACTER VARYING, CHAR VARYING -> TEXT
1548                    "CHARACTER VARYING" | "CHAR VARYING" => DataType::Text,
1549                    // FLOAT4, REAL -> REAL
1550                    "FLOAT4" => DataType::Custom {
1551                        name: "REAL".to_string(),
1552                    },
1553                    // LOGICAL -> BOOLEAN
1554                    "LOGICAL" => DataType::Boolean,
1555                    // TIMESTAMPNTZ / TIMESTAMP_NTZ -> TIMESTAMP
1556                    "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => DataType::Timestamp {
1557                        precision: None,
1558                        timezone: false,
1559                    },
1560                    // TIMESTAMP_US -> TIMESTAMP (DuckDB's default timestamp is microsecond precision)
1561                    "TIMESTAMP_US" => DataType::Timestamp {
1562                        precision: None,
1563                        timezone: false,
1564                    },
1565                    // TIMESTAMPLTZ / TIMESTAMPTZ / TIMESTAMP_LTZ / TIMESTAMP_TZ -> TIMESTAMPTZ
1566                    "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" | "TIMESTAMPTZ" | "TIMESTAMP_TZ" => {
1567                        DataType::Timestamp {
1568                            precision: None,
1569                            timezone: true,
1570                        }
1571                    }
1572                    // DECFLOAT -> DECIMAL(38, 5) in DuckDB
1573                    "DECFLOAT" => DataType::Decimal {
1574                        precision: Some(38),
1575                        scale: Some(5),
1576                    },
1577                    // Keep other custom types as-is
1578                    _ => dt,
1579                }
1580            }
1581            // Keep all other types as-is
1582            other => other,
1583        };
1584        Ok(Expression::DataType(transformed))
1585    }
1586
1587    /// Transform interval to split embedded value+unit strings (e.g., '1 hour' -> '1' HOUR)
1588    /// DuckDB requires INTERVAL 'value' UNIT format, not INTERVAL 'value unit' format
1589    fn transform_interval(&self, interval: Interval) -> Result<Expression> {
1590        // Only transform if:
1591        // 1. There's a string literal value
1592        // 2. There's no unit already specified
1593        if interval.unit.is_some() {
1594            // Already has a unit, keep as-is
1595            return Ok(Expression::Interval(Box::new(interval)));
1596        }
1597
1598        if let Some(Expression::Literal(ref lit)) = interval.this {
1599            if let Literal::String(ref s) = lit.as_ref() {
1600                // Try to parse the string as "value unit" format
1601                if let Some((value, unit)) = Self::parse_interval_string(s) {
1602                    // Create new interval with separated value and unit
1603                    return Ok(Expression::Interval(Box::new(Interval {
1604                        this: Some(Expression::Literal(Box::new(Literal::String(
1605                            value.to_string(),
1606                        )))),
1607                        unit: Some(IntervalUnitSpec::Simple {
1608                            unit,
1609                            use_plural: false, // DuckDB uses singular form
1610                        }),
1611                    })));
1612                }
1613            }
1614        }
1615
1616        // No transformation needed
1617        Ok(Expression::Interval(Box::new(interval)))
1618    }
1619
1620    /// Parse an interval string like "1 hour" into (value, unit)
1621    /// Returns None if the string doesn't match the expected format
1622    fn parse_interval_string(s: &str) -> Option<(&str, IntervalUnit)> {
1623        let s = s.trim();
1624
1625        // Find where the number ends and the unit begins
1626        // Number can be: optional -, digits, optional decimal point, more digits
1627        let mut num_end = 0;
1628        let mut chars = s.chars().peekable();
1629
1630        // Skip leading minus
1631        if chars.peek() == Some(&'-') {
1632            chars.next();
1633            num_end += 1;
1634        }
1635
1636        // Skip digits
1637        while let Some(&c) = chars.peek() {
1638            if c.is_ascii_digit() {
1639                chars.next();
1640                num_end += 1;
1641            } else {
1642                break;
1643            }
1644        }
1645
1646        // Skip optional decimal point and more digits
1647        if chars.peek() == Some(&'.') {
1648            chars.next();
1649            num_end += 1;
1650            while let Some(&c) = chars.peek() {
1651                if c.is_ascii_digit() {
1652                    chars.next();
1653                    num_end += 1;
1654                } else {
1655                    break;
1656                }
1657            }
1658        }
1659
1660        if num_end == 0 || (num_end == 1 && s.starts_with('-')) {
1661            return None; // No number found
1662        }
1663
1664        let value = &s[..num_end];
1665        let rest = s[num_end..].trim();
1666
1667        // Rest should be alphabetic (the unit)
1668        if rest.is_empty() || !rest.chars().all(|c| c.is_ascii_alphabetic()) {
1669            return None;
1670        }
1671
1672        // Map unit string to IntervalUnit
1673        let unit = match rest.to_uppercase().as_str() {
1674            "YEAR" | "YEARS" | "Y" => IntervalUnit::Year,
1675            "MONTH" | "MONTHS" | "MON" | "MONS" => IntervalUnit::Month,
1676            "DAY" | "DAYS" | "D" => IntervalUnit::Day,
1677            "HOUR" | "HOURS" | "H" | "HR" | "HRS" => IntervalUnit::Hour,
1678            "MINUTE" | "MINUTES" | "MIN" | "MINS" | "M" => IntervalUnit::Minute,
1679            "SECOND" | "SECONDS" | "SEC" | "SECS" | "S" => IntervalUnit::Second,
1680            "MILLISECOND" | "MILLISECONDS" | "MS" => IntervalUnit::Millisecond,
1681            "MICROSECOND" | "MICROSECONDS" | "US" => IntervalUnit::Microsecond,
1682            "QUARTER" | "QUARTERS" | "Q" => IntervalUnit::Quarter,
1683            "WEEK" | "WEEKS" | "W" => IntervalUnit::Week,
1684            _ => return None, // Unknown unit
1685        };
1686
1687        Some((value, unit))
1688    }
1689
1690    fn transform_function(&self, f: Function) -> Result<Expression> {
1691        let name_upper = f.name.to_uppercase();
1692        match name_upper.as_str() {
1693            "FROM_HEX" if f.args.len() == 1 => {
1694                let mut args = f.args;
1695                Ok(Expression::Unhex(Box::new(Unhex {
1696                    this: Box::new(args.remove(0)),
1697                    expression: None,
1698                })))
1699            }
1700
1701            // IFNULL -> COALESCE
1702            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1703                original_name: None,
1704                expressions: f.args,
1705                inferred_type: None,
1706            }))),
1707
1708            // NVL -> COALESCE
1709            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1710                original_name: None,
1711                expressions: f.args,
1712                inferred_type: None,
1713            }))),
1714
1715            // ISNULL -> COALESCE
1716            "ISNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1717                original_name: None,
1718                expressions: f.args,
1719                inferred_type: None,
1720            }))),
1721
1722            // ARRAY_COMPACT(arr) -> LIST_FILTER(arr, _u -> NOT _u IS NULL)
1723            "ARRAY_COMPACT" if f.args.len() == 1 => {
1724                let arr = f.args.into_iter().next().unwrap();
1725                let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
1726                    parameters: vec![Identifier::new("_u".to_string())],
1727                    body: Expression::Not(Box::new(crate::expressions::UnaryOp {
1728                        this: Expression::IsNull(Box::new(crate::expressions::IsNull {
1729                            this: Expression::boxed_column(Column {
1730                                table: None,
1731                                name: Identifier::new("_u".to_string()),
1732                                join_mark: false,
1733                                trailing_comments: Vec::new(),
1734                                span: None,
1735                                inferred_type: None,
1736                            }),
1737                            not: false,
1738                            postfix_form: false,
1739                        })),
1740                        inferred_type: None,
1741                    })),
1742                    colon: false,
1743                    parameter_types: Vec::new(),
1744                }));
1745                Ok(Expression::Function(Box::new(Function::new(
1746                    "LIST_FILTER".to_string(),
1747                    vec![arr, lambda],
1748                ))))
1749            }
1750
1751            // ARRAY_CONSTRUCT_COMPACT: handled in the generator (to avoid source-transform interference)
1752            "ARRAY_CONSTRUCT_COMPACT" => Ok(Expression::Function(Box::new(f))),
1753
1754            // GROUP_CONCAT -> LISTAGG in DuckDB
1755            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1756                Function::new("LISTAGG".to_string(), f.args),
1757            ))),
1758
1759            // LISTAGG is native to DuckDB
1760            "LISTAGG" => Ok(Expression::Function(Box::new(f))),
1761
1762            // STRING_AGG -> LISTAGG in DuckDB
1763            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1764                Function::new("LISTAGG".to_string(), f.args),
1765            ))),
1766
1767            // ARRAY_UNIQUE_AGG(col) -> LIST(DISTINCT col) FILTER(WHERE NOT col IS NULL)
1768            "ARRAY_UNIQUE_AGG" if f.args.len() == 1 => {
1769                let col = f.args.into_iter().next().unwrap();
1770                // NOT col IS NULL
1771                let filter_expr = Expression::Not(Box::new(UnaryOp {
1772                    this: Expression::IsNull(Box::new(IsNull {
1773                        this: col.clone(),
1774                        not: false,
1775                        postfix_form: false,
1776                    })),
1777                    inferred_type: None,
1778                }));
1779                Ok(Expression::ArrayAgg(Box::new(AggFunc {
1780                    this: col,
1781                    distinct: true,
1782                    filter: Some(filter_expr),
1783                    order_by: Vec::new(),
1784                    name: Some("LIST".to_string()),
1785                    ignore_nulls: None,
1786                    having_max: None,
1787                    limit: None,
1788                    inferred_type: None,
1789                })))
1790            }
1791
1792            // CHECK_JSON(x) -> CASE WHEN x IS NULL OR x = '' OR JSON_VALID(x) THEN NULL ELSE 'Invalid JSON' END
1793            "CHECK_JSON" if f.args.len() == 1 => {
1794                let x = f.args.into_iter().next().unwrap();
1795                // x IS NULL
1796                let is_null = Expression::IsNull(Box::new(IsNull {
1797                    this: x.clone(),
1798                    not: false,
1799                    postfix_form: false,
1800                }));
1801                // x = ''
1802                let eq_empty = Expression::Eq(Box::new(BinaryOp::new(
1803                    x.clone(),
1804                    Expression::Literal(Box::new(Literal::String(String::new()))),
1805                )));
1806                // JSON_VALID(x)
1807                let json_valid = Expression::Function(Box::new(Function::new(
1808                    "JSON_VALID".to_string(),
1809                    vec![x],
1810                )));
1811                // x IS NULL OR x = ''
1812                let or1 = Expression::Or(Box::new(BinaryOp::new(is_null, eq_empty)));
1813                // (x IS NULL OR x = '') OR JSON_VALID(x)
1814                let condition = Expression::Or(Box::new(BinaryOp::new(or1, json_valid)));
1815                Ok(Expression::Case(Box::new(Case {
1816                    operand: None,
1817                    whens: vec![(condition, Expression::Null(Null))],
1818                    else_: Some(Expression::Literal(Box::new(Literal::String(
1819                        "Invalid JSON".to_string(),
1820                    )))),
1821                    comments: Vec::new(),
1822                    inferred_type: None,
1823                })))
1824            }
1825
1826            // SUBSTR is native in DuckDB (keep as-is, don't convert to SUBSTRING)
1827            "SUBSTR" => Ok(Expression::Function(Box::new(f))),
1828
1829            // FLATTEN -> UNNEST in DuckDB
1830            "FLATTEN" => Ok(Expression::Function(Box::new(Function::new(
1831                "UNNEST".to_string(),
1832                f.args,
1833            )))),
1834
1835            // ARRAY_FLATTEN -> FLATTEN in DuckDB
1836            "ARRAY_FLATTEN" => Ok(Expression::Function(Box::new(Function::new(
1837                "FLATTEN".to_string(),
1838                f.args,
1839            )))),
1840
1841            // RPAD with 2 args -> RPAD with 3 args (default padding ' ')
1842            "RPAD" if f.args.len() == 2 => {
1843                let mut args = f.args;
1844                args.push(Expression::Literal(Box::new(Literal::String(
1845                    " ".to_string(),
1846                ))));
1847                Ok(Expression::Function(Box::new(Function::new(
1848                    "RPAD".to_string(),
1849                    args,
1850                ))))
1851            }
1852
1853            // BASE64_DECODE_STRING(x) -> DECODE(FROM_BASE64(x))
1854            // BASE64_DECODE_STRING(x, alphabet) -> DECODE(FROM_BASE64(REPLACE(REPLACE(REPLACE(x, '-', '+'), '_', '/'), '+', '=')))
1855            "BASE64_DECODE_STRING" => {
1856                let mut args = f.args;
1857                let input = args.remove(0);
1858                let has_alphabet = !args.is_empty();
1859                let decoded_input = if has_alphabet {
1860                    // Apply alphabet replacements: REPLACE(REPLACE(REPLACE(x, '-', '+'), '_', '/'), '+', '=')
1861                    let r1 = Expression::Function(Box::new(Function::new(
1862                        "REPLACE".to_string(),
1863                        vec![
1864                            input,
1865                            Expression::Literal(Box::new(Literal::String("-".to_string()))),
1866                            Expression::Literal(Box::new(Literal::String("+".to_string()))),
1867                        ],
1868                    )));
1869                    let r2 = Expression::Function(Box::new(Function::new(
1870                        "REPLACE".to_string(),
1871                        vec![
1872                            r1,
1873                            Expression::Literal(Box::new(Literal::String("_".to_string()))),
1874                            Expression::Literal(Box::new(Literal::String("/".to_string()))),
1875                        ],
1876                    )));
1877                    Expression::Function(Box::new(Function::new(
1878                        "REPLACE".to_string(),
1879                        vec![
1880                            r2,
1881                            Expression::Literal(Box::new(Literal::String("+".to_string()))),
1882                            Expression::Literal(Box::new(Literal::String("=".to_string()))),
1883                        ],
1884                    )))
1885                } else {
1886                    input
1887                };
1888                let from_base64 = Expression::Function(Box::new(Function::new(
1889                    "FROM_BASE64".to_string(),
1890                    vec![decoded_input],
1891                )));
1892                Ok(Expression::Function(Box::new(Function::new(
1893                    "DECODE".to_string(),
1894                    vec![from_base64],
1895                ))))
1896            }
1897
1898            // BASE64_DECODE_BINARY(x) -> FROM_BASE64(x)
1899            // BASE64_DECODE_BINARY(x, alphabet) -> FROM_BASE64(REPLACE(REPLACE(REPLACE(x, '-', '+'), '_', '/'), '+', '='))
1900            "BASE64_DECODE_BINARY" => {
1901                let mut args = f.args;
1902                let input = args.remove(0);
1903                let has_alphabet = !args.is_empty();
1904                let decoded_input = if has_alphabet {
1905                    let r1 = Expression::Function(Box::new(Function::new(
1906                        "REPLACE".to_string(),
1907                        vec![
1908                            input,
1909                            Expression::Literal(Box::new(Literal::String("-".to_string()))),
1910                            Expression::Literal(Box::new(Literal::String("+".to_string()))),
1911                        ],
1912                    )));
1913                    let r2 = Expression::Function(Box::new(Function::new(
1914                        "REPLACE".to_string(),
1915                        vec![
1916                            r1,
1917                            Expression::Literal(Box::new(Literal::String("_".to_string()))),
1918                            Expression::Literal(Box::new(Literal::String("/".to_string()))),
1919                        ],
1920                    )));
1921                    Expression::Function(Box::new(Function::new(
1922                        "REPLACE".to_string(),
1923                        vec![
1924                            r2,
1925                            Expression::Literal(Box::new(Literal::String("+".to_string()))),
1926                            Expression::Literal(Box::new(Literal::String("=".to_string()))),
1927                        ],
1928                    )))
1929                } else {
1930                    input
1931                };
1932                Ok(Expression::Function(Box::new(Function::new(
1933                    "FROM_BASE64".to_string(),
1934                    vec![decoded_input],
1935                ))))
1936            }
1937
1938            // SPACE(n) -> REPEAT(' ', CAST(n AS BIGINT))
1939            "SPACE" if f.args.len() == 1 => {
1940                let arg = f.args.into_iter().next().unwrap();
1941                let cast_arg = Expression::Cast(Box::new(Cast {
1942                    this: arg,
1943                    to: DataType::BigInt { length: None },
1944                    trailing_comments: Vec::new(),
1945                    double_colon_syntax: false,
1946                    format: None,
1947                    default: None,
1948                    inferred_type: None,
1949                }));
1950                Ok(Expression::Function(Box::new(Function::new(
1951                    "REPEAT".to_string(),
1952                    vec![
1953                        Expression::Literal(Box::new(Literal::String(" ".to_string()))),
1954                        cast_arg,
1955                    ],
1956                ))))
1957            }
1958
1959            // IS_ARRAY(x) -> JSON_TYPE(x) = 'ARRAY'
1960            "IS_ARRAY" if f.args.len() == 1 => {
1961                let arg = f.args.into_iter().next().unwrap();
1962                let json_type = Expression::Function(Box::new(Function::new(
1963                    "JSON_TYPE".to_string(),
1964                    vec![arg],
1965                )));
1966                Ok(Expression::Eq(Box::new(BinaryOp {
1967                    left: json_type,
1968                    right: Expression::Literal(Box::new(Literal::String("ARRAY".to_string()))),
1969                    left_comments: Vec::new(),
1970                    operator_comments: Vec::new(),
1971                    trailing_comments: Vec::new(),
1972                    inferred_type: None,
1973                })))
1974            }
1975
1976            // EXPLODE -> UNNEST
1977            "EXPLODE" => Ok(Expression::Function(Box::new(Function::new(
1978                "UNNEST".to_string(),
1979                f.args,
1980            )))),
1981
1982            // GETDATE -> CURRENT_TIMESTAMP
1983            "GETDATE" => Ok(Expression::CurrentTimestamp(
1984                crate::expressions::CurrentTimestamp {
1985                    precision: None,
1986                    sysdate: false,
1987                },
1988            )),
1989
1990            // TODAY -> CURRENT_DATE
1991            "TODAY" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
1992
1993            // CURDATE -> CURRENT_DATE
1994            "CURDATE" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
1995
1996            // GET_CURRENT_TIME -> CURRENT_TIME
1997            "GET_CURRENT_TIME" => Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
1998                precision: None,
1999            })),
2000
2001            // CURRENT_LOCALTIMESTAMP -> LOCALTIMESTAMP
2002            "CURRENT_LOCALTIMESTAMP" => Ok(Expression::Localtimestamp(Box::new(
2003                crate::expressions::Localtimestamp { this: None },
2004            ))),
2005
2006            // REGEXP_EXTRACT_ALL: strip default group_idx=0
2007            "REGEXP_EXTRACT_ALL" if f.args.len() == 3 => {
2008                // If third arg is literal 0, strip it
2009                if matches!(&f.args[2], Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::Number(n) if n == "0"))
2010                {
2011                    Ok(Expression::Function(Box::new(Function::new(
2012                        "REGEXP_EXTRACT_ALL".to_string(),
2013                        vec![f.args[0].clone(), f.args[1].clone()],
2014                    ))))
2015                } else {
2016                    Ok(Expression::Function(Box::new(Function::new(
2017                        "REGEXP_EXTRACT_ALL".to_string(),
2018                        f.args,
2019                    ))))
2020                }
2021            }
2022
2023            // CURRENT_DATE is native
2024            "CURRENT_DATE" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
2025
2026            // TO_DATE with 1 arg -> CAST(x AS DATE)
2027            "TO_DATE" if f.args.len() == 1 => {
2028                let arg = f.args.into_iter().next().unwrap();
2029                Ok(Expression::Cast(Box::new(Cast {
2030                    this: arg,
2031                    to: DataType::Date,
2032                    trailing_comments: Vec::new(),
2033                    double_colon_syntax: false,
2034                    format: None,
2035                    default: None,
2036                    inferred_type: None,
2037                })))
2038            }
2039
2040            // TO_TIMESTAMP is native in DuckDB (kept as-is for identity)
2041
2042            // DATE_FORMAT -> STRFTIME in DuckDB with format conversion
2043            "DATE_FORMAT" if f.args.len() >= 2 => {
2044                let mut args = f.args;
2045                args[1] = Self::convert_format_to_duckdb(&args[1]);
2046                Ok(Expression::Function(Box::new(Function::new(
2047                    "STRFTIME".to_string(),
2048                    args,
2049                ))))
2050            }
2051
2052            // DATE_PARSE -> STRPTIME in DuckDB with format conversion
2053            "DATE_PARSE" if f.args.len() >= 2 => {
2054                let mut args = f.args;
2055                args[1] = Self::convert_format_to_duckdb(&args[1]);
2056                Ok(Expression::Function(Box::new(Function::new(
2057                    "STRPTIME".to_string(),
2058                    args,
2059                ))))
2060            }
2061
2062            // FORMAT_DATE -> STRFTIME in DuckDB
2063            "FORMAT_DATE" if f.args.len() >= 2 => {
2064                let mut args = f.args;
2065                args[1] = Self::convert_format_to_duckdb(&args[1]);
2066                Ok(Expression::Function(Box::new(Function::new(
2067                    "STRFTIME".to_string(),
2068                    args,
2069                ))))
2070            }
2071
2072            // TO_CHAR -> STRFTIME in DuckDB
2073            "TO_CHAR" if f.args.len() >= 2 => {
2074                let mut args = f.args;
2075                args[1] = Self::convert_format_to_duckdb(&args[1]);
2076                Ok(Expression::Function(Box::new(Function::new(
2077                    "STRFTIME".to_string(),
2078                    args,
2079                ))))
2080            }
2081
2082            // EPOCH_MS is native to DuckDB
2083            "EPOCH_MS" => Ok(Expression::Function(Box::new(f))),
2084
2085            // EPOCH -> EPOCH (native)
2086            "EPOCH" => Ok(Expression::Function(Box::new(f))),
2087
2088            // FROM_UNIXTIME -> TO_TIMESTAMP in DuckDB
2089            "FROM_UNIXTIME" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
2090                Function::new("TO_TIMESTAMP".to_string(), f.args),
2091            ))),
2092
2093            // UNIX_TIMESTAMP -> EPOCH
2094            "UNIX_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
2095                "EPOCH".to_string(),
2096                f.args,
2097            )))),
2098
2099            // JSON_EXTRACT -> arrow operator (->)
2100            "JSON_EXTRACT" if f.args.len() == 2 => {
2101                let mut args = f.args;
2102                let path = args.pop().unwrap();
2103                let this = args.pop().unwrap();
2104                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
2105                    options: None,
2106                    source_dialect: None,
2107
2108                    this,
2109                    path,
2110                    returning: None,
2111                    arrow_syntax: true,
2112                    hash_arrow_syntax: false,
2113                    wrapper_option: None,
2114                    quotes_option: None,
2115                    on_scalar_string: false,
2116                    on_error: None,
2117                })))
2118            }
2119
2120            // JSON_EXTRACT_STRING -> arrow operator (->>)
2121            "JSON_EXTRACT_STRING" if f.args.len() == 2 => {
2122                let mut args = f.args;
2123                let path = args.pop().unwrap();
2124                let this = args.pop().unwrap();
2125                Ok(Expression::JsonExtractScalar(Box::new(JsonExtractFunc {
2126                    options: None,
2127                    source_dialect: None,
2128
2129                    this,
2130                    path,
2131                    returning: None,
2132                    arrow_syntax: true,
2133                    hash_arrow_syntax: false,
2134                    wrapper_option: None,
2135                    quotes_option: None,
2136                    on_scalar_string: false,
2137                    on_error: None,
2138                })))
2139            }
2140
2141            // ARRAY_CONSTRUCT -> list_value or [a, b, c] syntax
2142            "ARRAY_CONSTRUCT" => Ok(Expression::Function(Box::new(Function::new(
2143                "list_value".to_string(),
2144                f.args,
2145            )))),
2146
2147            // ARRAY -> list_value
2148            // ARRAY -> list_value for non-subquery args, keep ARRAY for subquery args
2149            "ARRAY" => {
2150                // Check if any arg contains a query (subquery)
2151                let has_query = f
2152                    .args
2153                    .iter()
2154                    .any(|a| matches!(a, Expression::Subquery(_) | Expression::Select(_)));
2155                if has_query {
2156                    // Keep as ARRAY() for subquery args
2157                    Ok(Expression::Function(Box::new(Function::new(
2158                        "ARRAY".to_string(),
2159                        f.args,
2160                    ))))
2161                } else {
2162                    Ok(Expression::Function(Box::new(Function::new(
2163                        "list_value".to_string(),
2164                        f.args,
2165                    ))))
2166                }
2167            }
2168
2169            // LIST_VALUE -> Array literal notation [...]
2170            "LIST_VALUE" => Ok(Expression::Array(Box::new(crate::expressions::Array {
2171                expressions: f.args,
2172                inferred_type: None,
2173            }))),
2174
2175            // ARRAY_AGG -> LIST in DuckDB (or array_agg which is also supported)
2176            "ARRAY_AGG" => Ok(Expression::Function(Box::new(Function::new(
2177                "list".to_string(),
2178                f.args,
2179            )))),
2180
2181            // LIST_CONTAINS / ARRAY_CONTAINS -> keep normalized form
2182            "LIST_CONTAINS" | "ARRAY_CONTAINS" => Ok(Expression::Function(Box::new(
2183                Function::new("ARRAY_CONTAINS".to_string(), f.args),
2184            ))),
2185
2186            // ARRAY_SIZE/CARDINALITY -> ARRAY_LENGTH in DuckDB
2187            "ARRAY_SIZE" | "CARDINALITY" => Ok(Expression::Function(Box::new(Function::new(
2188                "ARRAY_LENGTH".to_string(),
2189                f.args,
2190            )))),
2191
2192            // LEN -> LENGTH
2193            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
2194                f.args.into_iter().next().unwrap(),
2195            )))),
2196
2197            // CEILING -> CEIL (both work)
2198            "CEILING" if f.args.len() == 1 => Ok(Expression::Ceil(Box::new(CeilFunc {
2199                this: f.args.into_iter().next().unwrap(),
2200                decimals: None,
2201                to: None,
2202            }))),
2203
2204            // LOGICAL_OR -> BOOL_OR with CAST to BOOLEAN
2205            "LOGICAL_OR" if f.args.len() == 1 => {
2206                let arg = f.args.into_iter().next().unwrap();
2207                Ok(Expression::Function(Box::new(Function::new(
2208                    "BOOL_OR".to_string(),
2209                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
2210                        this: arg,
2211                        to: crate::expressions::DataType::Boolean,
2212                        trailing_comments: Vec::new(),
2213                        double_colon_syntax: false,
2214                        format: None,
2215                        default: None,
2216                        inferred_type: None,
2217                    }))],
2218                ))))
2219            }
2220
2221            // LOGICAL_AND -> BOOL_AND with CAST to BOOLEAN
2222            "LOGICAL_AND" if f.args.len() == 1 => {
2223                let arg = f.args.into_iter().next().unwrap();
2224                Ok(Expression::Function(Box::new(Function::new(
2225                    "BOOL_AND".to_string(),
2226                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
2227                        this: arg,
2228                        to: crate::expressions::DataType::Boolean,
2229                        trailing_comments: Vec::new(),
2230                        double_colon_syntax: false,
2231                        format: None,
2232                        default: None,
2233                        inferred_type: None,
2234                    }))],
2235                ))))
2236            }
2237
2238            // REGEXP_LIKE -> REGEXP_MATCHES in DuckDB
2239            "REGEXP_LIKE" => Ok(Expression::Function(Box::new(Function::new(
2240                "REGEXP_MATCHES".to_string(),
2241                f.args,
2242            )))),
2243
2244            // POSITION is native
2245            "POSITION" => Ok(Expression::Function(Box::new(f))),
2246
2247            // CHARINDEX(substr, str) -> STRPOS(str, substr) in DuckDB
2248            "CHARINDEX" if f.args.len() == 2 => {
2249                let mut args = f.args;
2250                let substr = args.remove(0);
2251                let str_expr = args.remove(0);
2252                Ok(Expression::Function(Box::new(Function::new(
2253                    "STRPOS".to_string(),
2254                    vec![str_expr, substr],
2255                ))))
2256            }
2257
2258            // CHARINDEX(substr, str, pos) -> complex CASE expression for DuckDB
2259            // CASE WHEN STRPOS(SUBSTRING(str, CASE WHEN pos <= 0 THEN 1 ELSE pos END), substr) = 0
2260            // THEN 0
2261            // ELSE STRPOS(SUBSTRING(str, CASE WHEN pos <= 0 THEN 1 ELSE pos END), substr) + CASE WHEN pos <= 0 THEN 1 ELSE pos END - 1
2262            // END
2263            "CHARINDEX" if f.args.len() == 3 => {
2264                let mut args = f.args;
2265                let substr = args.remove(0);
2266                let str_expr = args.remove(0);
2267                let pos = args.remove(0);
2268
2269                let zero = Expression::Literal(Box::new(Literal::Number("0".to_string())));
2270                let one = Expression::Literal(Box::new(Literal::Number("1".to_string())));
2271
2272                // CASE WHEN pos <= 0 THEN 1 ELSE pos END
2273                let pos_case = Expression::Case(Box::new(Case {
2274                    operand: None,
2275                    whens: vec![(
2276                        Expression::Lte(Box::new(BinaryOp::new(pos.clone(), zero.clone()))),
2277                        one.clone(),
2278                    )],
2279                    else_: Some(pos.clone()),
2280                    comments: Vec::new(),
2281                    inferred_type: None,
2282                }));
2283
2284                // SUBSTRING(str, pos_case)
2285                let substring_expr = Expression::Substring(Box::new(SubstringFunc {
2286                    this: str_expr,
2287                    start: pos_case.clone(),
2288                    length: None,
2289                    from_for_syntax: false,
2290                }));
2291
2292                // STRPOS(SUBSTRING(...), substr)
2293                let strpos = Expression::Function(Box::new(Function::new(
2294                    "STRPOS".to_string(),
2295                    vec![substring_expr, substr],
2296                )));
2297
2298                // STRPOS(...) = 0
2299                let eq_zero = Expression::Eq(Box::new(BinaryOp::new(strpos.clone(), zero.clone())));
2300
2301                // STRPOS(...) + pos_case - 1
2302                let add_pos = Expression::Add(Box::new(BinaryOp::new(strpos, pos_case)));
2303                let sub_one = Expression::Sub(Box::new(BinaryOp::new(add_pos, one)));
2304
2305                Ok(Expression::Case(Box::new(Case {
2306                    operand: None,
2307                    whens: vec![(eq_zero, zero)],
2308                    else_: Some(sub_one),
2309                    comments: Vec::new(),
2310                    inferred_type: None,
2311                })))
2312            }
2313
2314            // SPLIT -> STR_SPLIT in DuckDB
2315            "SPLIT" => Ok(Expression::Function(Box::new(Function::new(
2316                "STR_SPLIT".to_string(),
2317                f.args,
2318            )))),
2319
2320            // STRING_SPLIT -> STR_SPLIT in DuckDB
2321            "STRING_SPLIT" => Ok(Expression::Function(Box::new(Function::new(
2322                "STR_SPLIT".to_string(),
2323                f.args,
2324            )))),
2325
2326            // STRTOK_TO_ARRAY -> STR_SPLIT
2327            "STRTOK_TO_ARRAY" => Ok(Expression::Function(Box::new(Function::new(
2328                "STR_SPLIT".to_string(),
2329                f.args,
2330            )))),
2331
2332            // REGEXP_SPLIT -> STR_SPLIT_REGEX in DuckDB
2333            "REGEXP_SPLIT" => Ok(Expression::Function(Box::new(Function::new(
2334                "STR_SPLIT_REGEX".to_string(),
2335                f.args,
2336            )))),
2337
2338            // EDITDIST3 -> LEVENSHTEIN in DuckDB
2339            "EDITDIST3" => Ok(Expression::Function(Box::new(Function::new(
2340                "LEVENSHTEIN".to_string(),
2341                f.args,
2342            )))),
2343
2344            // JSON_EXTRACT_PATH -> arrow operator (->)
2345            "JSON_EXTRACT_PATH" if f.args.len() >= 2 => {
2346                let mut args = f.args;
2347                let this = args.remove(0);
2348                let path = args.remove(0);
2349                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
2350                    options: None,
2351                    source_dialect: None,
2352
2353                    this,
2354                    path,
2355                    returning: None,
2356                    arrow_syntax: true,
2357                    hash_arrow_syntax: false,
2358                    wrapper_option: None,
2359                    quotes_option: None,
2360                    on_scalar_string: false,
2361                    on_error: None,
2362                })))
2363            }
2364
2365            // JSON_EXTRACT_PATH_TEXT -> arrow operator (->>)
2366            "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
2367                let mut args = f.args;
2368                let this = args.remove(0);
2369                let path = args.remove(0);
2370                Ok(Expression::JsonExtractScalar(Box::new(JsonExtractFunc {
2371                    options: None,
2372                    source_dialect: None,
2373
2374                    this,
2375                    path,
2376                    returning: None,
2377                    arrow_syntax: true,
2378                    hash_arrow_syntax: false,
2379                    wrapper_option: None,
2380                    quotes_option: None,
2381                    on_scalar_string: false,
2382                    on_error: None,
2383                })))
2384            }
2385
2386            // DATE_ADD(date, interval) -> date + interval in DuckDB
2387            "DATE_ADD" if f.args.len() == 2 => {
2388                let mut args = f.args;
2389                let date = args.remove(0);
2390                let interval = args.remove(0);
2391                Ok(Expression::Add(Box::new(BinaryOp {
2392                    left: date,
2393                    right: interval,
2394                    left_comments: Vec::new(),
2395                    operator_comments: Vec::new(),
2396                    trailing_comments: Vec::new(),
2397                    inferred_type: None,
2398                })))
2399            }
2400
2401            // DATE_SUB(date, interval) -> date - interval in DuckDB
2402            "DATE_SUB" if f.args.len() == 2 => {
2403                let mut args = f.args;
2404                let date = args.remove(0);
2405                let interval = args.remove(0);
2406                Ok(Expression::Sub(Box::new(BinaryOp {
2407                    left: date,
2408                    right: interval,
2409                    left_comments: Vec::new(),
2410                    operator_comments: Vec::new(),
2411                    trailing_comments: Vec::new(),
2412                    inferred_type: None,
2413                })))
2414            }
2415
2416            // RANGE(n) -> RANGE(0, n) in DuckDB
2417            "RANGE" if f.args.len() == 1 => {
2418                let mut new_args = vec![Expression::number(0)];
2419                new_args.extend(f.args);
2420                Ok(Expression::Function(Box::new(Function::new(
2421                    "RANGE".to_string(),
2422                    new_args,
2423                ))))
2424            }
2425
2426            // GENERATE_SERIES(n) -> GENERATE_SERIES(0, n) in DuckDB
2427            "GENERATE_SERIES" if f.args.len() == 1 => {
2428                let mut new_args = vec![Expression::number(0)];
2429                new_args.extend(f.args);
2430                Ok(Expression::Function(Box::new(Function::new(
2431                    "GENERATE_SERIES".to_string(),
2432                    new_args,
2433                ))))
2434            }
2435
2436            // REGEXP_EXTRACT(str, pattern, 0) -> REGEXP_EXTRACT(str, pattern) in DuckDB
2437            // Drop the group argument when it's 0 (default)
2438            "REGEXP_EXTRACT" if f.args.len() == 3 => {
2439                // Check if the third argument is 0
2440                let drop_group = match &f.args[2] {
2441                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
2442                        let Literal::Number(n) = lit.as_ref() else {
2443                            unreachable!()
2444                        };
2445                        n == "0"
2446                    }
2447                    _ => false,
2448                };
2449                if drop_group {
2450                    Ok(Expression::Function(Box::new(Function::new(
2451                        "REGEXP_EXTRACT".to_string(),
2452                        vec![f.args[0].clone(), f.args[1].clone()],
2453                    ))))
2454                } else {
2455                    Ok(Expression::Function(Box::new(f)))
2456                }
2457            }
2458
2459            // STRUCT_PACK(a := 1, b := 2) -> {'a': 1, 'b': 2} (DuckDB struct literal)
2460            "STRUCT_PACK" => {
2461                let mut fields = Vec::new();
2462                for arg in f.args {
2463                    match arg {
2464                        Expression::NamedArgument(na) => {
2465                            fields.push((Some(na.name.name.clone()), na.value));
2466                        }
2467                        // Non-named arguments get positional keys
2468                        other => {
2469                            fields.push((None, other));
2470                        }
2471                    }
2472                }
2473                Ok(Expression::Struct(Box::new(Struct { fields })))
2474            }
2475
2476            // REPLACE with 2 args -> add empty string 3rd arg
2477            "REPLACE" if f.args.len() == 2 => {
2478                let mut args = f.args;
2479                args.push(Expression::Literal(Box::new(
2480                    Literal::String(String::new()),
2481                )));
2482                Ok(Expression::Function(Box::new(Function::new(
2483                    "REPLACE".to_string(),
2484                    args,
2485                ))))
2486            }
2487
2488            // TO_UNIXTIME -> EPOCH in DuckDB
2489            "TO_UNIXTIME" => Ok(Expression::Function(Box::new(Function::new(
2490                "EPOCH".to_string(),
2491                f.args,
2492            )))),
2493
2494            // FROM_ISO8601_TIMESTAMP -> CAST(x AS TIMESTAMPTZ) in DuckDB
2495            "FROM_ISO8601_TIMESTAMP" if f.args.len() == 1 => {
2496                use crate::expressions::{Cast, DataType};
2497                Ok(Expression::Cast(Box::new(Cast {
2498                    this: f.args.into_iter().next().unwrap(),
2499                    to: DataType::Timestamp {
2500                        precision: None,
2501                        timezone: true,
2502                    },
2503                    trailing_comments: Vec::new(),
2504                    double_colon_syntax: false,
2505                    format: None,
2506                    default: None,
2507                    inferred_type: None,
2508                })))
2509            }
2510
2511            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT in DuckDB
2512            "APPROX_DISTINCT" => {
2513                // Drop the accuracy parameter (second arg) if present
2514                let args = if f.args.len() > 1 {
2515                    vec![f.args.into_iter().next().unwrap()]
2516                } else {
2517                    f.args
2518                };
2519                Ok(Expression::Function(Box::new(Function::new(
2520                    "APPROX_COUNT_DISTINCT".to_string(),
2521                    args,
2522                ))))
2523            }
2524
2525            // ARRAY_SORT is native to DuckDB (but drop the lambda comparator)
2526            "ARRAY_SORT" => {
2527                let args = vec![f.args.into_iter().next().unwrap()];
2528                Ok(Expression::Function(Box::new(Function::new(
2529                    "ARRAY_SORT".to_string(),
2530                    args,
2531                ))))
2532            }
2533
2534            // TO_UTF8 -> ENCODE in DuckDB
2535            "TO_UTF8" => Ok(Expression::Function(Box::new(Function::new(
2536                "ENCODE".to_string(),
2537                f.args,
2538            )))),
2539
2540            // FROM_UTF8 -> DECODE in DuckDB
2541            "FROM_UTF8" => Ok(Expression::Function(Box::new(Function::new(
2542                "DECODE".to_string(),
2543                f.args,
2544            )))),
2545
2546            // ARBITRARY -> ANY_VALUE in DuckDB
2547            "ARBITRARY" => Ok(Expression::Function(Box::new(Function::new(
2548                "ANY_VALUE".to_string(),
2549                f.args,
2550            )))),
2551
2552            // MAX_BY -> ARG_MAX in DuckDB
2553            "MAX_BY" => Ok(Expression::Function(Box::new(Function::new(
2554                "ARG_MAX".to_string(),
2555                f.args,
2556            )))),
2557
2558            // MIN_BY -> ARG_MIN in DuckDB
2559            "MIN_BY" => Ok(Expression::Function(Box::new(Function::new(
2560                "ARG_MIN".to_string(),
2561                f.args,
2562            )))),
2563
2564            // ===== Snowflake-specific function transforms =====
2565            "IFF" if f.args.len() == 3 => {
2566                let mut args = f.args;
2567                let cond = args.remove(0);
2568                let true_val = args.remove(0);
2569                let false_val = args.remove(0);
2570                Ok(Expression::Case(Box::new(Case {
2571                    operand: None,
2572                    whens: vec![(cond, true_val)],
2573                    else_: Some(false_val),
2574                    comments: Vec::new(),
2575                    inferred_type: None,
2576                })))
2577            }
2578            "SKEW" => Ok(Expression::Function(Box::new(Function::new(
2579                "SKEWNESS".to_string(),
2580                f.args,
2581            )))),
2582            "VAR_SAMP" => Ok(Expression::Function(Box::new(Function::new(
2583                "VARIANCE".to_string(),
2584                f.args,
2585            )))),
2586            "VARIANCE_POP" => Ok(Expression::Function(Box::new(Function::new(
2587                "VAR_POP".to_string(),
2588                f.args,
2589            )))),
2590            "REGR_VALX" if f.args.len() == 2 => {
2591                let mut args = f.args;
2592                let y = args.remove(0);
2593                let x = args.remove(0);
2594                Ok(Expression::Case(Box::new(Case {
2595                    operand: None,
2596                    whens: vec![(
2597                        Expression::IsNull(Box::new(crate::expressions::IsNull {
2598                            this: y,
2599                            not: false,
2600                            postfix_form: false,
2601                        })),
2602                        Expression::Cast(Box::new(Cast {
2603                            this: Expression::Null(crate::expressions::Null),
2604                            to: DataType::Double {
2605                                precision: None,
2606                                scale: None,
2607                            },
2608                            trailing_comments: Vec::new(),
2609                            double_colon_syntax: false,
2610                            format: None,
2611                            default: None,
2612                            inferred_type: None,
2613                        })),
2614                    )],
2615                    else_: Some(x),
2616                    comments: Vec::new(),
2617                    inferred_type: None,
2618                })))
2619            }
2620            "REGR_VALY" if f.args.len() == 2 => {
2621                let mut args = f.args;
2622                let y = args.remove(0);
2623                let x = args.remove(0);
2624                Ok(Expression::Case(Box::new(Case {
2625                    operand: None,
2626                    whens: vec![(
2627                        Expression::IsNull(Box::new(crate::expressions::IsNull {
2628                            this: x,
2629                            not: false,
2630                            postfix_form: false,
2631                        })),
2632                        Expression::Cast(Box::new(Cast {
2633                            this: Expression::Null(crate::expressions::Null),
2634                            to: DataType::Double {
2635                                precision: None,
2636                                scale: None,
2637                            },
2638                            trailing_comments: Vec::new(),
2639                            double_colon_syntax: false,
2640                            format: None,
2641                            default: None,
2642                            inferred_type: None,
2643                        })),
2644                    )],
2645                    else_: Some(y),
2646                    comments: Vec::new(),
2647                    inferred_type: None,
2648                })))
2649            }
2650            "BOOLNOT" if f.args.len() == 1 => {
2651                let arg = f.args.into_iter().next().unwrap();
2652                // BOOLNOT(x) -> NOT (ROUND(x, 0))
2653                let rounded = Expression::Function(Box::new(Function::new(
2654                    "ROUND".to_string(),
2655                    vec![arg, Expression::number(0)],
2656                )));
2657                Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
2658                    this: Expression::Paren(Box::new(Paren {
2659                        this: rounded,
2660                        trailing_comments: Vec::new(),
2661                    })),
2662                    inferred_type: None,
2663                })))
2664            }
2665            "BITMAP_BIT_POSITION" if f.args.len() == 1 => {
2666                let n = f.args.into_iter().next().unwrap();
2667                let case_expr = Expression::Case(Box::new(Case {
2668                    operand: None,
2669                    whens: vec![(
2670                        Expression::Gt(Box::new(BinaryOp {
2671                            left: n.clone(),
2672                            right: Expression::number(0),
2673                            left_comments: Vec::new(),
2674                            operator_comments: Vec::new(),
2675                            trailing_comments: Vec::new(),
2676                            inferred_type: None,
2677                        })),
2678                        Expression::Sub(Box::new(BinaryOp {
2679                            left: n.clone(),
2680                            right: Expression::number(1),
2681                            left_comments: Vec::new(),
2682                            operator_comments: Vec::new(),
2683                            trailing_comments: Vec::new(),
2684                            inferred_type: None,
2685                        })),
2686                    )],
2687                    else_: Some(Expression::Abs(Box::new(UnaryFunc {
2688                        this: n,
2689                        original_name: None,
2690                        inferred_type: None,
2691                    }))),
2692                    comments: Vec::new(),
2693                    inferred_type: None,
2694                }));
2695                Ok(Expression::Mod(Box::new(BinaryOp {
2696                    left: Expression::Paren(Box::new(Paren {
2697                        this: case_expr,
2698                        trailing_comments: Vec::new(),
2699                    })),
2700                    right: Expression::number(32768),
2701                    left_comments: Vec::new(),
2702                    operator_comments: Vec::new(),
2703                    trailing_comments: Vec::new(),
2704                    inferred_type: None,
2705                })))
2706            }
2707            // GREATEST/LEAST - pass through (null-wrapping is handled by source dialect transforms)
2708            "GREATEST" | "LEAST" => Ok(Expression::Function(Box::new(f))),
2709            "GREATEST_IGNORE_NULLS" => Ok(Expression::Greatest(Box::new(VarArgFunc {
2710                expressions: f.args,
2711                original_name: None,
2712                inferred_type: None,
2713            }))),
2714            "LEAST_IGNORE_NULLS" => Ok(Expression::Least(Box::new(VarArgFunc {
2715                expressions: f.args,
2716                original_name: None,
2717                inferred_type: None,
2718            }))),
2719            "PARSE_JSON" => Ok(Expression::Function(Box::new(Function::new(
2720                "JSON".to_string(),
2721                f.args,
2722            )))),
2723            // TRY_PARSE_JSON(x) -> CASE WHEN JSON_VALID(x) THEN CAST(x AS JSON) ELSE NULL END
2724            "TRY_PARSE_JSON" if f.args.len() == 1 => {
2725                let x = f.args.into_iter().next().unwrap();
2726                let json_valid = Expression::Function(Box::new(Function::new(
2727                    "JSON_VALID".to_string(),
2728                    vec![x.clone()],
2729                )));
2730                let cast_json = Expression::Cast(Box::new(crate::expressions::Cast {
2731                    this: x,
2732                    to: DataType::Json,
2733                    double_colon_syntax: false,
2734                    trailing_comments: Vec::new(),
2735                    format: None,
2736                    default: None,
2737                    inferred_type: None,
2738                }));
2739                Ok(Expression::Case(Box::new(crate::expressions::Case {
2740                    operand: None,
2741                    whens: vec![(json_valid, cast_json)],
2742                    else_: Some(Expression::Null(crate::expressions::Null)),
2743                    comments: Vec::new(),
2744                    inferred_type: None,
2745                })))
2746            }
2747            "OBJECT_CONSTRUCT_KEEP_NULL" => {
2748                // OBJECT_CONSTRUCT_KEEP_NULL -> JSON_OBJECT (preserves NULLs)
2749                Ok(Expression::Function(Box::new(Function::new(
2750                    "JSON_OBJECT".to_string(),
2751                    f.args,
2752                ))))
2753            }
2754            "OBJECT_CONSTRUCT" => {
2755                // Convert to DuckDB struct literal: {'key1': val1, 'key2': val2}
2756                let args = f.args;
2757                if args.is_empty() {
2758                    // Empty OBJECT_CONSTRUCT() -> STRUCT_PACK() (no args)
2759                    Ok(Expression::Function(Box::new(Function::new(
2760                        "STRUCT_PACK".to_string(),
2761                        vec![],
2762                    ))))
2763                } else {
2764                    // Build struct literal from key-value pairs
2765                    let mut fields = Vec::new();
2766                    let mut i = 0;
2767                    while i + 1 < args.len() {
2768                        let key = &args[i];
2769                        let value = args[i + 1].clone();
2770                        let key_name = match key {
2771                            Expression::Literal(lit)
2772                                if matches!(lit.as_ref(), Literal::String(_)) =>
2773                            {
2774                                let Literal::String(s) = lit.as_ref() else {
2775                                    unreachable!()
2776                                };
2777                                Some(s.clone())
2778                            }
2779                            _ => None,
2780                        };
2781                        fields.push((key_name, value));
2782                        i += 2;
2783                    }
2784                    Ok(Expression::Struct(Box::new(Struct { fields })))
2785                }
2786            }
2787            "IS_NULL_VALUE" if f.args.len() == 1 => {
2788                let arg = f.args.into_iter().next().unwrap();
2789                Ok(Expression::Eq(Box::new(BinaryOp {
2790                    left: Expression::Function(Box::new(Function::new(
2791                        "JSON_TYPE".to_string(),
2792                        vec![arg],
2793                    ))),
2794                    right: Expression::Literal(Box::new(Literal::String("NULL".to_string()))),
2795                    left_comments: Vec::new(),
2796                    operator_comments: Vec::new(),
2797                    trailing_comments: Vec::new(),
2798                    inferred_type: None,
2799                })))
2800            }
2801            "TRY_TO_DOUBLE" | "TRY_TO_NUMBER" | "TRY_TO_NUMERIC" | "TRY_TO_DECIMAL"
2802                if f.args.len() == 1 =>
2803            {
2804                let arg = f.args.into_iter().next().unwrap();
2805                Ok(Expression::TryCast(Box::new(Cast {
2806                    this: arg,
2807                    to: DataType::Double {
2808                        precision: None,
2809                        scale: None,
2810                    },
2811                    trailing_comments: Vec::new(),
2812                    double_colon_syntax: false,
2813                    format: None,
2814                    default: None,
2815                    inferred_type: None,
2816                })))
2817            }
2818            "TRY_TO_TIME" if f.args.len() == 1 => {
2819                let arg = f.args.into_iter().next().unwrap();
2820                Ok(Expression::TryCast(Box::new(Cast {
2821                    this: arg,
2822                    to: DataType::Time {
2823                        precision: None,
2824                        timezone: false,
2825                    },
2826                    trailing_comments: Vec::new(),
2827                    double_colon_syntax: false,
2828                    format: None,
2829                    default: None,
2830                    inferred_type: None,
2831                })))
2832            }
2833            "TRY_TO_TIME" if f.args.len() == 2 => {
2834                let mut args = f.args;
2835                let value = args.remove(0);
2836                let fmt = self.convert_snowflake_time_format(args.remove(0));
2837                Ok(Expression::TryCast(Box::new(Cast {
2838                    this: Expression::Function(Box::new(Function::new(
2839                        "TRY_STRPTIME".to_string(),
2840                        vec![value, fmt],
2841                    ))),
2842                    to: DataType::Time {
2843                        precision: None,
2844                        timezone: false,
2845                    },
2846                    trailing_comments: Vec::new(),
2847                    double_colon_syntax: false,
2848                    format: None,
2849                    default: None,
2850                    inferred_type: None,
2851                })))
2852            }
2853            "TRY_TO_TIMESTAMP" if f.args.len() == 1 => {
2854                let arg = f.args.into_iter().next().unwrap();
2855                Ok(Expression::TryCast(Box::new(Cast {
2856                    this: arg,
2857                    to: DataType::Timestamp {
2858                        precision: None,
2859                        timezone: false,
2860                    },
2861                    trailing_comments: Vec::new(),
2862                    double_colon_syntax: false,
2863                    format: None,
2864                    default: None,
2865                    inferred_type: None,
2866                })))
2867            }
2868            "TRY_TO_TIMESTAMP" if f.args.len() == 2 => {
2869                let mut args = f.args;
2870                let value = args.remove(0);
2871                let fmt = self.convert_snowflake_time_format(args.remove(0));
2872                Ok(Expression::Cast(Box::new(Cast {
2873                    this: Expression::Function(Box::new(Function::new(
2874                        "TRY_STRPTIME".to_string(),
2875                        vec![value, fmt],
2876                    ))),
2877                    to: DataType::Timestamp {
2878                        precision: None,
2879                        timezone: false,
2880                    },
2881                    trailing_comments: Vec::new(),
2882                    double_colon_syntax: false,
2883                    format: None,
2884                    default: None,
2885                    inferred_type: None,
2886                })))
2887            }
2888            "TRY_TO_DATE" if f.args.len() == 1 => {
2889                let arg = f.args.into_iter().next().unwrap();
2890                Ok(Expression::TryCast(Box::new(Cast {
2891                    this: arg,
2892                    to: DataType::Date,
2893                    trailing_comments: Vec::new(),
2894                    double_colon_syntax: false,
2895                    format: None,
2896                    default: None,
2897                    inferred_type: None,
2898                })))
2899            }
2900            "DAYOFWEEKISO" | "DAYOFWEEK_ISO" => Ok(Expression::Function(Box::new(Function::new(
2901                "ISODOW".to_string(),
2902                f.args,
2903            )))),
2904            "YEAROFWEEK" | "YEAROFWEEKISO" if f.args.len() == 1 => {
2905                let arg = f.args.into_iter().next().unwrap();
2906                Ok(Expression::Extract(Box::new(
2907                    crate::expressions::ExtractFunc {
2908                        this: arg,
2909                        field: crate::expressions::DateTimeField::Custom("ISOYEAR".to_string()),
2910                    },
2911                )))
2912            }
2913            "WEEKISO" => Ok(Expression::Function(Box::new(Function::new(
2914                "WEEKOFYEAR".to_string(),
2915                f.args,
2916            )))),
2917            "TIME_FROM_PARTS" | "TIMEFROMPARTS" if f.args.len() == 3 => {
2918                let args_ref = &f.args;
2919                // Check if all args are in-range literals: h < 24, m < 60, s < 60
2920                let all_in_range = if let (Some(h_val), Some(m_val), Some(s_val)) = (
2921                    Self::extract_number_value(&args_ref[0]),
2922                    Self::extract_number_value(&args_ref[1]),
2923                    Self::extract_number_value(&args_ref[2]),
2924                ) {
2925                    h_val >= 0.0
2926                        && h_val < 24.0
2927                        && m_val >= 0.0
2928                        && m_val < 60.0
2929                        && s_val >= 0.0
2930                        && s_val < 60.0
2931                } else {
2932                    false
2933                };
2934                if all_in_range {
2935                    // Use MAKE_TIME for normal values
2936                    Ok(Expression::Function(Box::new(Function::new(
2937                        "MAKE_TIME".to_string(),
2938                        f.args,
2939                    ))))
2940                } else {
2941                    // TIME_FROM_PARTS(h, m, s) -> CAST('00:00:00' AS TIME) + INTERVAL ((h * 3600) + (m * 60) + s) SECOND
2942                    // Use arithmetic approach to handle out-of-range values (e.g., 100 minutes)
2943                    let mut args = f.args;
2944                    let h = args.remove(0);
2945                    let m = args.remove(0);
2946                    let s = args.remove(0);
2947                    let seconds_expr = Expression::Add(Box::new(BinaryOp {
2948                        left: Expression::Add(Box::new(BinaryOp {
2949                            left: Expression::Paren(Box::new(Paren {
2950                                this: Expression::Mul(Box::new(BinaryOp {
2951                                    left: h,
2952                                    right: Expression::number(3600),
2953                                    left_comments: Vec::new(),
2954                                    operator_comments: Vec::new(),
2955                                    trailing_comments: Vec::new(),
2956                                    inferred_type: None,
2957                                })),
2958                                trailing_comments: Vec::new(),
2959                            })),
2960                            right: Expression::Paren(Box::new(Paren {
2961                                this: Expression::Mul(Box::new(BinaryOp {
2962                                    left: m,
2963                                    right: Expression::number(60),
2964                                    left_comments: Vec::new(),
2965                                    operator_comments: Vec::new(),
2966                                    trailing_comments: Vec::new(),
2967                                    inferred_type: None,
2968                                })),
2969                                trailing_comments: Vec::new(),
2970                            })),
2971                            left_comments: Vec::new(),
2972                            operator_comments: Vec::new(),
2973                            trailing_comments: Vec::new(),
2974                            inferred_type: None,
2975                        })),
2976                        right: s,
2977                        left_comments: Vec::new(),
2978                        operator_comments: Vec::new(),
2979                        trailing_comments: Vec::new(),
2980                        inferred_type: None,
2981                    }));
2982                    let base_time = Expression::Cast(Box::new(Cast {
2983                        this: Expression::Literal(Box::new(Literal::String(
2984                            "00:00:00".to_string(),
2985                        ))),
2986                        to: DataType::Time {
2987                            precision: None,
2988                            timezone: false,
2989                        },
2990                        trailing_comments: Vec::new(),
2991                        double_colon_syntax: false,
2992                        format: None,
2993                        default: None,
2994                        inferred_type: None,
2995                    }));
2996                    Ok(Expression::Add(Box::new(BinaryOp {
2997                        left: base_time,
2998                        right: Expression::Interval(Box::new(Interval {
2999                            this: Some(Expression::Paren(Box::new(crate::expressions::Paren {
3000                                this: seconds_expr,
3001                                trailing_comments: Vec::new(),
3002                            }))),
3003                            unit: Some(IntervalUnitSpec::Simple {
3004                                unit: IntervalUnit::Second,
3005                                use_plural: false,
3006                            }),
3007                        })),
3008                        left_comments: Vec::new(),
3009                        operator_comments: Vec::new(),
3010                        trailing_comments: Vec::new(),
3011                        inferred_type: None,
3012                    })))
3013                }
3014            }
3015            "TIME_FROM_PARTS" | "TIMEFROMPARTS" if f.args.len() == 4 => {
3016                let mut args = f.args;
3017                let h = args.remove(0);
3018                let m = args.remove(0);
3019                let s = args.remove(0);
3020                let ns = args.remove(0);
3021                let seconds_expr = Expression::Add(Box::new(BinaryOp {
3022                    left: Expression::Add(Box::new(BinaryOp {
3023                        left: Expression::Add(Box::new(BinaryOp {
3024                            left: Expression::Paren(Box::new(Paren {
3025                                this: Expression::Mul(Box::new(BinaryOp {
3026                                    left: h,
3027                                    right: Expression::number(3600),
3028                                    left_comments: Vec::new(),
3029                                    operator_comments: Vec::new(),
3030                                    trailing_comments: Vec::new(),
3031                                    inferred_type: None,
3032                                })),
3033                                trailing_comments: Vec::new(),
3034                            })),
3035                            right: Expression::Paren(Box::new(Paren {
3036                                this: Expression::Mul(Box::new(BinaryOp {
3037                                    left: m,
3038                                    right: Expression::number(60),
3039                                    left_comments: Vec::new(),
3040                                    operator_comments: Vec::new(),
3041                                    trailing_comments: Vec::new(),
3042                                    inferred_type: None,
3043                                })),
3044                                trailing_comments: Vec::new(),
3045                            })),
3046                            left_comments: Vec::new(),
3047                            operator_comments: Vec::new(),
3048                            trailing_comments: Vec::new(),
3049                            inferred_type: None,
3050                        })),
3051                        right: s,
3052                        left_comments: Vec::new(),
3053                        operator_comments: Vec::new(),
3054                        trailing_comments: Vec::new(),
3055                        inferred_type: None,
3056                    })),
3057                    right: Expression::Paren(Box::new(Paren {
3058                        this: Expression::Div(Box::new(BinaryOp {
3059                            left: ns,
3060                            right: Expression::Literal(Box::new(Literal::Number(
3061                                "1000000000.0".to_string(),
3062                            ))),
3063                            left_comments: Vec::new(),
3064                            operator_comments: Vec::new(),
3065                            trailing_comments: Vec::new(),
3066                            inferred_type: None,
3067                        })),
3068                        trailing_comments: Vec::new(),
3069                    })),
3070                    left_comments: Vec::new(),
3071                    operator_comments: Vec::new(),
3072                    trailing_comments: Vec::new(),
3073                    inferred_type: None,
3074                }));
3075                let base_time = Expression::Cast(Box::new(Cast {
3076                    this: Expression::Literal(Box::new(Literal::String("00:00:00".to_string()))),
3077                    to: DataType::Time {
3078                        precision: None,
3079                        timezone: false,
3080                    },
3081                    trailing_comments: Vec::new(),
3082                    double_colon_syntax: false,
3083                    format: None,
3084                    default: None,
3085                    inferred_type: None,
3086                }));
3087                Ok(Expression::Add(Box::new(BinaryOp {
3088                    left: base_time,
3089                    right: Expression::Interval(Box::new(Interval {
3090                        this: Some(Expression::Paren(Box::new(crate::expressions::Paren {
3091                            this: seconds_expr,
3092                            trailing_comments: Vec::new(),
3093                        }))),
3094                        unit: Some(IntervalUnitSpec::Simple {
3095                            unit: IntervalUnit::Second,
3096                            use_plural: false,
3097                        }),
3098                    })),
3099                    left_comments: Vec::new(),
3100                    operator_comments: Vec::new(),
3101                    trailing_comments: Vec::new(),
3102                    inferred_type: None,
3103                })))
3104            }
3105            "TIMESTAMP_FROM_PARTS" | "TIMESTAMPFROMPARTS" if f.args.len() == 6 => {
3106                Ok(Expression::Function(Box::new(Function::new(
3107                    "MAKE_TIMESTAMP".to_string(),
3108                    f.args,
3109                ))))
3110            }
3111            "TIMESTAMP_FROM_PARTS" | "TIMESTAMPFROMPARTS" | "TIMESTAMP_NTZ_FROM_PARTS"
3112                if f.args.len() == 2 =>
3113            {
3114                let mut args = f.args;
3115                let d = args.remove(0);
3116                let t = args.remove(0);
3117                Ok(Expression::Add(Box::new(BinaryOp {
3118                    left: d,
3119                    right: t,
3120                    left_comments: Vec::new(),
3121                    operator_comments: Vec::new(),
3122                    trailing_comments: Vec::new(),
3123                    inferred_type: None,
3124                })))
3125            }
3126            "TIMESTAMP_LTZ_FROM_PARTS" if f.args.len() == 6 => {
3127                Ok(Expression::Cast(Box::new(Cast {
3128                    this: Expression::Function(Box::new(Function::new(
3129                        "MAKE_TIMESTAMP".to_string(),
3130                        f.args,
3131                    ))),
3132                    to: DataType::Timestamp {
3133                        precision: None,
3134                        timezone: true,
3135                    },
3136                    trailing_comments: Vec::new(),
3137                    double_colon_syntax: false,
3138                    format: None,
3139                    default: None,
3140                    inferred_type: None,
3141                })))
3142            }
3143            "TIMESTAMP_TZ_FROM_PARTS" if f.args.len() == 8 => {
3144                let mut args = f.args;
3145                let ts_args = vec![
3146                    args.remove(0),
3147                    args.remove(0),
3148                    args.remove(0),
3149                    args.remove(0),
3150                    args.remove(0),
3151                    args.remove(0),
3152                ];
3153                let _nano = args.remove(0);
3154                let tz = args.remove(0);
3155                Ok(Expression::AtTimeZone(Box::new(
3156                    crate::expressions::AtTimeZone {
3157                        this: Expression::Function(Box::new(Function::new(
3158                            "MAKE_TIMESTAMP".to_string(),
3159                            ts_args,
3160                        ))),
3161                        zone: tz,
3162                    },
3163                )))
3164            }
3165            "BOOLAND_AGG" if f.args.len() == 1 => {
3166                let arg = f.args.into_iter().next().unwrap();
3167                Ok(Expression::Function(Box::new(Function::new(
3168                    "BOOL_AND".to_string(),
3169                    vec![Expression::Cast(Box::new(Cast {
3170                        this: arg,
3171                        to: DataType::Boolean,
3172                        trailing_comments: Vec::new(),
3173                        double_colon_syntax: false,
3174                        format: None,
3175                        default: None,
3176                        inferred_type: None,
3177                    }))],
3178                ))))
3179            }
3180            "BOOLOR_AGG" if f.args.len() == 1 => {
3181                let arg = f.args.into_iter().next().unwrap();
3182                Ok(Expression::Function(Box::new(Function::new(
3183                    "BOOL_OR".to_string(),
3184                    vec![Expression::Cast(Box::new(Cast {
3185                        this: arg,
3186                        to: DataType::Boolean,
3187                        trailing_comments: Vec::new(),
3188                        double_colon_syntax: false,
3189                        format: None,
3190                        default: None,
3191                        inferred_type: None,
3192                    }))],
3193                ))))
3194            }
3195            "NVL2" if f.args.len() == 3 => {
3196                let mut args = f.args;
3197                let a = args.remove(0);
3198                let b = args.remove(0);
3199                let c = args.remove(0);
3200                Ok(Expression::Case(Box::new(Case {
3201                    operand: None,
3202                    whens: vec![(
3203                        Expression::Not(Box::new(crate::expressions::UnaryOp {
3204                            this: Expression::IsNull(Box::new(crate::expressions::IsNull {
3205                                this: a,
3206                                not: false,
3207                                postfix_form: false,
3208                            })),
3209                            inferred_type: None,
3210                        })),
3211                        b,
3212                    )],
3213                    else_: Some(c),
3214                    comments: Vec::new(),
3215                    inferred_type: None,
3216                })))
3217            }
3218            "EQUAL_NULL" if f.args.len() == 2 => {
3219                let mut args = f.args;
3220                let a = args.remove(0);
3221                let b = args.remove(0);
3222                Ok(Expression::NullSafeEq(Box::new(BinaryOp {
3223                    left: a,
3224                    right: b,
3225                    left_comments: Vec::new(),
3226                    operator_comments: Vec::new(),
3227                    trailing_comments: Vec::new(),
3228                    inferred_type: None,
3229                })))
3230            }
3231            "EDITDISTANCE" if f.args.len() == 3 => {
3232                // EDITDISTANCE(a, b, max) -> CASE WHEN LEVENSHTEIN(a, b) IS NULL OR max IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(a, b), max) END
3233                let mut args = f.args;
3234                let a = args.remove(0);
3235                let b = args.remove(0);
3236                let max_dist = args.remove(0);
3237                let lev = Expression::Function(Box::new(Function::new(
3238                    "LEVENSHTEIN".to_string(),
3239                    vec![a, b],
3240                )));
3241                let lev_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
3242                    this: lev.clone(),
3243                    not: false,
3244                    postfix_form: false,
3245                }));
3246                let max_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
3247                    this: max_dist.clone(),
3248                    not: false,
3249                    postfix_form: false,
3250                }));
3251                let null_check = Expression::Or(Box::new(BinaryOp {
3252                    left: lev_is_null,
3253                    right: max_is_null,
3254                    left_comments: Vec::new(),
3255                    operator_comments: Vec::new(),
3256                    trailing_comments: Vec::new(),
3257                    inferred_type: None,
3258                }));
3259                let least = Expression::Least(Box::new(VarArgFunc {
3260                    expressions: vec![lev, max_dist],
3261                    original_name: None,
3262                    inferred_type: None,
3263                }));
3264                Ok(Expression::Case(Box::new(Case {
3265                    operand: None,
3266                    whens: vec![(null_check, Expression::Null(crate::expressions::Null))],
3267                    else_: Some(least),
3268                    comments: Vec::new(),
3269                    inferred_type: None,
3270                })))
3271            }
3272            "EDITDISTANCE" => Ok(Expression::Function(Box::new(Function::new(
3273                "LEVENSHTEIN".to_string(),
3274                f.args,
3275            )))),
3276            "BITAND" if f.args.len() == 2 => {
3277                let mut args = f.args;
3278                let left = args.remove(0);
3279                let right = args.remove(0);
3280                // Wrap shift expressions in parentheses for correct precedence
3281                let wrap = |e: Expression| -> Expression {
3282                    match &e {
3283                        Expression::BitwiseLeftShift(_) | Expression::BitwiseRightShift(_) => {
3284                            Expression::Paren(Box::new(Paren {
3285                                this: e,
3286                                trailing_comments: Vec::new(),
3287                            }))
3288                        }
3289                        _ => e,
3290                    }
3291                };
3292                Ok(Expression::BitwiseAnd(Box::new(BinaryOp {
3293                    left: wrap(left),
3294                    right: wrap(right),
3295                    left_comments: Vec::new(),
3296                    operator_comments: Vec::new(),
3297                    trailing_comments: Vec::new(),
3298                    inferred_type: None,
3299                })))
3300            }
3301            "BITOR" if f.args.len() == 2 => {
3302                let mut args = f.args;
3303                let left = args.remove(0);
3304                let right = args.remove(0);
3305                // Wrap shift expressions in parentheses for correct precedence
3306                let wrap = |e: Expression| -> Expression {
3307                    match &e {
3308                        Expression::BitwiseLeftShift(_) | Expression::BitwiseRightShift(_) => {
3309                            Expression::Paren(Box::new(Paren {
3310                                this: e,
3311                                trailing_comments: Vec::new(),
3312                            }))
3313                        }
3314                        _ => e,
3315                    }
3316                };
3317                Ok(Expression::BitwiseOr(Box::new(BinaryOp {
3318                    left: wrap(left),
3319                    right: wrap(right),
3320                    left_comments: Vec::new(),
3321                    operator_comments: Vec::new(),
3322                    trailing_comments: Vec::new(),
3323                    inferred_type: None,
3324                })))
3325            }
3326            "BITXOR" if f.args.len() == 2 => {
3327                let mut args = f.args;
3328                Ok(Expression::BitwiseXor(Box::new(BinaryOp {
3329                    left: args.remove(0),
3330                    right: args.remove(0),
3331                    left_comments: Vec::new(),
3332                    operator_comments: Vec::new(),
3333                    trailing_comments: Vec::new(),
3334                    inferred_type: None,
3335                })))
3336            }
3337            "BITNOT" if f.args.len() == 1 => {
3338                let arg = f.args.into_iter().next().unwrap();
3339                Ok(Expression::BitwiseNot(Box::new(
3340                    crate::expressions::UnaryOp {
3341                        this: Expression::Paren(Box::new(Paren {
3342                            this: arg,
3343                            trailing_comments: Vec::new(),
3344                        })),
3345                        inferred_type: None,
3346                    },
3347                )))
3348            }
3349            "BITSHIFTLEFT" if f.args.len() == 2 => {
3350                let mut args = f.args;
3351                let a = args.remove(0);
3352                let b = args.remove(0);
3353                // Check if first arg is BINARY/BLOB type (e.g., X'002A'::BINARY)
3354                let is_binary = if let Expression::Cast(ref c) = a {
3355                    matches!(
3356                        &c.to,
3357                        DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob
3358                    ) || matches!(&c.to, DataType::Custom { name } if name == "BLOB")
3359                } else {
3360                    false
3361                };
3362                if is_binary {
3363                    // CAST(CAST(a AS BIT) << b AS BLOB)
3364                    let cast_to_bit = Expression::Cast(Box::new(Cast {
3365                        this: a,
3366                        to: DataType::Custom {
3367                            name: "BIT".to_string(),
3368                        },
3369                        trailing_comments: Vec::new(),
3370                        double_colon_syntax: false,
3371                        format: None,
3372                        default: None,
3373                        inferred_type: None,
3374                    }));
3375                    let shift = Expression::BitwiseLeftShift(Box::new(BinaryOp {
3376                        left: cast_to_bit,
3377                        right: b,
3378                        left_comments: Vec::new(),
3379                        operator_comments: Vec::new(),
3380                        trailing_comments: Vec::new(),
3381                        inferred_type: None,
3382                    }));
3383                    Ok(Expression::Cast(Box::new(Cast {
3384                        this: shift,
3385                        to: DataType::Custom {
3386                            name: "BLOB".to_string(),
3387                        },
3388                        trailing_comments: Vec::new(),
3389                        double_colon_syntax: false,
3390                        format: None,
3391                        default: None,
3392                        inferred_type: None,
3393                    })))
3394                } else {
3395                    Ok(Expression::BitwiseLeftShift(Box::new(BinaryOp {
3396                        left: Expression::Cast(Box::new(Cast {
3397                            this: a,
3398                            to: DataType::Int128,
3399                            trailing_comments: Vec::new(),
3400                            double_colon_syntax: false,
3401                            format: None,
3402                            default: None,
3403                            inferred_type: None,
3404                        })),
3405                        right: b,
3406                        left_comments: Vec::new(),
3407                        operator_comments: Vec::new(),
3408                        trailing_comments: Vec::new(),
3409                        inferred_type: None,
3410                    })))
3411                }
3412            }
3413            "BITSHIFTRIGHT" if f.args.len() == 2 => {
3414                let mut args = f.args;
3415                let a = args.remove(0);
3416                let b = args.remove(0);
3417                // Check if first arg is BINARY/BLOB type (e.g., X'002A'::BINARY)
3418                let is_binary = if let Expression::Cast(ref c) = a {
3419                    matches!(
3420                        &c.to,
3421                        DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob
3422                    ) || matches!(&c.to, DataType::Custom { name } if name == "BLOB")
3423                } else {
3424                    false
3425                };
3426                if is_binary {
3427                    // CAST(CAST(a AS BIT) >> b AS BLOB)
3428                    let cast_to_bit = Expression::Cast(Box::new(Cast {
3429                        this: a,
3430                        to: DataType::Custom {
3431                            name: "BIT".to_string(),
3432                        },
3433                        trailing_comments: Vec::new(),
3434                        double_colon_syntax: false,
3435                        format: None,
3436                        default: None,
3437                        inferred_type: None,
3438                    }));
3439                    let shift = Expression::BitwiseRightShift(Box::new(BinaryOp {
3440                        left: cast_to_bit,
3441                        right: b,
3442                        left_comments: Vec::new(),
3443                        operator_comments: Vec::new(),
3444                        trailing_comments: Vec::new(),
3445                        inferred_type: None,
3446                    }));
3447                    Ok(Expression::Cast(Box::new(Cast {
3448                        this: shift,
3449                        to: DataType::Custom {
3450                            name: "BLOB".to_string(),
3451                        },
3452                        trailing_comments: Vec::new(),
3453                        double_colon_syntax: false,
3454                        format: None,
3455                        default: None,
3456                        inferred_type: None,
3457                    })))
3458                } else {
3459                    Ok(Expression::BitwiseRightShift(Box::new(BinaryOp {
3460                        left: Expression::Cast(Box::new(Cast {
3461                            this: a,
3462                            to: DataType::Int128,
3463                            trailing_comments: Vec::new(),
3464                            double_colon_syntax: false,
3465                            format: None,
3466                            default: None,
3467                            inferred_type: None,
3468                        })),
3469                        right: b,
3470                        left_comments: Vec::new(),
3471                        operator_comments: Vec::new(),
3472                        trailing_comments: Vec::new(),
3473                        inferred_type: None,
3474                    })))
3475                }
3476            }
3477            "SQUARE" if f.args.len() == 1 => {
3478                let arg = f.args.into_iter().next().unwrap();
3479                Ok(Expression::Function(Box::new(Function::new(
3480                    "POWER".to_string(),
3481                    vec![arg, Expression::number(2)],
3482                ))))
3483            }
3484            "LIST"
3485                if f.args.len() == 1 && !matches!(f.args.first(), Some(Expression::Select(_))) =>
3486            {
3487                Ok(Expression::Function(Box::new(Function::new(
3488                    "ARRAY_AGG".to_string(),
3489                    f.args,
3490                ))))
3491            }
3492            "UUID_STRING" => {
3493                if f.args.is_empty() {
3494                    Ok(Expression::Function(Box::new(Function::new(
3495                        "UUID".to_string(),
3496                        vec![],
3497                    ))))
3498                } else {
3499                    Ok(Expression::Function(Box::new(Function::new(
3500                        "UUID_STRING".to_string(),
3501                        f.args,
3502                    ))))
3503                }
3504            }
3505            "ENDSWITH" => Ok(Expression::Function(Box::new(Function::new(
3506                "ENDS_WITH".to_string(),
3507                f.args,
3508            )))),
3509            // REGEXP_REPLACE: 'g' flag is handled by cross_dialect_normalize for source dialects
3510            // that default to global replacement (e.g., Snowflake). DuckDB defaults to first-match,
3511            // so no 'g' flag needed for DuckDB identity or PostgreSQL->DuckDB.
3512            "REGEXP_REPLACE" if f.args.len() == 2 => {
3513                // 2-arg form (subject, pattern) -> add empty replacement
3514                let mut args = f.args;
3515                args.push(Expression::Literal(Box::new(
3516                    Literal::String(String::new()),
3517                )));
3518                Ok(Expression::Function(Box::new(Function::new(
3519                    "REGEXP_REPLACE".to_string(),
3520                    args,
3521                ))))
3522            }
3523            "DIV0" if f.args.len() == 2 => {
3524                let mut args = f.args;
3525                let a = args.remove(0);
3526                let b = args.remove(0);
3527                Ok(Expression::Case(Box::new(Case {
3528                    operand: None,
3529                    whens: vec![(
3530                        Expression::And(Box::new(BinaryOp {
3531                            left: Expression::Eq(Box::new(BinaryOp {
3532                                left: b.clone(),
3533                                right: Expression::number(0),
3534                                left_comments: Vec::new(),
3535                                operator_comments: Vec::new(),
3536                                trailing_comments: Vec::new(),
3537                                inferred_type: None,
3538                            })),
3539                            right: Expression::Not(Box::new(crate::expressions::UnaryOp {
3540                                this: Expression::IsNull(Box::new(crate::expressions::IsNull {
3541                                    this: a.clone(),
3542                                    not: false,
3543                                    postfix_form: false,
3544                                })),
3545                                inferred_type: None,
3546                            })),
3547                            left_comments: Vec::new(),
3548                            operator_comments: Vec::new(),
3549                            trailing_comments: Vec::new(),
3550                            inferred_type: None,
3551                        })),
3552                        Expression::number(0),
3553                    )],
3554                    else_: Some(Expression::Div(Box::new(BinaryOp {
3555                        left: a,
3556                        right: b,
3557                        left_comments: Vec::new(),
3558                        operator_comments: Vec::new(),
3559                        trailing_comments: Vec::new(),
3560                        inferred_type: None,
3561                    }))),
3562                    comments: Vec::new(),
3563                    inferred_type: None,
3564                })))
3565            }
3566            "DIV0NULL" if f.args.len() == 2 => {
3567                let mut args = f.args;
3568                let a = args.remove(0);
3569                let b = args.remove(0);
3570                Ok(Expression::Case(Box::new(Case {
3571                    operand: None,
3572                    whens: vec![(
3573                        Expression::Or(Box::new(BinaryOp {
3574                            left: Expression::Eq(Box::new(BinaryOp {
3575                                left: b.clone(),
3576                                right: Expression::number(0),
3577                                left_comments: Vec::new(),
3578                                operator_comments: Vec::new(),
3579                                trailing_comments: Vec::new(),
3580                                inferred_type: None,
3581                            })),
3582                            right: Expression::IsNull(Box::new(crate::expressions::IsNull {
3583                                this: b.clone(),
3584                                not: false,
3585                                postfix_form: false,
3586                            })),
3587                            left_comments: Vec::new(),
3588                            operator_comments: Vec::new(),
3589                            trailing_comments: Vec::new(),
3590                            inferred_type: None,
3591                        })),
3592                        Expression::number(0),
3593                    )],
3594                    else_: Some(Expression::Div(Box::new(BinaryOp {
3595                        left: a,
3596                        right: b,
3597                        left_comments: Vec::new(),
3598                        operator_comments: Vec::new(),
3599                        trailing_comments: Vec::new(),
3600                        inferred_type: None,
3601                    }))),
3602                    comments: Vec::new(),
3603                    inferred_type: None,
3604                })))
3605            }
3606            "ZEROIFNULL" if f.args.len() == 1 => {
3607                let x = f.args.into_iter().next().unwrap();
3608                Ok(Expression::Case(Box::new(Case {
3609                    operand: None,
3610                    whens: vec![(
3611                        Expression::IsNull(Box::new(crate::expressions::IsNull {
3612                            this: x.clone(),
3613                            not: false,
3614                            postfix_form: false,
3615                        })),
3616                        Expression::number(0),
3617                    )],
3618                    else_: Some(x),
3619                    comments: Vec::new(),
3620                    inferred_type: None,
3621                })))
3622            }
3623            "NULLIFZERO" if f.args.len() == 1 => {
3624                let x = f.args.into_iter().next().unwrap();
3625                Ok(Expression::Case(Box::new(Case {
3626                    operand: None,
3627                    whens: vec![(
3628                        Expression::Eq(Box::new(BinaryOp {
3629                            left: x.clone(),
3630                            right: Expression::number(0),
3631                            left_comments: Vec::new(),
3632                            operator_comments: Vec::new(),
3633                            trailing_comments: Vec::new(),
3634                            inferred_type: None,
3635                        })),
3636                        Expression::Null(crate::expressions::Null),
3637                    )],
3638                    else_: Some(x),
3639                    comments: Vec::new(),
3640                    inferred_type: None,
3641                })))
3642            }
3643            "TO_DOUBLE" if f.args.len() == 1 => {
3644                let arg = f.args.into_iter().next().unwrap();
3645                Ok(Expression::Cast(Box::new(Cast {
3646                    this: arg,
3647                    to: DataType::Double {
3648                        precision: None,
3649                        scale: None,
3650                    },
3651                    trailing_comments: Vec::new(),
3652                    double_colon_syntax: false,
3653                    format: None,
3654                    default: None,
3655                    inferred_type: None,
3656                })))
3657            }
3658            "DATE" if f.args.len() == 1 => {
3659                let arg = f.args.into_iter().next().unwrap();
3660                Ok(Expression::Cast(Box::new(Cast {
3661                    this: arg,
3662                    to: DataType::Date,
3663                    trailing_comments: Vec::new(),
3664                    double_colon_syntax: false,
3665                    format: None,
3666                    default: None,
3667                    inferred_type: None,
3668                })))
3669            }
3670            "DATE" if f.args.len() == 2 => {
3671                let mut args = f.args;
3672                let value = args.remove(0);
3673                let fmt = self.convert_snowflake_date_format(args.remove(0));
3674                Ok(Expression::Cast(Box::new(Cast {
3675                    this: Expression::Function(Box::new(Function::new(
3676                        "STRPTIME".to_string(),
3677                        vec![value, fmt],
3678                    ))),
3679                    to: DataType::Date,
3680                    trailing_comments: Vec::new(),
3681                    double_colon_syntax: false,
3682                    format: None,
3683                    default: None,
3684                    inferred_type: None,
3685                })))
3686            }
3687            "SYSDATE" => Ok(Expression::AtTimeZone(Box::new(
3688                crate::expressions::AtTimeZone {
3689                    this: Expression::CurrentTimestamp(crate::expressions::CurrentTimestamp {
3690                        precision: None,
3691                        sysdate: false,
3692                    }),
3693                    zone: Expression::Literal(Box::new(Literal::String("UTC".to_string()))),
3694                },
3695            ))),
3696            "HEX_DECODE_BINARY" => Ok(Expression::Function(Box::new(Function::new(
3697                "UNHEX".to_string(),
3698                f.args,
3699            )))),
3700            "CONVERT_TIMEZONE" if f.args.len() == 3 => {
3701                let mut args = f.args;
3702                let src_tz = args.remove(0);
3703                let tgt_tz = args.remove(0);
3704                let ts = args.remove(0);
3705                let cast_ts = Expression::Cast(Box::new(Cast {
3706                    this: ts,
3707                    to: DataType::Timestamp {
3708                        precision: None,
3709                        timezone: false,
3710                    },
3711                    trailing_comments: Vec::new(),
3712                    double_colon_syntax: false,
3713                    format: None,
3714                    default: None,
3715                    inferred_type: None,
3716                }));
3717                Ok(Expression::AtTimeZone(Box::new(
3718                    crate::expressions::AtTimeZone {
3719                        this: Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
3720                            this: cast_ts,
3721                            zone: src_tz,
3722                        })),
3723                        zone: tgt_tz,
3724                    },
3725                )))
3726            }
3727            "CONVERT_TIMEZONE" if f.args.len() == 2 => {
3728                let mut args = f.args;
3729                let tgt_tz = args.remove(0);
3730                let ts = args.remove(0);
3731                let cast_ts = Expression::Cast(Box::new(Cast {
3732                    this: ts,
3733                    to: DataType::Timestamp {
3734                        precision: None,
3735                        timezone: false,
3736                    },
3737                    trailing_comments: Vec::new(),
3738                    double_colon_syntax: false,
3739                    format: None,
3740                    default: None,
3741                    inferred_type: None,
3742                }));
3743                Ok(Expression::AtTimeZone(Box::new(
3744                    crate::expressions::AtTimeZone {
3745                        this: cast_ts,
3746                        zone: tgt_tz,
3747                    },
3748                )))
3749            }
3750            "DATE_PART" | "DATEPART" if f.args.len() == 2 => self.transform_date_part(f.args),
3751            "DATEADD" | "TIMEADD" if f.args.len() == 3 => self.transform_dateadd(f.args),
3752            "TIMESTAMPADD" if f.args.len() == 3 => self.transform_dateadd(f.args),
3753            "DATEDIFF" | "TIMEDIFF" if f.args.len() == 3 => self.transform_datediff(f.args),
3754            "TIMESTAMPDIFF" if f.args.len() == 3 => self.transform_datediff(f.args),
3755            "CORR" if f.args.len() == 2 => {
3756                // DuckDB handles NaN natively - no ISNAN wrapping needed
3757                Ok(Expression::Function(Box::new(f)))
3758            }
3759            "TO_TIMESTAMP" | "TO_TIMESTAMP_NTZ" if f.args.len() == 2 => {
3760                let mut args = f.args;
3761                let value = args.remove(0);
3762                let second_arg = args.remove(0);
3763                match &second_arg {
3764                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => Ok(
3765                        Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
3766                            this: Expression::Function(Box::new(Function::new(
3767                                "TO_TIMESTAMP".to_string(),
3768                                vec![Expression::Div(Box::new(BinaryOp {
3769                                    left: value,
3770                                    right: Expression::Function(Box::new(Function::new(
3771                                        "POWER".to_string(),
3772                                        vec![Expression::number(10), second_arg],
3773                                    ))),
3774                                    left_comments: Vec::new(),
3775                                    operator_comments: Vec::new(),
3776                                    trailing_comments: Vec::new(),
3777                                    inferred_type: None,
3778                                }))],
3779                            ))),
3780                            zone: Expression::Literal(Box::new(Literal::String("UTC".to_string()))),
3781                        })),
3782                    ),
3783                    _ => {
3784                        let fmt = self.convert_snowflake_time_format(second_arg);
3785                        Ok(Expression::Function(Box::new(Function::new(
3786                            "STRPTIME".to_string(),
3787                            vec![value, fmt],
3788                        ))))
3789                    }
3790                }
3791            }
3792            "TO_TIME" if f.args.len() == 1 => {
3793                let arg = f.args.into_iter().next().unwrap();
3794                Ok(Expression::Cast(Box::new(Cast {
3795                    this: arg,
3796                    to: DataType::Time {
3797                        precision: None,
3798                        timezone: false,
3799                    },
3800                    trailing_comments: Vec::new(),
3801                    double_colon_syntax: false,
3802                    format: None,
3803                    default: None,
3804                    inferred_type: None,
3805                })))
3806            }
3807            "TO_TIME" if f.args.len() == 2 => {
3808                let mut args = f.args;
3809                let value = args.remove(0);
3810                let fmt = self.convert_snowflake_time_format(args.remove(0));
3811                Ok(Expression::Cast(Box::new(Cast {
3812                    this: Expression::Function(Box::new(Function::new(
3813                        "STRPTIME".to_string(),
3814                        vec![value, fmt],
3815                    ))),
3816                    to: DataType::Time {
3817                        precision: None,
3818                        timezone: false,
3819                    },
3820                    trailing_comments: Vec::new(),
3821                    double_colon_syntax: false,
3822                    format: None,
3823                    default: None,
3824                    inferred_type: None,
3825                })))
3826            }
3827            "TO_DATE" if f.args.len() == 2 => {
3828                let mut args = f.args;
3829                let value = args.remove(0);
3830                let fmt = self.convert_snowflake_date_format(args.remove(0));
3831                Ok(Expression::Cast(Box::new(Cast {
3832                    this: Expression::Function(Box::new(Function::new(
3833                        "STRPTIME".to_string(),
3834                        vec![value, fmt],
3835                    ))),
3836                    to: DataType::Date,
3837                    trailing_comments: Vec::new(),
3838                    double_colon_syntax: false,
3839                    format: None,
3840                    default: None,
3841                    inferred_type: None,
3842                })))
3843            }
3844            // LAST_DAY with 2 args handled by comprehensive handler below
3845
3846            // SAFE_DIVIDE(x, y) -> CASE WHEN y <> 0 THEN x / y ELSE NULL END
3847            "SAFE_DIVIDE" if f.args.len() == 2 => {
3848                let mut args = f.args;
3849                let x = args.remove(0);
3850                let y = args.remove(0);
3851                Ok(Expression::Case(Box::new(Case {
3852                    operand: None,
3853                    whens: vec![(
3854                        Expression::Neq(Box::new(BinaryOp {
3855                            left: y.clone(),
3856                            right: Expression::number(0),
3857                            left_comments: Vec::new(),
3858                            operator_comments: Vec::new(),
3859                            trailing_comments: Vec::new(),
3860                            inferred_type: None,
3861                        })),
3862                        Expression::Div(Box::new(BinaryOp {
3863                            left: x,
3864                            right: y,
3865                            left_comments: Vec::new(),
3866                            operator_comments: Vec::new(),
3867                            trailing_comments: Vec::new(),
3868                            inferred_type: None,
3869                        })),
3870                    )],
3871                    else_: Some(Expression::Null(crate::expressions::Null)),
3872                    comments: Vec::new(),
3873                    inferred_type: None,
3874                })))
3875            }
3876
3877            // TO_HEX(x) -> LOWER(HEX(x)) in DuckDB (BigQuery TO_HEX returns lowercase)
3878            "TO_HEX" if f.args.len() == 1 => {
3879                let arg = f.args.into_iter().next().unwrap();
3880                Ok(Expression::Lower(Box::new(UnaryFunc::new(
3881                    Expression::Function(Box::new(Function::new("HEX".to_string(), vec![arg]))),
3882                ))))
3883            }
3884
3885            // EDIT_DISTANCE -> LEVENSHTEIN in DuckDB
3886            "EDIT_DISTANCE" if f.args.len() >= 2 => {
3887                // Only use the first two args (drop max_distance kwarg)
3888                let mut args = f.args;
3889                let a = args.remove(0);
3890                let b = args.remove(0);
3891                Ok(Expression::Function(Box::new(Function::new(
3892                    "LEVENSHTEIN".to_string(),
3893                    vec![a, b],
3894                ))))
3895            }
3896
3897            // UNIX_DATE(d) -> DATE_DIFF('DAY', CAST('1970-01-01' AS DATE), d) in DuckDB
3898            "UNIX_DATE" if f.args.len() == 1 => {
3899                let arg = f.args.into_iter().next().unwrap();
3900                Ok(Expression::Function(Box::new(Function::new(
3901                    "DATE_DIFF".to_string(),
3902                    vec![
3903                        Expression::Literal(Box::new(Literal::String("DAY".to_string()))),
3904                        Expression::Cast(Box::new(Cast {
3905                            this: Expression::Literal(Box::new(Literal::String(
3906                                "1970-01-01".to_string(),
3907                            ))),
3908                            to: DataType::Date,
3909                            trailing_comments: Vec::new(),
3910                            double_colon_syntax: false,
3911                            format: None,
3912                            default: None,
3913                            inferred_type: None,
3914                        })),
3915                        arg,
3916                    ],
3917                ))))
3918            }
3919
3920            // TIMESTAMP(x) -> CAST(x AS TIMESTAMPTZ) in DuckDB
3921            "TIMESTAMP" if f.args.len() == 1 => {
3922                let arg = f.args.into_iter().next().unwrap();
3923                Ok(Expression::Cast(Box::new(Cast {
3924                    this: arg,
3925                    to: DataType::Custom {
3926                        name: "TIMESTAMPTZ".to_string(),
3927                    },
3928                    trailing_comments: Vec::new(),
3929                    double_colon_syntax: false,
3930                    format: None,
3931                    default: None,
3932                    inferred_type: None,
3933                })))
3934            }
3935
3936            // TIME(h, m, s) -> MAKE_TIME(h, m, s) in DuckDB
3937            "TIME" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
3938                "MAKE_TIME".to_string(),
3939                f.args,
3940            )))),
3941
3942            // DATE(y, m, d) -> MAKE_DATE(y, m, d) in DuckDB
3943            "DATE" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
3944                "MAKE_DATE".to_string(),
3945                f.args,
3946            )))),
3947
3948            // DATETIME(y, m, d, h, min, sec) -> MAKE_TIMESTAMP(y, m, d, h, min, sec) in DuckDB
3949            "DATETIME" if f.args.len() == 6 => Ok(Expression::Function(Box::new(Function::new(
3950                "MAKE_TIMESTAMP".to_string(),
3951                f.args,
3952            )))),
3953
3954            // PARSE_TIMESTAMP(fmt, x) -> STRPTIME(x, fmt) in DuckDB (swap args)
3955            "PARSE_TIMESTAMP" if f.args.len() >= 2 => {
3956                let mut args = f.args;
3957                let fmt = args.remove(0);
3958                let value = args.remove(0);
3959                // Convert BigQuery format to DuckDB strptime format
3960                let duckdb_fmt = self.convert_bq_to_strptime_format(fmt);
3961                Ok(Expression::Function(Box::new(Function::new(
3962                    "STRPTIME".to_string(),
3963                    vec![value, duckdb_fmt],
3964                ))))
3965            }
3966
3967            // BOOLAND(a, b) -> ((ROUND(a, 0)) AND (ROUND(b, 0)))
3968            "BOOLAND" if f.args.len() == 2 => {
3969                let mut args = f.args;
3970                let a = args.remove(0);
3971                let b = args.remove(0);
3972                let ra = Expression::Function(Box::new(Function::new(
3973                    "ROUND".to_string(),
3974                    vec![a, Expression::number(0)],
3975                )));
3976                let rb = Expression::Function(Box::new(Function::new(
3977                    "ROUND".to_string(),
3978                    vec![b, Expression::number(0)],
3979                )));
3980                Ok(Expression::Paren(Box::new(Paren {
3981                    this: Expression::And(Box::new(BinaryOp {
3982                        left: Expression::Paren(Box::new(Paren {
3983                            this: ra,
3984                            trailing_comments: Vec::new(),
3985                        })),
3986                        right: Expression::Paren(Box::new(Paren {
3987                            this: rb,
3988                            trailing_comments: Vec::new(),
3989                        })),
3990                        left_comments: Vec::new(),
3991                        operator_comments: Vec::new(),
3992                        trailing_comments: Vec::new(),
3993                        inferred_type: None,
3994                    })),
3995                    trailing_comments: Vec::new(),
3996                })))
3997            }
3998
3999            // BOOLOR(a, b) -> ((ROUND(a, 0)) OR (ROUND(b, 0)))
4000            "BOOLOR" if f.args.len() == 2 => {
4001                let mut args = f.args;
4002                let a = args.remove(0);
4003                let b = args.remove(0);
4004                let ra = Expression::Function(Box::new(Function::new(
4005                    "ROUND".to_string(),
4006                    vec![a, Expression::number(0)],
4007                )));
4008                let rb = Expression::Function(Box::new(Function::new(
4009                    "ROUND".to_string(),
4010                    vec![b, Expression::number(0)],
4011                )));
4012                Ok(Expression::Paren(Box::new(Paren {
4013                    this: Expression::Or(Box::new(BinaryOp {
4014                        left: Expression::Paren(Box::new(Paren {
4015                            this: ra,
4016                            trailing_comments: Vec::new(),
4017                        })),
4018                        right: Expression::Paren(Box::new(Paren {
4019                            this: rb,
4020                            trailing_comments: Vec::new(),
4021                        })),
4022                        left_comments: Vec::new(),
4023                        operator_comments: Vec::new(),
4024                        trailing_comments: Vec::new(),
4025                        inferred_type: None,
4026                    })),
4027                    trailing_comments: Vec::new(),
4028                })))
4029            }
4030
4031            // BOOLXOR(a, b) -> (ROUND(a, 0) AND (NOT ROUND(b, 0))) OR ((NOT ROUND(a, 0)) AND ROUND(b, 0))
4032            "BOOLXOR" if f.args.len() == 2 => {
4033                let mut args = f.args;
4034                let a = args.remove(0);
4035                let b = args.remove(0);
4036                let ra = Expression::Function(Box::new(Function::new(
4037                    "ROUND".to_string(),
4038                    vec![a, Expression::number(0)],
4039                )));
4040                let rb = Expression::Function(Box::new(Function::new(
4041                    "ROUND".to_string(),
4042                    vec![b, Expression::number(0)],
4043                )));
4044                // (ra AND (NOT rb)) OR ((NOT ra) AND rb)
4045                let not_rb = Expression::Not(Box::new(crate::expressions::UnaryOp {
4046                    this: rb.clone(),
4047                    inferred_type: None,
4048                }));
4049                let not_ra = Expression::Not(Box::new(crate::expressions::UnaryOp {
4050                    this: ra.clone(),
4051                    inferred_type: None,
4052                }));
4053                let left_and = Expression::And(Box::new(BinaryOp {
4054                    left: ra,
4055                    right: Expression::Paren(Box::new(Paren {
4056                        this: not_rb,
4057                        trailing_comments: Vec::new(),
4058                    })),
4059                    left_comments: Vec::new(),
4060                    operator_comments: Vec::new(),
4061                    trailing_comments: Vec::new(),
4062                    inferred_type: None,
4063                }));
4064                let right_and = Expression::And(Box::new(BinaryOp {
4065                    left: Expression::Paren(Box::new(Paren {
4066                        this: not_ra,
4067                        trailing_comments: Vec::new(),
4068                    })),
4069                    right: rb,
4070                    left_comments: Vec::new(),
4071                    operator_comments: Vec::new(),
4072                    trailing_comments: Vec::new(),
4073                    inferred_type: None,
4074                }));
4075                Ok(Expression::Or(Box::new(BinaryOp {
4076                    left: Expression::Paren(Box::new(Paren {
4077                        this: left_and,
4078                        trailing_comments: Vec::new(),
4079                    })),
4080                    right: Expression::Paren(Box::new(Paren {
4081                        this: right_and,
4082                        trailing_comments: Vec::new(),
4083                    })),
4084                    left_comments: Vec::new(),
4085                    operator_comments: Vec::new(),
4086                    trailing_comments: Vec::new(),
4087                    inferred_type: None,
4088                })))
4089            }
4090
4091            // DECODE(expr, search1, result1, ..., default) -> CASE WHEN expr = search1 THEN result1 ... ELSE default END
4092            // For NULL search values, use IS NULL instead of = NULL
4093            "DECODE" if f.args.len() >= 3 => {
4094                let mut args = f.args;
4095                let expr = args.remove(0);
4096                let mut whens = Vec::new();
4097                let mut else_expr = None;
4098                while args.len() >= 2 {
4099                    let search = args.remove(0);
4100                    let result = args.remove(0);
4101                    // For NULL search values, use IS NULL; otherwise use =
4102                    let condition = if matches!(&search, Expression::Null(_)) {
4103                        Expression::IsNull(Box::new(crate::expressions::IsNull {
4104                            this: expr.clone(),
4105                            not: false,
4106                            postfix_form: false,
4107                        }))
4108                    } else {
4109                        Expression::Eq(Box::new(BinaryOp {
4110                            left: expr.clone(),
4111                            right: search,
4112                            left_comments: Vec::new(),
4113                            operator_comments: Vec::new(),
4114                            trailing_comments: Vec::new(),
4115                            inferred_type: None,
4116                        }))
4117                    };
4118                    whens.push((condition, result));
4119                }
4120                if !args.is_empty() {
4121                    else_expr = Some(args.remove(0));
4122                }
4123                Ok(Expression::Case(Box::new(Case {
4124                    operand: None,
4125                    whens,
4126                    else_: else_expr,
4127                    comments: Vec::new(),
4128                    inferred_type: None,
4129                })))
4130            }
4131
4132            // TRY_TO_BOOLEAN -> CASE WHEN UPPER(CAST(x AS TEXT)) = 'ON' THEN TRUE WHEN ... = 'OFF' THEN FALSE ELSE TRY_CAST(x AS BOOLEAN) END
4133            "TRY_TO_BOOLEAN" if f.args.len() == 1 => {
4134                let arg = f.args.into_iter().next().unwrap();
4135                let cast_text = Expression::Cast(Box::new(Cast {
4136                    this: arg.clone(),
4137                    to: DataType::Text,
4138                    trailing_comments: Vec::new(),
4139                    double_colon_syntax: false,
4140                    format: None,
4141                    default: None,
4142                    inferred_type: None,
4143                }));
4144                let upper_text = Expression::Upper(Box::new(UnaryFunc::new(cast_text)));
4145                Ok(Expression::Case(Box::new(Case {
4146                    operand: None,
4147                    whens: vec![
4148                        (
4149                            Expression::Eq(Box::new(BinaryOp {
4150                                left: upper_text.clone(),
4151                                right: Expression::Literal(Box::new(Literal::String(
4152                                    "ON".to_string(),
4153                                ))),
4154                                left_comments: Vec::new(),
4155                                operator_comments: Vec::new(),
4156                                trailing_comments: Vec::new(),
4157                                inferred_type: None,
4158                            })),
4159                            Expression::Boolean(crate::expressions::BooleanLiteral { value: true }),
4160                        ),
4161                        (
4162                            Expression::Eq(Box::new(BinaryOp {
4163                                left: upper_text,
4164                                right: Expression::Literal(Box::new(Literal::String(
4165                                    "OFF".to_string(),
4166                                ))),
4167                                left_comments: Vec::new(),
4168                                operator_comments: Vec::new(),
4169                                trailing_comments: Vec::new(),
4170                                inferred_type: None,
4171                            })),
4172                            Expression::Boolean(crate::expressions::BooleanLiteral {
4173                                value: false,
4174                            }),
4175                        ),
4176                    ],
4177                    else_: Some(Expression::TryCast(Box::new(Cast {
4178                        this: arg,
4179                        to: DataType::Boolean,
4180                        trailing_comments: Vec::new(),
4181                        double_colon_syntax: false,
4182                        format: None,
4183                        default: None,
4184                        inferred_type: None,
4185                    }))),
4186                    comments: Vec::new(),
4187                    inferred_type: None,
4188                })))
4189            }
4190
4191            // TO_BOOLEAN -> complex CASE expression
4192            "TO_BOOLEAN" if f.args.len() == 1 => {
4193                let arg = f.args.into_iter().next().unwrap();
4194                let cast_text = Expression::Cast(Box::new(Cast {
4195                    this: arg.clone(),
4196                    to: DataType::Text,
4197                    trailing_comments: Vec::new(),
4198                    double_colon_syntax: false,
4199                    format: None,
4200                    default: None,
4201                    inferred_type: None,
4202                }));
4203                let upper_text = Expression::Upper(Box::new(UnaryFunc::new(cast_text)));
4204                Ok(Expression::Case(Box::new(Case {
4205                    operand: None,
4206                    whens: vec![
4207                        (
4208                            Expression::Eq(Box::new(BinaryOp {
4209                                left: upper_text.clone(),
4210                                right: Expression::Literal(Box::new(Literal::String(
4211                                    "ON".to_string(),
4212                                ))),
4213                                left_comments: Vec::new(),
4214                                operator_comments: Vec::new(),
4215                                trailing_comments: Vec::new(),
4216                                inferred_type: None,
4217                            })),
4218                            Expression::Boolean(crate::expressions::BooleanLiteral { value: true }),
4219                        ),
4220                        (
4221                            Expression::Eq(Box::new(BinaryOp {
4222                                left: upper_text,
4223                                right: Expression::Literal(Box::new(Literal::String(
4224                                    "OFF".to_string(),
4225                                ))),
4226                                left_comments: Vec::new(),
4227                                operator_comments: Vec::new(),
4228                                trailing_comments: Vec::new(),
4229                                inferred_type: None,
4230                            })),
4231                            Expression::Boolean(crate::expressions::BooleanLiteral {
4232                                value: false,
4233                            }),
4234                        ),
4235                        (
4236                            Expression::Or(Box::new(BinaryOp {
4237                                left: Expression::Function(Box::new(Function::new(
4238                                    "ISNAN".to_string(),
4239                                    vec![Expression::TryCast(Box::new(Cast {
4240                                        this: arg.clone(),
4241                                        to: DataType::Custom {
4242                                            name: "REAL".to_string(),
4243                                        },
4244                                        trailing_comments: Vec::new(),
4245                                        double_colon_syntax: false,
4246                                        format: None,
4247                                        default: None,
4248                                        inferred_type: None,
4249                                    }))],
4250                                ))),
4251                                right: Expression::Function(Box::new(Function::new(
4252                                    "ISINF".to_string(),
4253                                    vec![Expression::TryCast(Box::new(Cast {
4254                                        this: arg.clone(),
4255                                        to: DataType::Custom {
4256                                            name: "REAL".to_string(),
4257                                        },
4258                                        trailing_comments: Vec::new(),
4259                                        double_colon_syntax: false,
4260                                        format: None,
4261                                        default: None,
4262                                        inferred_type: None,
4263                                    }))],
4264                                ))),
4265                                left_comments: Vec::new(),
4266                                operator_comments: Vec::new(),
4267                                trailing_comments: Vec::new(),
4268                                inferred_type: None,
4269                            })),
4270                            Expression::Function(Box::new(Function::new(
4271                                "ERROR".to_string(),
4272                                vec![Expression::Literal(Box::new(Literal::String(
4273                                    "TO_BOOLEAN: Non-numeric values NaN and INF are not supported"
4274                                        .to_string(),
4275                                )))],
4276                            ))),
4277                        ),
4278                    ],
4279                    else_: Some(Expression::Cast(Box::new(Cast {
4280                        this: arg,
4281                        to: DataType::Boolean,
4282                        trailing_comments: Vec::new(),
4283                        double_colon_syntax: false,
4284                        format: None,
4285                        default: None,
4286                        inferred_type: None,
4287                    }))),
4288                    comments: Vec::new(),
4289                    inferred_type: None,
4290                })))
4291            }
4292
4293            // OBJECT_INSERT(obj, key, value) -> STRUCT_INSERT(obj, key := value)
4294            // Special case: OBJECT_INSERT(OBJECT_CONSTRUCT(), key, value) -> STRUCT_PACK(key := value)
4295            "OBJECT_INSERT" if f.args.len() == 3 => {
4296                let mut args = f.args;
4297                let obj = args.remove(0);
4298                let key = args.remove(0);
4299                let value = args.remove(0);
4300                // Extract key string for named arg
4301                let key_name = match &key {
4302                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
4303                        let Literal::String(s) = lit.as_ref() else {
4304                            unreachable!()
4305                        };
4306                        s.clone()
4307                    }
4308                    _ => "key".to_string(),
4309                };
4310                let named_arg =
4311                    Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
4312                        name: Identifier::new(&key_name),
4313                        value,
4314                        separator: crate::expressions::NamedArgSeparator::ColonEq,
4315                    }));
4316                // Check if the inner object is an empty STRUCT_PACK or OBJECT_CONSTRUCT
4317                let is_empty_struct = match &obj {
4318                    Expression::Struct(s) if s.fields.is_empty() => true,
4319                    Expression::Function(f) => {
4320                        let n = f.name.to_uppercase();
4321                        (n == "STRUCT_PACK" || n == "OBJECT_CONSTRUCT") && f.args.is_empty()
4322                    }
4323                    _ => false,
4324                };
4325                if is_empty_struct {
4326                    // Collapse: OBJECT_INSERT(empty, key, value) -> STRUCT_PACK(key := value)
4327                    Ok(Expression::Function(Box::new(Function::new(
4328                        "STRUCT_PACK".to_string(),
4329                        vec![named_arg],
4330                    ))))
4331                } else {
4332                    Ok(Expression::Function(Box::new(Function::new(
4333                        "STRUCT_INSERT".to_string(),
4334                        vec![obj, named_arg],
4335                    ))))
4336                }
4337            }
4338
4339            // GET(array_or_obj, key) -> array[key+1] for arrays, obj -> '$.key' for objects
4340            "GET" if f.args.len() == 2 => {
4341                let mut args = f.args;
4342                let this = args.remove(0);
4343                let key = args.remove(0);
4344                match &key {
4345                    // String key -> JSON extract (object access)
4346                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
4347                        let Literal::String(s) = lit.as_ref() else {
4348                            unreachable!()
4349                        };
4350                        let json_path = format!("$.{}", s);
4351                        Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
4352                            options: None,
4353                            source_dialect: None,
4354
4355                            this,
4356                            path: Expression::Literal(Box::new(Literal::String(json_path))),
4357                            returning: None,
4358                            arrow_syntax: true,
4359                            hash_arrow_syntax: false,
4360                            wrapper_option: None,
4361                            quotes_option: None,
4362                            on_scalar_string: false,
4363                            on_error: None,
4364                        })))
4365                    }
4366                    // Numeric key -> array subscript
4367                    // For MAP access: key is used as-is (map[key])
4368                    // For ARRAY access: Snowflake is 0-based, DuckDB is 1-based, so add 1
4369                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4370                        let Literal::Number(n) = lit.as_ref() else {
4371                            unreachable!()
4372                        };
4373                        let idx: i64 = n.parse().unwrap_or(0);
4374                        let is_map = matches!(&this, Expression::Cast(c) if matches!(c.to, DataType::Map { .. }));
4375                        let index_val = if is_map { idx } else { idx + 1 };
4376                        Ok(Expression::Subscript(Box::new(
4377                            crate::expressions::Subscript {
4378                                this,
4379                                index: Expression::number(index_val),
4380                            },
4381                        )))
4382                    }
4383                    _ => {
4384                        // Unknown key type - use JSON arrow
4385                        Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
4386                            options: None,
4387                            source_dialect: None,
4388
4389                            this,
4390                            path: Expression::JSONPath(Box::new(JSONPath {
4391                                expressions: vec![
4392                                    Expression::JSONPathRoot(JSONPathRoot),
4393                                    Expression::JSONPathKey(Box::new(JSONPathKey {
4394                                        this: Box::new(key),
4395                                    })),
4396                                ],
4397                                escape: None,
4398                            })),
4399                            returning: None,
4400                            arrow_syntax: true,
4401                            hash_arrow_syntax: false,
4402                            wrapper_option: None,
4403                            quotes_option: None,
4404                            on_scalar_string: false,
4405                            on_error: None,
4406                        })))
4407                    }
4408                }
4409            }
4410
4411            // GET_PATH(obj, path) -> obj -> json_path in DuckDB
4412            "GET_PATH" if f.args.len() == 2 => {
4413                let mut args = f.args;
4414                let this = args.remove(0);
4415                let path = args.remove(0);
4416                // Convert Snowflake path to JSONPath
4417                let json_path = match &path {
4418                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
4419                        let Literal::String(s) = lit.as_ref() else {
4420                            unreachable!()
4421                        };
4422                        // Convert bracket notation ["key"] to quoted dot notation ."key"
4423                        let s = Self::convert_bracket_to_quoted_path(s);
4424                        // Convert Snowflake path (e.g., 'attr[0].name' or '[0].attr') to JSON path ($.attr[0].name or $[0].attr)
4425                        let normalized = if s.starts_with('$') {
4426                            s
4427                        } else if s.starts_with('[') {
4428                            format!("${}", s)
4429                        } else {
4430                            format!("$.{}", s)
4431                        };
4432                        Expression::Literal(Box::new(Literal::String(normalized)))
4433                    }
4434                    _ => path,
4435                };
4436                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
4437                    options: None,
4438                    source_dialect: None,
4439
4440                    this,
4441                    path: json_path,
4442                    returning: None,
4443                    arrow_syntax: true,
4444                    hash_arrow_syntax: false,
4445                    wrapper_option: None,
4446                    quotes_option: None,
4447                    on_scalar_string: false,
4448                    on_error: None,
4449                })))
4450            }
4451
4452            // BASE64_ENCODE(x) -> TO_BASE64(x)
4453            "BASE64_ENCODE" if f.args.len() == 1 => Ok(Expression::Function(Box::new(
4454                Function::new("TO_BASE64".to_string(), f.args),
4455            ))),
4456
4457            // BASE64_ENCODE(x, max_line_length) -> RTRIM(REGEXP_REPLACE(TO_BASE64(x), '(.{N})', '\1' || CHR(10), 'g'), CHR(10))
4458            "BASE64_ENCODE" if f.args.len() >= 2 => {
4459                let mut args = f.args;
4460                let x = args.remove(0);
4461                let line_len = args.remove(0);
4462                let line_len_str = match &line_len {
4463                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4464                        let Literal::Number(n) = lit.as_ref() else {
4465                            unreachable!()
4466                        };
4467                        n.clone()
4468                    }
4469                    _ => "76".to_string(),
4470                };
4471                let to_base64 =
4472                    Expression::Function(Box::new(Function::new("TO_BASE64".to_string(), vec![x])));
4473                let pattern = format!("(.{{{}}})", line_len_str);
4474                let chr_10 = Expression::Function(Box::new(Function::new(
4475                    "CHR".to_string(),
4476                    vec![Expression::number(10)],
4477                )));
4478                let replacement = Expression::Concat(Box::new(BinaryOp {
4479                    left: Expression::Literal(Box::new(Literal::String("\\1".to_string()))),
4480                    right: chr_10.clone(),
4481                    left_comments: Vec::new(),
4482                    operator_comments: Vec::new(),
4483                    trailing_comments: Vec::new(),
4484                    inferred_type: None,
4485                }));
4486                let regexp_replace = Expression::Function(Box::new(Function::new(
4487                    "REGEXP_REPLACE".to_string(),
4488                    vec![
4489                        to_base64,
4490                        Expression::Literal(Box::new(Literal::String(pattern))),
4491                        replacement,
4492                        Expression::Literal(Box::new(Literal::String("g".to_string()))),
4493                    ],
4494                )));
4495                Ok(Expression::Function(Box::new(Function::new(
4496                    "RTRIM".to_string(),
4497                    vec![regexp_replace, chr_10],
4498                ))))
4499            }
4500
4501            // TRY_TO_DATE with 2 args -> CAST(CAST(TRY_STRPTIME(value, fmt) AS TIMESTAMP) AS DATE)
4502            "TRY_TO_DATE" if f.args.len() == 2 => {
4503                let mut args = f.args;
4504                let value = args.remove(0);
4505                let fmt = self.convert_snowflake_date_format(args.remove(0));
4506                Ok(Expression::Cast(Box::new(Cast {
4507                    this: Expression::Cast(Box::new(Cast {
4508                        this: Expression::Function(Box::new(Function::new(
4509                            "TRY_STRPTIME".to_string(),
4510                            vec![value, fmt],
4511                        ))),
4512                        to: DataType::Timestamp {
4513                            precision: None,
4514                            timezone: false,
4515                        },
4516                        trailing_comments: Vec::new(),
4517                        double_colon_syntax: false,
4518                        format: None,
4519                        default: None,
4520                        inferred_type: None,
4521                    })),
4522                    to: DataType::Date,
4523                    trailing_comments: Vec::new(),
4524                    double_colon_syntax: false,
4525                    format: None,
4526                    default: None,
4527                    inferred_type: None,
4528                })))
4529            }
4530
4531            // REGEXP_REPLACE with 4 args: check if 4th arg is a number (Snowflake position) or flags (DuckDB native)
4532            // REGEXP_REPLACE with 4 args: check if 4th is a string flag (DuckDB native) or a numeric position
4533            "REGEXP_REPLACE" if f.args.len() == 4 => {
4534                let is_snowflake_position = matches!(&f.args[3], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)));
4535                if is_snowflake_position {
4536                    // Snowflake form: REGEXP_REPLACE(subject, pattern, replacement, position) -> add 'g' flag
4537                    let mut args = f.args;
4538                    let subject = args.remove(0);
4539                    let pattern = args.remove(0);
4540                    let replacement = args.remove(0);
4541                    Ok(Expression::Function(Box::new(Function::new(
4542                        "REGEXP_REPLACE".to_string(),
4543                        vec![
4544                            subject,
4545                            pattern,
4546                            replacement,
4547                            Expression::Literal(Box::new(Literal::String("g".to_string()))),
4548                        ],
4549                    ))))
4550                } else {
4551                    // DuckDB native form (string flags) or pass through
4552                    Ok(Expression::Function(Box::new(f)))
4553                }
4554            }
4555
4556            // REGEXP_REPLACE with 5+ args -> Snowflake form: (subject, pattern, replacement, position, occurrence, params)
4557            "REGEXP_REPLACE" if f.args.len() >= 5 => {
4558                let mut args = f.args;
4559                let subject = args.remove(0);
4560                let pattern = args.remove(0);
4561                let replacement = args.remove(0);
4562                let _position = args.remove(0);
4563                let occurrence = if !args.is_empty() {
4564                    Some(args.remove(0))
4565                } else {
4566                    None
4567                };
4568                let params = if !args.is_empty() {
4569                    Some(args.remove(0))
4570                } else {
4571                    None
4572                };
4573
4574                let mut flags = String::new();
4575                if let Some(Expression::Literal(lit)) = &params {
4576                    if let Literal::String(p) = lit.as_ref() {
4577                        flags = p.clone();
4578                    }
4579                }
4580                let is_global = match &occurrence {
4581                    Some(Expression::Literal(lit))
4582                        if matches!(lit.as_ref(), Literal::Number(_)) =>
4583                    {
4584                        let Literal::Number(n) = lit.as_ref() else {
4585                            unreachable!()
4586                        };
4587                        n == "0"
4588                    }
4589                    None => true,
4590                    _ => false,
4591                };
4592                if is_global && !flags.contains('g') {
4593                    flags.push('g');
4594                }
4595
4596                Ok(Expression::Function(Box::new(Function::new(
4597                    "REGEXP_REPLACE".to_string(),
4598                    vec![
4599                        subject,
4600                        pattern,
4601                        replacement,
4602                        Expression::Literal(Box::new(Literal::String(flags))),
4603                    ],
4604                ))))
4605            }
4606
4607            // ROUND with named args (EXPR =>, SCALE =>, ROUNDING_MODE =>)
4608            "ROUND"
4609                if f.args
4610                    .iter()
4611                    .any(|a| matches!(a, Expression::NamedArgument(_))) =>
4612            {
4613                let mut expr_val = None;
4614                let mut scale_val = None;
4615                let mut rounding_mode = None;
4616                for arg in &f.args {
4617                    if let Expression::NamedArgument(na) = arg {
4618                        match na.name.name.to_uppercase().as_str() {
4619                            "EXPR" => expr_val = Some(na.value.clone()),
4620                            "SCALE" => scale_val = Some(na.value.clone()),
4621                            "ROUNDING_MODE" => rounding_mode = Some(na.value.clone()),
4622                            _ => {}
4623                        }
4624                    }
4625                }
4626                if let Some(expr) = expr_val {
4627                    let scale = scale_val.unwrap_or(Expression::number(0));
4628                    let is_half_to_even = match &rounding_mode {
4629                        Some(Expression::Literal(lit))
4630                            if matches!(lit.as_ref(), Literal::String(_)) =>
4631                        {
4632                            let Literal::String(s) = lit.as_ref() else {
4633                                unreachable!()
4634                            };
4635                            s == "HALF_TO_EVEN"
4636                        }
4637                        _ => false,
4638                    };
4639                    if is_half_to_even {
4640                        Ok(Expression::Function(Box::new(Function::new(
4641                            "ROUND_EVEN".to_string(),
4642                            vec![expr, scale],
4643                        ))))
4644                    } else {
4645                        Ok(Expression::Function(Box::new(Function::new(
4646                            "ROUND".to_string(),
4647                            vec![expr, scale],
4648                        ))))
4649                    }
4650                } else {
4651                    Ok(Expression::Function(Box::new(f)))
4652                }
4653            }
4654
4655            // ROUND(x, scale, 'HALF_TO_EVEN') -> ROUND_EVEN(x, scale)
4656            // ROUND(x, scale, 'HALF_AWAY_FROM_ZERO') -> ROUND(x, scale)
4657            "ROUND" if f.args.len() == 3 => {
4658                let mut args = f.args;
4659                let x = args.remove(0);
4660                let scale = args.remove(0);
4661                let mode = args.remove(0);
4662                let is_half_to_even = match &mode {
4663                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
4664                        let Literal::String(s) = lit.as_ref() else {
4665                            unreachable!()
4666                        };
4667                        s == "HALF_TO_EVEN"
4668                    }
4669                    _ => false,
4670                };
4671                if is_half_to_even {
4672                    Ok(Expression::Function(Box::new(Function::new(
4673                        "ROUND_EVEN".to_string(),
4674                        vec![x, scale],
4675                    ))))
4676                } else {
4677                    // HALF_AWAY_FROM_ZERO is default in DuckDB, just drop the mode
4678                    Ok(Expression::Function(Box::new(Function::new(
4679                        "ROUND".to_string(),
4680                        vec![x, scale],
4681                    ))))
4682                }
4683            }
4684
4685            // ROUND(x, scale) where scale is non-integer -> ROUND(x, CAST(scale AS INT))
4686            "ROUND" if f.args.len() == 2 => {
4687                let mut args = f.args;
4688                let x = args.remove(0);
4689                let scale = args.remove(0);
4690                let needs_cast = match &scale {
4691                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4692                        let Literal::Number(n) = lit.as_ref() else {
4693                            unreachable!()
4694                        };
4695                        n.contains('.')
4696                    }
4697                    Expression::Cast(_) => {
4698                        // Already has a CAST - wrap in another CAST to INT
4699                        true
4700                    }
4701                    _ => false,
4702                };
4703                if needs_cast {
4704                    Ok(Expression::Function(Box::new(Function::new(
4705                        "ROUND".to_string(),
4706                        vec![
4707                            x,
4708                            Expression::Cast(Box::new(Cast {
4709                                this: scale,
4710                                to: DataType::Int {
4711                                    length: None,
4712                                    integer_spelling: false,
4713                                },
4714                                trailing_comments: Vec::new(),
4715                                double_colon_syntax: false,
4716                                format: None,
4717                                default: None,
4718                                inferred_type: None,
4719                            })),
4720                        ],
4721                    ))))
4722                } else {
4723                    Ok(Expression::Function(Box::new(Function::new(
4724                        "ROUND".to_string(),
4725                        vec![x, scale],
4726                    ))))
4727                }
4728            }
4729
4730            // FLOOR(x, scale) -> ROUND(FLOOR(x * POWER(10, scale)) / POWER(10, scale), scale)
4731            "FLOOR" if f.args.len() == 2 => {
4732                let mut args = f.args;
4733                let x = args.remove(0);
4734                let scale = args.remove(0);
4735                // Check if scale needs CAST to INT
4736                let needs_cast = match &scale {
4737                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4738                        let Literal::Number(n) = lit.as_ref() else {
4739                            unreachable!()
4740                        };
4741                        n.contains('.')
4742                    }
4743                    _ => false,
4744                };
4745                let int_scale = if needs_cast {
4746                    Expression::Cast(Box::new(Cast {
4747                        this: scale.clone(),
4748                        to: DataType::Int {
4749                            length: None,
4750                            integer_spelling: false,
4751                        },
4752                        trailing_comments: Vec::new(),
4753                        double_colon_syntax: false,
4754                        format: None,
4755                        default: None,
4756                        inferred_type: None,
4757                    }))
4758                } else {
4759                    scale.clone()
4760                };
4761                let power_10 = Expression::Function(Box::new(Function::new(
4762                    "POWER".to_string(),
4763                    vec![Expression::number(10), int_scale.clone()],
4764                )));
4765                let x_paren = match &x {
4766                    Expression::Add(_)
4767                    | Expression::Sub(_)
4768                    | Expression::Mul(_)
4769                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
4770                        this: x,
4771                        trailing_comments: Vec::new(),
4772                    })),
4773                    _ => x,
4774                };
4775                let multiplied = Expression::Mul(Box::new(BinaryOp {
4776                    left: x_paren,
4777                    right: power_10.clone(),
4778                    left_comments: Vec::new(),
4779                    operator_comments: Vec::new(),
4780                    trailing_comments: Vec::new(),
4781                    inferred_type: None,
4782                }));
4783                let floored = Expression::Function(Box::new(Function::new(
4784                    "FLOOR".to_string(),
4785                    vec![multiplied],
4786                )));
4787                let divided = Expression::Div(Box::new(BinaryOp {
4788                    left: floored,
4789                    right: power_10,
4790                    left_comments: Vec::new(),
4791                    operator_comments: Vec::new(),
4792                    trailing_comments: Vec::new(),
4793                    inferred_type: None,
4794                }));
4795                Ok(Expression::Function(Box::new(Function::new(
4796                    "ROUND".to_string(),
4797                    vec![divided, int_scale],
4798                ))))
4799            }
4800
4801            // CEIL(x, scale) -> ROUND(CEIL(x * POWER(10, scale)) / POWER(10, scale), scale)
4802            "CEIL" | "CEILING" if f.args.len() == 2 => {
4803                let mut args = f.args;
4804                let x = args.remove(0);
4805                let scale = args.remove(0);
4806                let needs_cast = match &scale {
4807                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4808                        let Literal::Number(n) = lit.as_ref() else {
4809                            unreachable!()
4810                        };
4811                        n.contains('.')
4812                    }
4813                    _ => false,
4814                };
4815                let int_scale = if needs_cast {
4816                    Expression::Cast(Box::new(Cast {
4817                        this: scale.clone(),
4818                        to: DataType::Int {
4819                            length: None,
4820                            integer_spelling: false,
4821                        },
4822                        trailing_comments: Vec::new(),
4823                        double_colon_syntax: false,
4824                        format: None,
4825                        default: None,
4826                        inferred_type: None,
4827                    }))
4828                } else {
4829                    scale.clone()
4830                };
4831                let power_10 = Expression::Function(Box::new(Function::new(
4832                    "POWER".to_string(),
4833                    vec![Expression::number(10), int_scale.clone()],
4834                )));
4835                let x_paren = match &x {
4836                    Expression::Add(_)
4837                    | Expression::Sub(_)
4838                    | Expression::Mul(_)
4839                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
4840                        this: x,
4841                        trailing_comments: Vec::new(),
4842                    })),
4843                    _ => x,
4844                };
4845                let multiplied = Expression::Mul(Box::new(BinaryOp {
4846                    left: x_paren,
4847                    right: power_10.clone(),
4848                    left_comments: Vec::new(),
4849                    operator_comments: Vec::new(),
4850                    trailing_comments: Vec::new(),
4851                    inferred_type: None,
4852                }));
4853                let ceiled = Expression::Function(Box::new(Function::new(
4854                    "CEIL".to_string(),
4855                    vec![multiplied],
4856                )));
4857                let divided = Expression::Div(Box::new(BinaryOp {
4858                    left: ceiled,
4859                    right: power_10,
4860                    left_comments: Vec::new(),
4861                    operator_comments: Vec::new(),
4862                    trailing_comments: Vec::new(),
4863                    inferred_type: None,
4864                }));
4865                Ok(Expression::Function(Box::new(Function::new(
4866                    "ROUND".to_string(),
4867                    vec![divided, int_scale],
4868                ))))
4869            }
4870
4871            // ADD_MONTHS(date, n) -> CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + INTERVAL n MONTH) ELSE date + INTERVAL n MONTH END
4872            "ADD_MONTHS" if f.args.len() == 2 => {
4873                let mut args = f.args;
4874                let date_expr_raw = args.remove(0);
4875                let months_expr = args.remove(0);
4876
4877                // Track whether the raw expression was a string literal
4878                let was_string_literal = matches!(&date_expr_raw, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)));
4879
4880                // Wrap string literals in CAST(... AS TIMESTAMP) for DuckDB
4881                let date_expr = match &date_expr_raw {
4882                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
4883                        Expression::Cast(Box::new(Cast {
4884                            this: date_expr_raw,
4885                            to: DataType::Timestamp {
4886                                precision: None,
4887                                timezone: false,
4888                            },
4889                            trailing_comments: Vec::new(),
4890                            double_colon_syntax: false,
4891                            format: None,
4892                            default: None,
4893                            inferred_type: None,
4894                        }))
4895                    }
4896                    _ => date_expr_raw,
4897                };
4898
4899                // Determine the type of the date expression for outer CAST
4900                // But NOT if the CAST was added by us (for string literal wrapping)
4901                let date_type = if was_string_literal {
4902                    None
4903                } else {
4904                    match &date_expr {
4905                        Expression::Cast(c) => Some(c.to.clone()),
4906                        _ => None,
4907                    }
4908                };
4909
4910                // Determine interval expression - for non-integer months, use TO_MONTHS(CAST(ROUND(n) AS INT))
4911                let is_non_integer_months = match &months_expr {
4912                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4913                        let Literal::Number(n) = lit.as_ref() else {
4914                            unreachable!()
4915                        };
4916                        n.contains('.')
4917                    }
4918                    Expression::Neg(_) => {
4919                        if let Expression::Neg(um) = &months_expr {
4920                            matches!(&um.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n.contains('.')))
4921                        } else {
4922                            false
4923                        }
4924                    }
4925                    // Cast to DECIMAL type means non-integer months
4926                    Expression::Cast(c) => matches!(&c.to, DataType::Decimal { .. }),
4927                    _ => false,
4928                };
4929
4930                let is_negative = match &months_expr {
4931                    Expression::Neg(_) => true,
4932                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
4933                        let Literal::Number(n) = lit.as_ref() else {
4934                            unreachable!()
4935                        };
4936                        n.starts_with('-')
4937                    }
4938                    _ => false,
4939                };
4940                let is_null = matches!(&months_expr, Expression::Null(_));
4941
4942                let interval_expr = if is_non_integer_months {
4943                    // For non-integer: TO_MONTHS(CAST(ROUND(n) AS INT))
4944                    Expression::Function(Box::new(Function::new(
4945                        "TO_MONTHS".to_string(),
4946                        vec![Expression::Cast(Box::new(Cast {
4947                            this: Expression::Function(Box::new(Function::new(
4948                                "ROUND".to_string(),
4949                                vec![months_expr.clone()],
4950                            ))),
4951                            to: DataType::Int {
4952                                length: None,
4953                                integer_spelling: false,
4954                            },
4955                            trailing_comments: Vec::new(),
4956                            double_colon_syntax: false,
4957                            format: None,
4958                            default: None,
4959                            inferred_type: None,
4960                        }))],
4961                    )))
4962                } else if is_negative || is_null {
4963                    // For negative or NULL: INTERVAL (n) MONTH
4964                    Expression::Interval(Box::new(Interval {
4965                        this: Some(Expression::Paren(Box::new(Paren {
4966                            this: months_expr.clone(),
4967                            trailing_comments: Vec::new(),
4968                        }))),
4969                        unit: Some(IntervalUnitSpec::Simple {
4970                            unit: IntervalUnit::Month,
4971                            use_plural: false,
4972                        }),
4973                    }))
4974                } else {
4975                    // For positive integer: INTERVAL n MONTH
4976                    Expression::Interval(Box::new(Interval {
4977                        this: Some(months_expr.clone()),
4978                        unit: Some(IntervalUnitSpec::Simple {
4979                            unit: IntervalUnit::Month,
4980                            use_plural: false,
4981                        }),
4982                    }))
4983                };
4984
4985                let date_plus_interval = Expression::Add(Box::new(BinaryOp {
4986                    left: date_expr.clone(),
4987                    right: interval_expr.clone(),
4988                    left_comments: Vec::new(),
4989                    operator_comments: Vec::new(),
4990                    trailing_comments: Vec::new(),
4991                    inferred_type: None,
4992                }));
4993
4994                let case_expr = Expression::Case(Box::new(Case {
4995                    operand: None,
4996                    whens: vec![(
4997                        Expression::Eq(Box::new(BinaryOp {
4998                            left: Expression::Function(Box::new(Function::new(
4999                                "LAST_DAY".to_string(),
5000                                vec![date_expr.clone()],
5001                            ))),
5002                            right: date_expr.clone(),
5003                            left_comments: Vec::new(),
5004                            operator_comments: Vec::new(),
5005                            trailing_comments: Vec::new(),
5006                            inferred_type: None,
5007                        })),
5008                        Expression::Function(Box::new(Function::new(
5009                            "LAST_DAY".to_string(),
5010                            vec![date_plus_interval.clone()],
5011                        ))),
5012                    )],
5013                    else_: Some(date_plus_interval),
5014                    comments: Vec::new(),
5015                    inferred_type: None,
5016                }));
5017
5018                // Wrap in CAST if date had explicit type
5019                if let Some(dt) = date_type {
5020                    Ok(Expression::Cast(Box::new(Cast {
5021                        this: case_expr,
5022                        to: dt,
5023                        trailing_comments: Vec::new(),
5024                        double_colon_syntax: false,
5025                        format: None,
5026                        default: None,
5027                        inferred_type: None,
5028                    })))
5029                } else {
5030                    Ok(case_expr)
5031                }
5032            }
5033
5034            // TIME_SLICE(date, n, 'UNIT') -> TIME_BUCKET(INTERVAL n UNIT, date)
5035            // TIME_SLICE(date, n, 'UNIT', 'END') -> TIME_BUCKET(INTERVAL n UNIT, date) + INTERVAL n UNIT
5036            "TIME_SLICE" if f.args.len() >= 3 => {
5037                let mut args = f.args;
5038                let date_expr = args.remove(0);
5039                let n = args.remove(0);
5040                let unit_str = args.remove(0);
5041                let alignment = if !args.is_empty() {
5042                    Some(args.remove(0))
5043                } else {
5044                    None
5045                };
5046
5047                // Extract unit string
5048                let unit = match &unit_str {
5049                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
5050                        let Literal::String(s) = lit.as_ref() else {
5051                            unreachable!()
5052                        };
5053                        s.to_uppercase()
5054                    }
5055                    Expression::Column(c) => c.name.name.to_uppercase(),
5056                    Expression::Identifier(i) => i.name.to_uppercase(),
5057                    _ => "DAY".to_string(),
5058                };
5059
5060                let interval_unit = match unit.as_str() {
5061                    "YEAR" => IntervalUnit::Year,
5062                    "QUARTER" => IntervalUnit::Quarter,
5063                    "MONTH" => IntervalUnit::Month,
5064                    "WEEK" => IntervalUnit::Week,
5065                    "DAY" => IntervalUnit::Day,
5066                    "HOUR" => IntervalUnit::Hour,
5067                    "MINUTE" => IntervalUnit::Minute,
5068                    "SECOND" => IntervalUnit::Second,
5069                    _ => IntervalUnit::Day,
5070                };
5071
5072                let interval = Expression::Interval(Box::new(Interval {
5073                    this: Some(n.clone()),
5074                    unit: Some(IntervalUnitSpec::Simple {
5075                        unit: interval_unit.clone(),
5076                        use_plural: false,
5077                    }),
5078                }));
5079
5080                let time_bucket = Expression::Function(Box::new(Function::new(
5081                    "TIME_BUCKET".to_string(),
5082                    vec![interval.clone(), date_expr.clone()],
5083                )));
5084
5085                let is_end = match &alignment {
5086                    Some(Expression::Literal(lit))
5087                        if matches!(lit.as_ref(), Literal::String(_)) =>
5088                    {
5089                        let Literal::String(s) = lit.as_ref() else {
5090                            unreachable!()
5091                        };
5092                        s.to_uppercase() == "END"
5093                    }
5094                    _ => false,
5095                };
5096
5097                // Determine if date is a DATE type (needs CAST)
5098                let is_date_type = match &date_expr {
5099                    Expression::Cast(c) => matches!(&c.to, DataType::Date),
5100                    _ => false,
5101                };
5102
5103                if is_end {
5104                    let bucket_plus = Expression::Add(Box::new(BinaryOp {
5105                        left: time_bucket,
5106                        right: Expression::Interval(Box::new(Interval {
5107                            this: Some(n),
5108                            unit: Some(IntervalUnitSpec::Simple {
5109                                unit: interval_unit,
5110                                use_plural: false,
5111                            }),
5112                        })),
5113                        left_comments: Vec::new(),
5114                        operator_comments: Vec::new(),
5115                        trailing_comments: Vec::new(),
5116                        inferred_type: None,
5117                    }));
5118                    if is_date_type {
5119                        Ok(Expression::Cast(Box::new(Cast {
5120                            this: bucket_plus,
5121                            to: DataType::Date,
5122                            trailing_comments: Vec::new(),
5123                            double_colon_syntax: false,
5124                            format: None,
5125                            default: None,
5126                            inferred_type: None,
5127                        })))
5128                    } else {
5129                        Ok(bucket_plus)
5130                    }
5131                } else {
5132                    Ok(time_bucket)
5133                }
5134            }
5135
5136            // DATE_FROM_PARTS(year, month, day) -> CAST(MAKE_DATE(year, 1, 1) + INTERVAL (month - 1) MONTH + INTERVAL (day - 1) DAY AS DATE)
5137            "DATE_FROM_PARTS" | "DATEFROMPARTS" if f.args.len() == 3 => {
5138                let mut args = f.args;
5139                let year = args.remove(0);
5140                let month = args.remove(0);
5141                let day = args.remove(0);
5142
5143                let make_date = Expression::Function(Box::new(Function::new(
5144                    "MAKE_DATE".to_string(),
5145                    vec![year, Expression::number(1), Expression::number(1)],
5146                )));
5147
5148                // Wrap compound expressions in parens to get ((expr) - 1) instead of (expr - 1)
5149                let month_wrapped = match &month {
5150                    Expression::Add(_)
5151                    | Expression::Sub(_)
5152                    | Expression::Mul(_)
5153                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
5154                        this: month,
5155                        trailing_comments: Vec::new(),
5156                    })),
5157                    _ => month,
5158                };
5159                let day_wrapped = match &day {
5160                    Expression::Add(_)
5161                    | Expression::Sub(_)
5162                    | Expression::Mul(_)
5163                    | Expression::Div(_) => Expression::Paren(Box::new(Paren {
5164                        this: day,
5165                        trailing_comments: Vec::new(),
5166                    })),
5167                    _ => day,
5168                };
5169                let month_minus_1 = Expression::Sub(Box::new(BinaryOp {
5170                    left: month_wrapped,
5171                    right: Expression::number(1),
5172                    left_comments: Vec::new(),
5173                    operator_comments: Vec::new(),
5174                    trailing_comments: Vec::new(),
5175                    inferred_type: None,
5176                }));
5177                let month_interval = Expression::Interval(Box::new(Interval {
5178                    this: Some(Expression::Paren(Box::new(Paren {
5179                        this: month_minus_1,
5180                        trailing_comments: Vec::new(),
5181                    }))),
5182                    unit: Some(IntervalUnitSpec::Simple {
5183                        unit: IntervalUnit::Month,
5184                        use_plural: false,
5185                    }),
5186                }));
5187
5188                let day_minus_1 = Expression::Sub(Box::new(BinaryOp {
5189                    left: day_wrapped,
5190                    right: Expression::number(1),
5191                    left_comments: Vec::new(),
5192                    operator_comments: Vec::new(),
5193                    trailing_comments: Vec::new(),
5194                    inferred_type: None,
5195                }));
5196                let day_interval = Expression::Interval(Box::new(Interval {
5197                    this: Some(Expression::Paren(Box::new(Paren {
5198                        this: day_minus_1,
5199                        trailing_comments: Vec::new(),
5200                    }))),
5201                    unit: Some(IntervalUnitSpec::Simple {
5202                        unit: IntervalUnit::Day,
5203                        use_plural: false,
5204                    }),
5205                }));
5206
5207                let result = Expression::Add(Box::new(BinaryOp {
5208                    left: Expression::Add(Box::new(BinaryOp {
5209                        left: make_date,
5210                        right: month_interval,
5211                        left_comments: Vec::new(),
5212                        operator_comments: Vec::new(),
5213                        trailing_comments: Vec::new(),
5214                        inferred_type: None,
5215                    })),
5216                    right: day_interval,
5217                    left_comments: Vec::new(),
5218                    operator_comments: Vec::new(),
5219                    trailing_comments: Vec::new(),
5220                    inferred_type: None,
5221                }));
5222
5223                Ok(Expression::Cast(Box::new(Cast {
5224                    this: result,
5225                    to: DataType::Date,
5226                    trailing_comments: Vec::new(),
5227                    double_colon_syntax: false,
5228                    format: None,
5229                    default: None,
5230                    inferred_type: None,
5231                })))
5232            }
5233
5234            // NEXT_DAY(date, 'day_name') -> complex expression using ISODOW
5235            "NEXT_DAY" if f.args.len() == 2 => {
5236                let mut args = f.args;
5237                let date = args.remove(0);
5238                let day_name = args.remove(0);
5239
5240                // Parse day name to ISO day number (1=Monday..7=Sunday)
5241                let day_num = match &day_name {
5242                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
5243                        let Literal::String(s) = lit.as_ref() else {
5244                            unreachable!()
5245                        };
5246                        let upper = s.to_uppercase();
5247                        if upper.starts_with("MO") {
5248                            Some(1)
5249                        } else if upper.starts_with("TU") {
5250                            Some(2)
5251                        } else if upper.starts_with("WE") {
5252                            Some(3)
5253                        } else if upper.starts_with("TH") {
5254                            Some(4)
5255                        } else if upper.starts_with("FR") {
5256                            Some(5)
5257                        } else if upper.starts_with("SA") {
5258                            Some(6)
5259                        } else if upper.starts_with("SU") {
5260                            Some(7)
5261                        } else {
5262                            None
5263                        }
5264                    }
5265                    _ => None,
5266                };
5267
5268                let target_day_expr = if let Some(n) = day_num {
5269                    Expression::number(n)
5270                } else {
5271                    // Dynamic day name: CASE WHEN STARTS_WITH(UPPER(day_column), 'MO') THEN 1 ... END
5272                    Expression::Case(Box::new(Case {
5273                        operand: None,
5274                        whens: vec![
5275                            (
5276                                Expression::Function(Box::new(Function::new(
5277                                    "STARTS_WITH".to_string(),
5278                                    vec![
5279                                        Expression::Upper(Box::new(UnaryFunc::new(
5280                                            day_name.clone(),
5281                                        ))),
5282                                        Expression::Literal(Box::new(Literal::String(
5283                                            "MO".to_string(),
5284                                        ))),
5285                                    ],
5286                                ))),
5287                                Expression::number(1),
5288                            ),
5289                            (
5290                                Expression::Function(Box::new(Function::new(
5291                                    "STARTS_WITH".to_string(),
5292                                    vec![
5293                                        Expression::Upper(Box::new(UnaryFunc::new(
5294                                            day_name.clone(),
5295                                        ))),
5296                                        Expression::Literal(Box::new(Literal::String(
5297                                            "TU".to_string(),
5298                                        ))),
5299                                    ],
5300                                ))),
5301                                Expression::number(2),
5302                            ),
5303                            (
5304                                Expression::Function(Box::new(Function::new(
5305                                    "STARTS_WITH".to_string(),
5306                                    vec![
5307                                        Expression::Upper(Box::new(UnaryFunc::new(
5308                                            day_name.clone(),
5309                                        ))),
5310                                        Expression::Literal(Box::new(Literal::String(
5311                                            "WE".to_string(),
5312                                        ))),
5313                                    ],
5314                                ))),
5315                                Expression::number(3),
5316                            ),
5317                            (
5318                                Expression::Function(Box::new(Function::new(
5319                                    "STARTS_WITH".to_string(),
5320                                    vec![
5321                                        Expression::Upper(Box::new(UnaryFunc::new(
5322                                            day_name.clone(),
5323                                        ))),
5324                                        Expression::Literal(Box::new(Literal::String(
5325                                            "TH".to_string(),
5326                                        ))),
5327                                    ],
5328                                ))),
5329                                Expression::number(4),
5330                            ),
5331                            (
5332                                Expression::Function(Box::new(Function::new(
5333                                    "STARTS_WITH".to_string(),
5334                                    vec![
5335                                        Expression::Upper(Box::new(UnaryFunc::new(
5336                                            day_name.clone(),
5337                                        ))),
5338                                        Expression::Literal(Box::new(Literal::String(
5339                                            "FR".to_string(),
5340                                        ))),
5341                                    ],
5342                                ))),
5343                                Expression::number(5),
5344                            ),
5345                            (
5346                                Expression::Function(Box::new(Function::new(
5347                                    "STARTS_WITH".to_string(),
5348                                    vec![
5349                                        Expression::Upper(Box::new(UnaryFunc::new(
5350                                            day_name.clone(),
5351                                        ))),
5352                                        Expression::Literal(Box::new(Literal::String(
5353                                            "SA".to_string(),
5354                                        ))),
5355                                    ],
5356                                ))),
5357                                Expression::number(6),
5358                            ),
5359                            (
5360                                Expression::Function(Box::new(Function::new(
5361                                    "STARTS_WITH".to_string(),
5362                                    vec![
5363                                        Expression::Upper(Box::new(UnaryFunc::new(day_name))),
5364                                        Expression::Literal(Box::new(Literal::String(
5365                                            "SU".to_string(),
5366                                        ))),
5367                                    ],
5368                                ))),
5369                                Expression::number(7),
5370                            ),
5371                        ],
5372                        else_: None,
5373                        comments: Vec::new(),
5374                        inferred_type: None,
5375                    }))
5376                };
5377
5378                let isodow = Expression::Function(Box::new(Function::new(
5379                    "ISODOW".to_string(),
5380                    vec![date.clone()],
5381                )));
5382                // ((target_day - ISODOW(date) + 6) % 7) + 1
5383                let diff = Expression::Add(Box::new(BinaryOp {
5384                    left: Expression::Paren(Box::new(Paren {
5385                        this: Expression::Mod(Box::new(BinaryOp {
5386                            left: Expression::Paren(Box::new(Paren {
5387                                this: Expression::Add(Box::new(BinaryOp {
5388                                    left: Expression::Paren(Box::new(Paren {
5389                                        this: Expression::Sub(Box::new(BinaryOp {
5390                                            left: target_day_expr,
5391                                            right: isodow,
5392                                            left_comments: Vec::new(),
5393                                            operator_comments: Vec::new(),
5394                                            trailing_comments: Vec::new(),
5395                                            inferred_type: None,
5396                                        })),
5397                                        trailing_comments: Vec::new(),
5398                                    })),
5399                                    right: Expression::number(6),
5400                                    left_comments: Vec::new(),
5401                                    operator_comments: Vec::new(),
5402                                    trailing_comments: Vec::new(),
5403                                    inferred_type: None,
5404                                })),
5405                                trailing_comments: Vec::new(),
5406                            })),
5407                            right: Expression::number(7),
5408                            left_comments: Vec::new(),
5409                            operator_comments: Vec::new(),
5410                            trailing_comments: Vec::new(),
5411                            inferred_type: None,
5412                        })),
5413                        trailing_comments: Vec::new(),
5414                    })),
5415                    right: Expression::number(1),
5416                    left_comments: Vec::new(),
5417                    operator_comments: Vec::new(),
5418                    trailing_comments: Vec::new(),
5419                    inferred_type: None,
5420                }));
5421
5422                let result = Expression::Add(Box::new(BinaryOp {
5423                    left: date,
5424                    right: Expression::Interval(Box::new(Interval {
5425                        this: Some(Expression::Paren(Box::new(Paren {
5426                            this: diff,
5427                            trailing_comments: Vec::new(),
5428                        }))),
5429                        unit: Some(IntervalUnitSpec::Simple {
5430                            unit: IntervalUnit::Day,
5431                            use_plural: false,
5432                        }),
5433                    })),
5434                    left_comments: Vec::new(),
5435                    operator_comments: Vec::new(),
5436                    trailing_comments: Vec::new(),
5437                    inferred_type: None,
5438                }));
5439
5440                Ok(Expression::Cast(Box::new(Cast {
5441                    this: result,
5442                    to: DataType::Date,
5443                    trailing_comments: Vec::new(),
5444                    double_colon_syntax: false,
5445                    format: None,
5446                    default: None,
5447                    inferred_type: None,
5448                })))
5449            }
5450
5451            // PREVIOUS_DAY(date, 'day_name') -> complex expression using ISODOW
5452            "PREVIOUS_DAY" if f.args.len() == 2 => {
5453                let mut args = f.args;
5454                let date = args.remove(0);
5455                let day_name = args.remove(0);
5456
5457                let day_num = match &day_name {
5458                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
5459                        let Literal::String(s) = lit.as_ref() else {
5460                            unreachable!()
5461                        };
5462                        let upper = s.to_uppercase();
5463                        if upper.starts_with("MO") {
5464                            Some(1)
5465                        } else if upper.starts_with("TU") {
5466                            Some(2)
5467                        } else if upper.starts_with("WE") {
5468                            Some(3)
5469                        } else if upper.starts_with("TH") {
5470                            Some(4)
5471                        } else if upper.starts_with("FR") {
5472                            Some(5)
5473                        } else if upper.starts_with("SA") {
5474                            Some(6)
5475                        } else if upper.starts_with("SU") {
5476                            Some(7)
5477                        } else {
5478                            None
5479                        }
5480                    }
5481                    _ => None,
5482                };
5483
5484                let target_day_expr = if let Some(n) = day_num {
5485                    Expression::number(n)
5486                } else {
5487                    Expression::Case(Box::new(Case {
5488                        operand: None,
5489                        whens: vec![
5490                            (
5491                                Expression::Function(Box::new(Function::new(
5492                                    "STARTS_WITH".to_string(),
5493                                    vec![
5494                                        Expression::Upper(Box::new(UnaryFunc::new(
5495                                            day_name.clone(),
5496                                        ))),
5497                                        Expression::Literal(Box::new(Literal::String(
5498                                            "MO".to_string(),
5499                                        ))),
5500                                    ],
5501                                ))),
5502                                Expression::number(1),
5503                            ),
5504                            (
5505                                Expression::Function(Box::new(Function::new(
5506                                    "STARTS_WITH".to_string(),
5507                                    vec![
5508                                        Expression::Upper(Box::new(UnaryFunc::new(
5509                                            day_name.clone(),
5510                                        ))),
5511                                        Expression::Literal(Box::new(Literal::String(
5512                                            "TU".to_string(),
5513                                        ))),
5514                                    ],
5515                                ))),
5516                                Expression::number(2),
5517                            ),
5518                            (
5519                                Expression::Function(Box::new(Function::new(
5520                                    "STARTS_WITH".to_string(),
5521                                    vec![
5522                                        Expression::Upper(Box::new(UnaryFunc::new(
5523                                            day_name.clone(),
5524                                        ))),
5525                                        Expression::Literal(Box::new(Literal::String(
5526                                            "WE".to_string(),
5527                                        ))),
5528                                    ],
5529                                ))),
5530                                Expression::number(3),
5531                            ),
5532                            (
5533                                Expression::Function(Box::new(Function::new(
5534                                    "STARTS_WITH".to_string(),
5535                                    vec![
5536                                        Expression::Upper(Box::new(UnaryFunc::new(
5537                                            day_name.clone(),
5538                                        ))),
5539                                        Expression::Literal(Box::new(Literal::String(
5540                                            "TH".to_string(),
5541                                        ))),
5542                                    ],
5543                                ))),
5544                                Expression::number(4),
5545                            ),
5546                            (
5547                                Expression::Function(Box::new(Function::new(
5548                                    "STARTS_WITH".to_string(),
5549                                    vec![
5550                                        Expression::Upper(Box::new(UnaryFunc::new(
5551                                            day_name.clone(),
5552                                        ))),
5553                                        Expression::Literal(Box::new(Literal::String(
5554                                            "FR".to_string(),
5555                                        ))),
5556                                    ],
5557                                ))),
5558                                Expression::number(5),
5559                            ),
5560                            (
5561                                Expression::Function(Box::new(Function::new(
5562                                    "STARTS_WITH".to_string(),
5563                                    vec![
5564                                        Expression::Upper(Box::new(UnaryFunc::new(
5565                                            day_name.clone(),
5566                                        ))),
5567                                        Expression::Literal(Box::new(Literal::String(
5568                                            "SA".to_string(),
5569                                        ))),
5570                                    ],
5571                                ))),
5572                                Expression::number(6),
5573                            ),
5574                            (
5575                                Expression::Function(Box::new(Function::new(
5576                                    "STARTS_WITH".to_string(),
5577                                    vec![
5578                                        Expression::Upper(Box::new(UnaryFunc::new(day_name))),
5579                                        Expression::Literal(Box::new(Literal::String(
5580                                            "SU".to_string(),
5581                                        ))),
5582                                    ],
5583                                ))),
5584                                Expression::number(7),
5585                            ),
5586                        ],
5587                        else_: None,
5588                        comments: Vec::new(),
5589                        inferred_type: None,
5590                    }))
5591                };
5592
5593                let isodow = Expression::Function(Box::new(Function::new(
5594                    "ISODOW".to_string(),
5595                    vec![date.clone()],
5596                )));
5597                // ((ISODOW(date) - target_day + 6) % 7) + 1
5598                let diff = Expression::Add(Box::new(BinaryOp {
5599                    left: Expression::Paren(Box::new(Paren {
5600                        this: Expression::Mod(Box::new(BinaryOp {
5601                            left: Expression::Paren(Box::new(Paren {
5602                                this: Expression::Add(Box::new(BinaryOp {
5603                                    left: Expression::Paren(Box::new(Paren {
5604                                        this: Expression::Sub(Box::new(BinaryOp {
5605                                            left: isodow,
5606                                            right: target_day_expr,
5607                                            left_comments: Vec::new(),
5608                                            operator_comments: Vec::new(),
5609                                            trailing_comments: Vec::new(),
5610                                            inferred_type: None,
5611                                        })),
5612                                        trailing_comments: Vec::new(),
5613                                    })),
5614                                    right: Expression::number(6),
5615                                    left_comments: Vec::new(),
5616                                    operator_comments: Vec::new(),
5617                                    trailing_comments: Vec::new(),
5618                                    inferred_type: None,
5619                                })),
5620                                trailing_comments: Vec::new(),
5621                            })),
5622                            right: Expression::number(7),
5623                            left_comments: Vec::new(),
5624                            operator_comments: Vec::new(),
5625                            trailing_comments: Vec::new(),
5626                            inferred_type: None,
5627                        })),
5628                        trailing_comments: Vec::new(),
5629                    })),
5630                    right: Expression::number(1),
5631                    left_comments: Vec::new(),
5632                    operator_comments: Vec::new(),
5633                    trailing_comments: Vec::new(),
5634                    inferred_type: None,
5635                }));
5636
5637                let result = Expression::Sub(Box::new(BinaryOp {
5638                    left: date,
5639                    right: Expression::Interval(Box::new(Interval {
5640                        this: Some(Expression::Paren(Box::new(Paren {
5641                            this: diff,
5642                            trailing_comments: Vec::new(),
5643                        }))),
5644                        unit: Some(IntervalUnitSpec::Simple {
5645                            unit: IntervalUnit::Day,
5646                            use_plural: false,
5647                        }),
5648                    })),
5649                    left_comments: Vec::new(),
5650                    operator_comments: Vec::new(),
5651                    trailing_comments: Vec::new(),
5652                    inferred_type: None,
5653                }));
5654
5655                Ok(Expression::Cast(Box::new(Cast {
5656                    this: result,
5657                    to: DataType::Date,
5658                    trailing_comments: Vec::new(),
5659                    double_colon_syntax: false,
5660                    format: None,
5661                    default: None,
5662                    inferred_type: None,
5663                })))
5664            }
5665
5666            // LAST_DAY(date, YEAR) -> MAKE_DATE(EXTRACT(YEAR FROM date), 12, 31)
5667            // LAST_DAY(date, QUARTER) -> LAST_DAY(MAKE_DATE(EXTRACT(YEAR FROM date), EXTRACT(QUARTER FROM date) * 3, 1))
5668            // LAST_DAY(date, WEEK) -> CAST(date + INTERVAL ((7 - EXTRACT(DAYOFWEEK FROM date)) % 7) DAY AS DATE)
5669            "LAST_DAY" if f.args.len() == 2 => {
5670                let mut args = f.args;
5671                let date = args.remove(0);
5672                let unit = args.remove(0);
5673                let unit_str = match &unit {
5674                    Expression::Column(c) => c.name.name.to_uppercase(),
5675                    Expression::Identifier(i) => i.name.to_uppercase(),
5676                    _ => String::new(),
5677                };
5678
5679                match unit_str.as_str() {
5680                    "MONTH" => Ok(Expression::Function(Box::new(Function::new(
5681                        "LAST_DAY".to_string(),
5682                        vec![date],
5683                    )))),
5684                    "YEAR" => Ok(Expression::Function(Box::new(Function::new(
5685                        "MAKE_DATE".to_string(),
5686                        vec![
5687                            Expression::Extract(Box::new(crate::expressions::ExtractFunc {
5688                                this: date,
5689                                field: crate::expressions::DateTimeField::Year,
5690                            })),
5691                            Expression::number(12),
5692                            Expression::number(31),
5693                        ],
5694                    )))),
5695                    "QUARTER" => {
5696                        let year = Expression::Extract(Box::new(crate::expressions::ExtractFunc {
5697                            this: date.clone(),
5698                            field: crate::expressions::DateTimeField::Year,
5699                        }));
5700                        let quarter_month = Expression::Mul(Box::new(BinaryOp {
5701                            left: Expression::Extract(Box::new(crate::expressions::ExtractFunc {
5702                                this: date,
5703                                field: crate::expressions::DateTimeField::Custom(
5704                                    "QUARTER".to_string(),
5705                                ),
5706                            })),
5707                            right: Expression::number(3),
5708                            left_comments: Vec::new(),
5709                            operator_comments: Vec::new(),
5710                            trailing_comments: Vec::new(),
5711                            inferred_type: None,
5712                        }));
5713                        let make_date = Expression::Function(Box::new(Function::new(
5714                            "MAKE_DATE".to_string(),
5715                            vec![year, quarter_month, Expression::number(1)],
5716                        )));
5717                        Ok(Expression::Function(Box::new(Function::new(
5718                            "LAST_DAY".to_string(),
5719                            vec![make_date],
5720                        ))))
5721                    }
5722                    "WEEK" => {
5723                        let dow = Expression::Extract(Box::new(crate::expressions::ExtractFunc {
5724                            this: date.clone(),
5725                            field: crate::expressions::DateTimeField::Custom(
5726                                "DAYOFWEEK".to_string(),
5727                            ),
5728                        }));
5729                        let diff = Expression::Mod(Box::new(BinaryOp {
5730                            left: Expression::Paren(Box::new(Paren {
5731                                this: Expression::Sub(Box::new(BinaryOp {
5732                                    left: Expression::number(7),
5733                                    right: dow,
5734                                    left_comments: Vec::new(),
5735                                    operator_comments: Vec::new(),
5736                                    trailing_comments: Vec::new(),
5737                                    inferred_type: None,
5738                                })),
5739                                trailing_comments: Vec::new(),
5740                            })),
5741                            right: Expression::number(7),
5742                            left_comments: Vec::new(),
5743                            operator_comments: Vec::new(),
5744                            trailing_comments: Vec::new(),
5745                            inferred_type: None,
5746                        }));
5747                        let result = Expression::Add(Box::new(BinaryOp {
5748                            left: date,
5749                            right: Expression::Interval(Box::new(Interval {
5750                                this: Some(Expression::Paren(Box::new(Paren {
5751                                    this: diff,
5752                                    trailing_comments: Vec::new(),
5753                                }))),
5754                                unit: Some(IntervalUnitSpec::Simple {
5755                                    unit: IntervalUnit::Day,
5756                                    use_plural: false,
5757                                }),
5758                            })),
5759                            left_comments: Vec::new(),
5760                            operator_comments: Vec::new(),
5761                            trailing_comments: Vec::new(),
5762                            inferred_type: None,
5763                        }));
5764                        Ok(Expression::Cast(Box::new(Cast {
5765                            this: result,
5766                            to: DataType::Date,
5767                            trailing_comments: Vec::new(),
5768                            double_colon_syntax: false,
5769                            format: None,
5770                            default: None,
5771                            inferred_type: None,
5772                        })))
5773                    }
5774                    _ => Ok(Expression::Function(Box::new(Function::new(
5775                        "LAST_DAY".to_string(),
5776                        vec![date, unit],
5777                    )))),
5778                }
5779            }
5780
5781            // SEQ1/SEQ2/SEQ4/SEQ8 -> (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % range
5782            // When FROM clause is RANGE(n), a post-transform replaces this with `range % N`
5783            "SEQ1" | "SEQ2" | "SEQ4" | "SEQ8" => {
5784                let (range, half): (u128, u128) = match name_upper.as_str() {
5785                    "SEQ1" => (256, 128),
5786                    "SEQ2" => (65536, 32768),
5787                    "SEQ4" => (4294967296, 2147483648),
5788                    "SEQ8" => (18446744073709551616, 9223372036854775808),
5789                    _ => unreachable!("sequence type already matched in caller"),
5790                };
5791
5792                let is_signed = match f.args.first() {
5793                    Some(Expression::Literal(lit))
5794                        if matches!(lit.as_ref(), Literal::Number(_)) =>
5795                    {
5796                        let Literal::Number(n) = lit.as_ref() else {
5797                            unreachable!()
5798                        };
5799                        n == "1"
5800                    }
5801                    _ => false,
5802                };
5803
5804                let row_num = Expression::Sub(Box::new(BinaryOp {
5805                    left: Expression::WindowFunction(Box::new(
5806                        crate::expressions::WindowFunction {
5807                            this: Expression::Function(Box::new(Function::new(
5808                                "ROW_NUMBER".to_string(),
5809                                vec![],
5810                            ))),
5811                            over: crate::expressions::Over {
5812                                window_name: None,
5813                                partition_by: vec![],
5814                                order_by: vec![crate::expressions::Ordered {
5815                                    nulls_auto: false,
5816                                    this: Expression::number(1),
5817                                    desc: false,
5818                                    nulls_first: Some(true),
5819                                    explicit_asc: false,
5820                                    with_fill: None,
5821                                }],
5822                                frame: None,
5823                                alias: None,
5824                            },
5825                            keep: None,
5826                            inferred_type: None,
5827                        },
5828                    )),
5829                    right: Expression::number(1),
5830                    left_comments: Vec::new(),
5831                    operator_comments: Vec::new(),
5832                    trailing_comments: Vec::new(),
5833                    inferred_type: None,
5834                }));
5835
5836                let modded = Expression::Mod(Box::new(BinaryOp {
5837                    left: Expression::Paren(Box::new(Paren {
5838                        this: row_num,
5839                        trailing_comments: Vec::new(),
5840                    })),
5841                    right: Expression::Literal(Box::new(Literal::Number(range.to_string()))),
5842                    left_comments: Vec::new(),
5843                    operator_comments: Vec::new(),
5844                    trailing_comments: Vec::new(),
5845                    inferred_type: None,
5846                }));
5847
5848                if is_signed {
5849                    // CASE WHEN val >= half THEN val - range ELSE val END
5850                    let cond = Expression::Gte(Box::new(BinaryOp {
5851                        left: modded.clone(),
5852                        right: Expression::Literal(Box::new(Literal::Number(half.to_string()))),
5853                        left_comments: Vec::new(),
5854                        operator_comments: Vec::new(),
5855                        trailing_comments: Vec::new(),
5856                        inferred_type: None,
5857                    }));
5858                    let signed_val = Expression::Sub(Box::new(BinaryOp {
5859                        left: modded.clone(),
5860                        right: Expression::Literal(Box::new(Literal::Number(range.to_string()))),
5861                        left_comments: Vec::new(),
5862                        operator_comments: Vec::new(),
5863                        trailing_comments: Vec::new(),
5864                        inferred_type: None,
5865                    }));
5866                    Ok(Expression::Paren(Box::new(Paren {
5867                        this: Expression::Case(Box::new(Case {
5868                            operand: None,
5869                            whens: vec![(cond, signed_val)],
5870                            else_: Some(modded),
5871                            comments: Vec::new(),
5872                            inferred_type: None,
5873                        })),
5874                        trailing_comments: Vec::new(),
5875                    })))
5876                } else {
5877                    Ok(modded)
5878                }
5879            }
5880
5881            // TABLE(fn) -> fn (unwrap TABLE wrapper for DuckDB)
5882            // Also handles TABLE(GENERATOR(ROWCOUNT => n)) -> RANGE(n) directly
5883            "TABLE" if f.args.len() == 1 => {
5884                let inner = f.args.into_iter().next().unwrap();
5885                // If inner is GENERATOR, transform it to RANGE
5886                if let Expression::Function(ref gen_f) = inner {
5887                    if gen_f.name.to_uppercase() == "GENERATOR" {
5888                        let mut rowcount = None;
5889                        for arg in &gen_f.args {
5890                            if let Expression::NamedArgument(na) = arg {
5891                                if na.name.name.to_uppercase() == "ROWCOUNT" {
5892                                    rowcount = Some(na.value.clone());
5893                                }
5894                            }
5895                        }
5896                        if let Some(n) = rowcount {
5897                            return Ok(Expression::Function(Box::new(Function::new(
5898                                "RANGE".to_string(),
5899                                vec![n],
5900                            ))));
5901                        }
5902                    }
5903                }
5904                Ok(inner)
5905            }
5906
5907            // GENERATOR(ROWCOUNT => n) -> RANGE(n) in DuckDB
5908            "GENERATOR" => {
5909                let mut rowcount = None;
5910                for arg in &f.args {
5911                    if let Expression::NamedArgument(na) = arg {
5912                        if na.name.name.to_uppercase() == "ROWCOUNT" {
5913                            rowcount = Some(na.value.clone());
5914                        }
5915                    }
5916                }
5917                if let Some(n) = rowcount {
5918                    Ok(Expression::Function(Box::new(Function::new(
5919                        "RANGE".to_string(),
5920                        vec![n],
5921                    ))))
5922                } else {
5923                    Ok(Expression::Function(Box::new(f)))
5924                }
5925            }
5926
5927            // UNIFORM(low, high, gen) -> CAST(FLOOR(low + RANDOM() * (high - low + 1)) AS BIGINT)
5928            // or with seed: CAST(FLOOR(low + (ABS(HASH(seed)) % 1000000) / 1000000.0 * (high - low + 1)) AS BIGINT)
5929            "UNIFORM" if f.args.len() == 3 => {
5930                let mut args = f.args;
5931                let low = args.remove(0);
5932                let high = args.remove(0);
5933                let gen = args.remove(0);
5934
5935                let range = Expression::Add(Box::new(BinaryOp {
5936                    left: Expression::Sub(Box::new(BinaryOp {
5937                        left: high,
5938                        right: low.clone(),
5939                        left_comments: Vec::new(),
5940                        operator_comments: Vec::new(),
5941                        trailing_comments: Vec::new(),
5942                        inferred_type: None,
5943                    })),
5944                    right: Expression::number(1),
5945                    left_comments: Vec::new(),
5946                    operator_comments: Vec::new(),
5947                    trailing_comments: Vec::new(),
5948                    inferred_type: None,
5949                }));
5950
5951                // Check if gen is RANDOM() (function) or a literal seed
5952                let random_val = match &gen {
5953                    Expression::Rand(_) | Expression::Random(_) => {
5954                        // RANDOM() - use directly
5955                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])))
5956                    }
5957                    Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
5958                        // RANDOM(seed) or RANDOM() - just use RANDOM()
5959                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])))
5960                    }
5961                    _ => {
5962                        // Seed-based: (ABS(HASH(seed)) % 1000000) / 1000000.0
5963                        let hash = Expression::Function(Box::new(Function::new(
5964                            "HASH".to_string(),
5965                            vec![gen],
5966                        )));
5967                        let abs_hash = Expression::Abs(Box::new(UnaryFunc::new(hash)));
5968                        let modded = Expression::Mod(Box::new(BinaryOp {
5969                            left: abs_hash,
5970                            right: Expression::number(1000000),
5971                            left_comments: Vec::new(),
5972                            operator_comments: Vec::new(),
5973                            trailing_comments: Vec::new(),
5974                            inferred_type: None,
5975                        }));
5976                        let paren_modded = Expression::Paren(Box::new(Paren {
5977                            this: modded,
5978                            trailing_comments: Vec::new(),
5979                        }));
5980                        Expression::Div(Box::new(BinaryOp {
5981                            left: paren_modded,
5982                            right: Expression::Literal(Box::new(Literal::Number(
5983                                "1000000.0".to_string(),
5984                            ))),
5985                            left_comments: Vec::new(),
5986                            operator_comments: Vec::new(),
5987                            trailing_comments: Vec::new(),
5988                            inferred_type: None,
5989                        }))
5990                    }
5991                };
5992
5993                let inner = Expression::Function(Box::new(Function::new(
5994                    "FLOOR".to_string(),
5995                    vec![Expression::Add(Box::new(BinaryOp {
5996                        left: low,
5997                        right: Expression::Mul(Box::new(BinaryOp {
5998                            left: random_val,
5999                            right: Expression::Paren(Box::new(Paren {
6000                                this: range,
6001                                trailing_comments: Vec::new(),
6002                            })),
6003                            left_comments: Vec::new(),
6004                            operator_comments: Vec::new(),
6005                            trailing_comments: Vec::new(),
6006                            inferred_type: None,
6007                        })),
6008                        left_comments: Vec::new(),
6009                        operator_comments: Vec::new(),
6010                        trailing_comments: Vec::new(),
6011                        inferred_type: None,
6012                    }))],
6013                )));
6014
6015                Ok(Expression::Cast(Box::new(Cast {
6016                    this: inner,
6017                    to: DataType::BigInt { length: None },
6018                    trailing_comments: Vec::new(),
6019                    double_colon_syntax: false,
6020                    format: None,
6021                    default: None,
6022                    inferred_type: None,
6023                })))
6024            }
6025
6026            // NORMAL(mean, stddev, gen) -> Box-Muller transform
6027            // mean + (stddev * SQRT(-2 * LN(GREATEST(u1, 1e-10))) * COS(2 * PI() * u2))
6028            // where u1 and u2 are uniform random values derived from gen
6029            "NORMAL" if f.args.len() == 3 => {
6030                let mut args = f.args;
6031                let mean = args.remove(0);
6032                let stddev = args.remove(0);
6033                let gen = args.remove(0);
6034
6035                // Helper to create seed-based random: (ABS(HASH(seed)) % 1000000) / 1000000.0
6036                let make_seed_random = |seed: Expression| -> Expression {
6037                    let hash = Expression::Function(Box::new(Function::new(
6038                        "HASH".to_string(),
6039                        vec![seed],
6040                    )));
6041                    let abs_hash = Expression::Abs(Box::new(UnaryFunc::new(hash)));
6042                    let modded = Expression::Mod(Box::new(BinaryOp {
6043                        left: abs_hash,
6044                        right: Expression::number(1000000),
6045                        left_comments: Vec::new(),
6046                        operator_comments: Vec::new(),
6047                        trailing_comments: Vec::new(),
6048                        inferred_type: None,
6049                    }));
6050                    let paren_modded = Expression::Paren(Box::new(Paren {
6051                        this: modded,
6052                        trailing_comments: Vec::new(),
6053                    }));
6054                    Expression::Div(Box::new(BinaryOp {
6055                        left: paren_modded,
6056                        right: Expression::Literal(Box::new(Literal::Number(
6057                            "1000000.0".to_string(),
6058                        ))),
6059                        left_comments: Vec::new(),
6060                        operator_comments: Vec::new(),
6061                        trailing_comments: Vec::new(),
6062                        inferred_type: None,
6063                    }))
6064                };
6065
6066                // Determine u1 and u2 based on gen type
6067                let is_random_no_seed = match &gen {
6068                    Expression::Random(_) => true,
6069                    Expression::Rand(r) => r.seed.is_none(),
6070                    _ => false,
6071                };
6072                let (u1, u2) = if is_random_no_seed {
6073                    // RANDOM() -> u1 = RANDOM(), u2 = RANDOM()
6074                    let u1 =
6075                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])));
6076                    let u2 =
6077                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])));
6078                    (u1, u2)
6079                } else {
6080                    // Seed-based: extract the seed value
6081                    let seed = match gen {
6082                        Expression::Rand(r) => r.seed.map(|s| *s).unwrap_or(Expression::number(0)),
6083                        Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
6084                            if func.args.len() == 1 {
6085                                func.args.into_iter().next().unwrap()
6086                            } else {
6087                                Expression::number(0)
6088                            }
6089                        }
6090                        other => other,
6091                    };
6092                    let u1 = make_seed_random(seed.clone());
6093                    let seed_plus_1 = Expression::Add(Box::new(BinaryOp {
6094                        left: seed,
6095                        right: Expression::number(1),
6096                        left_comments: Vec::new(),
6097                        operator_comments: Vec::new(),
6098                        trailing_comments: Vec::new(),
6099                        inferred_type: None,
6100                    }));
6101                    let u2 = make_seed_random(seed_plus_1);
6102                    (u1, u2)
6103                };
6104
6105                // GREATEST(u1, 1e-10)
6106                let greatest = Expression::Greatest(Box::new(VarArgFunc {
6107                    expressions: vec![
6108                        u1,
6109                        Expression::Literal(Box::new(Literal::Number("1e-10".to_string()))),
6110                    ],
6111                    original_name: None,
6112                    inferred_type: None,
6113                }));
6114
6115                // SQRT(-2 * LN(GREATEST(u1, 1e-10)))
6116                let neg2 = Expression::Neg(Box::new(crate::expressions::UnaryOp {
6117                    this: Expression::number(2),
6118                    inferred_type: None,
6119                }));
6120                let ln_greatest =
6121                    Expression::Function(Box::new(Function::new("LN".to_string(), vec![greatest])));
6122                let neg2_times_ln = Expression::Mul(Box::new(BinaryOp {
6123                    left: neg2,
6124                    right: ln_greatest,
6125                    left_comments: Vec::new(),
6126                    operator_comments: Vec::new(),
6127                    trailing_comments: Vec::new(),
6128                    inferred_type: None,
6129                }));
6130                let sqrt_part = Expression::Function(Box::new(Function::new(
6131                    "SQRT".to_string(),
6132                    vec![neg2_times_ln],
6133                )));
6134
6135                // COS(2 * PI() * u2)
6136                let pi = Expression::Function(Box::new(Function::new("PI".to_string(), vec![])));
6137                let two_pi = Expression::Mul(Box::new(BinaryOp {
6138                    left: Expression::number(2),
6139                    right: pi,
6140                    left_comments: Vec::new(),
6141                    operator_comments: Vec::new(),
6142                    trailing_comments: Vec::new(),
6143                    inferred_type: None,
6144                }));
6145                let two_pi_u2 = if let Expression::Div(div) = u2 {
6146                    let numerator = Expression::Mul(Box::new(BinaryOp::new(two_pi, div.left)));
6147                    Expression::Div(Box::new(BinaryOp::new(numerator, div.right)))
6148                } else {
6149                    Expression::Mul(Box::new(BinaryOp::new(two_pi, u2)))
6150                };
6151                let cos_part = Expression::Function(Box::new(Function::new(
6152                    "COS".to_string(),
6153                    vec![two_pi_u2],
6154                )));
6155
6156                // stddev * sqrt_part * cos_part
6157                let stddev_times_sqrt = Expression::Mul(Box::new(BinaryOp {
6158                    left: stddev,
6159                    right: sqrt_part,
6160                    left_comments: Vec::new(),
6161                    operator_comments: Vec::new(),
6162                    trailing_comments: Vec::new(),
6163                    inferred_type: None,
6164                }));
6165                let inner = Expression::Mul(Box::new(BinaryOp {
6166                    left: stddev_times_sqrt,
6167                    right: cos_part,
6168                    left_comments: Vec::new(),
6169                    operator_comments: Vec::new(),
6170                    trailing_comments: Vec::new(),
6171                    inferred_type: None,
6172                }));
6173                let paren_inner = Expression::Paren(Box::new(Paren {
6174                    this: inner,
6175                    trailing_comments: Vec::new(),
6176                }));
6177
6178                // mean + (inner)
6179                Ok(Expression::Add(Box::new(BinaryOp {
6180                    left: mean,
6181                    right: paren_inner,
6182                    left_comments: Vec::new(),
6183                    operator_comments: Vec::new(),
6184                    trailing_comments: Vec::new(),
6185                    inferred_type: None,
6186                })))
6187            }
6188
6189            // DATE_TRUNC: DuckDB supports natively, just pass through
6190            // (DuckDB returns the correct type automatically)
6191
6192            // BITOR/BITAND with BITSHIFT need parenthesization
6193            // This is handled via the BITOR/BITAND transforms which create BitwiseOr/BitwiseAnd
6194            // The issue is operator precedence: BITOR(BITSHIFTLEFT(a, b), BITSHIFTLEFT(c, d))
6195            // should generate (a << b) | (c << d), not a << b | c << d
6196
6197            // ZIPF(s, n, gen) -> CTE-based emulation for DuckDB
6198            "ZIPF" if f.args.len() == 3 => {
6199                let mut args = f.args;
6200                let s_expr = args.remove(0);
6201                let n_expr = args.remove(0);
6202                let gen_expr = args.remove(0);
6203
6204                let s_sql = Self::expr_to_sql(&s_expr);
6205                let n_sql = Self::expr_to_sql(&n_expr);
6206                let (seed_sql, is_random) = Self::extract_seed_info(&gen_expr);
6207
6208                let rand_sql = if is_random {
6209                    format!("SELECT {} AS r", seed_sql)
6210                } else {
6211                    format!(
6212                        "SELECT (ABS(HASH({})) % 1000000) / 1000000.0 AS r",
6213                        seed_sql
6214                    )
6215                };
6216
6217                let template = format!(
6218                    "WITH rand AS ({}), weights AS (SELECT i, 1.0 / POWER(i, {}) AS w FROM RANGE(1, {} + 1) AS t(i)), cdf AS (SELECT i, SUM(w) OVER (ORDER BY i NULLS FIRST) / SUM(w) OVER () AS p FROM weights) SELECT MIN(i) FROM cdf WHERE p >= (SELECT r FROM rand)",
6219                    rand_sql, s_sql, n_sql
6220                );
6221
6222                Self::parse_as_subquery(&template)
6223            }
6224
6225            // RANDSTR(len, gen) -> subquery-based emulation for DuckDB
6226            "RANDSTR" if f.args.len() == 2 => {
6227                let mut args = f.args;
6228                let len_expr = args.remove(0);
6229                let gen_expr = args.remove(0);
6230
6231                let len_sql = Self::expr_to_sql(&len_expr);
6232                let (seed_sql, is_random) = Self::extract_seed_info(&gen_expr);
6233
6234                let random_value_sql = if is_random {
6235                    format!("(ABS(HASH(i + {})) % 1000) / 1000.0", seed_sql)
6236                } else {
6237                    format!("(ABS(HASH(i + {})) % 1000) / 1000.0", seed_sql)
6238                };
6239
6240                let template = format!(
6241                    "SELECT LISTAGG(SUBSTRING('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 + CAST(FLOOR(random_value * 62) AS INT), 1), '') FROM (SELECT {} AS random_value FROM RANGE({}) AS t(i))",
6242                    random_value_sql, len_sql
6243                );
6244
6245                Self::parse_as_subquery(&template)
6246            }
6247
6248            // MAP_CAT(map1, map2) -> explicit merge semantics for DuckDB
6249            "MAP_CAT" if f.args.len() == 2 => {
6250                let mut args = f.args;
6251                let left = Self::normalize_empty_map_expr(args.remove(0));
6252                let right = Self::normalize_empty_map_expr(args.remove(0));
6253                let left_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6254                    this: left.clone(),
6255                    not: false,
6256                    postfix_form: false,
6257                }));
6258                let right_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6259                    this: right.clone(),
6260                    not: false,
6261                    postfix_form: false,
6262                }));
6263                let null_cond = Expression::Or(Box::new(BinaryOp {
6264                    left: left_is_null,
6265                    right: right_is_null,
6266                    left_comments: Vec::new(),
6267                    operator_comments: Vec::new(),
6268                    trailing_comments: Vec::new(),
6269                    inferred_type: None,
6270                }));
6271
6272                let list_concat = Expression::Function(Box::new(Function::new(
6273                    "LIST_CONCAT".to_string(),
6274                    vec![
6275                        Expression::Function(Box::new(Function::new(
6276                            "MAP_KEYS".to_string(),
6277                            vec![left.clone()],
6278                        ))),
6279                        Expression::Function(Box::new(Function::new(
6280                            "MAP_KEYS".to_string(),
6281                            vec![right.clone()],
6282                        ))),
6283                    ],
6284                )));
6285                let list_distinct = Expression::Function(Box::new(Function::new(
6286                    "LIST_DISTINCT".to_string(),
6287                    vec![list_concat],
6288                )));
6289
6290                let k_ident = Identifier::new("__k");
6291                let k_ref = Expression::boxed_column(Column {
6292                    table: None,
6293                    name: k_ident.clone(),
6294                    join_mark: false,
6295                    trailing_comments: Vec::new(),
6296                    span: None,
6297                    inferred_type: None,
6298                });
6299                let right_key = Expression::Subscript(Box::new(crate::expressions::Subscript {
6300                    this: right.clone(),
6301                    index: k_ref.clone(),
6302                }));
6303                let left_key = Expression::Subscript(Box::new(crate::expressions::Subscript {
6304                    this: left.clone(),
6305                    index: k_ref.clone(),
6306                }));
6307                let key_value = Expression::Coalesce(Box::new(VarArgFunc {
6308                    expressions: vec![right_key, left_key],
6309                    original_name: None,
6310                    inferred_type: None,
6311                }));
6312                let struct_pack = Expression::Function(Box::new(Function::new(
6313                    "STRUCT_PACK".to_string(),
6314                    vec![
6315                        Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
6316                            name: Identifier::new("key"),
6317                            value: k_ref.clone(),
6318                            separator: crate::expressions::NamedArgSeparator::ColonEq,
6319                        })),
6320                        Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
6321                            name: Identifier::new("value"),
6322                            value: key_value,
6323                            separator: crate::expressions::NamedArgSeparator::ColonEq,
6324                        })),
6325                    ],
6326                )));
6327                let lambda_k = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
6328                    parameters: vec![k_ident],
6329                    body: struct_pack,
6330                    colon: false,
6331                    parameter_types: Vec::new(),
6332                }));
6333
6334                let list_transform = Expression::Function(Box::new(Function::new(
6335                    "LIST_TRANSFORM".to_string(),
6336                    vec![list_distinct, lambda_k],
6337                )));
6338
6339                let x_ident = Identifier::new("__x");
6340                let x_ref = Expression::boxed_column(Column {
6341                    table: None,
6342                    name: x_ident.clone(),
6343                    join_mark: false,
6344                    trailing_comments: Vec::new(),
6345                    span: None,
6346                    inferred_type: None,
6347                });
6348                let x_value = Expression::Dot(Box::new(crate::expressions::DotAccess {
6349                    this: x_ref,
6350                    field: Identifier::new("value"),
6351                    inferred_type: None,
6352                }));
6353                let x_value_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6354                    this: x_value,
6355                    not: false,
6356                    postfix_form: false,
6357                }));
6358                let lambda_x = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
6359                    parameters: vec![x_ident],
6360                    body: Expression::Not(Box::new(crate::expressions::UnaryOp {
6361                        this: x_value_is_null,
6362                        inferred_type: None,
6363                    })),
6364                    colon: false,
6365                    parameter_types: Vec::new(),
6366                }));
6367
6368                let list_filter = Expression::Function(Box::new(Function::new(
6369                    "LIST_FILTER".to_string(),
6370                    vec![list_transform, lambda_x],
6371                )));
6372                let merged_map = Expression::Function(Box::new(Function::new(
6373                    "MAP_FROM_ENTRIES".to_string(),
6374                    vec![list_filter],
6375                )));
6376
6377                Ok(Expression::Case(Box::new(Case {
6378                    operand: None,
6379                    whens: vec![(null_cond, Expression::Null(crate::expressions::Null))],
6380                    else_: Some(merged_map),
6381                    comments: Vec::new(),
6382                    inferred_type: None,
6383                })))
6384            }
6385
6386            // MINHASH(num_perm, value) -> DuckDB emulation using JSON state payload
6387            "MINHASH" if f.args.len() == 2 => {
6388                let mut args = f.args;
6389                let num_perm = args.remove(0);
6390                let value = args.remove(0);
6391
6392                let num_perm_sql = Self::expr_to_sql(&num_perm);
6393                let value_sql = Self::expr_to_sql(&value);
6394
6395                let template = format!(
6396                    "SELECT JSON_OBJECT('state', LIST(min_h ORDER BY seed NULLS FIRST), 'type', 'minhash', 'version', 1) FROM (SELECT seed, LIST_MIN(LIST_TRANSFORM(vals, __v -> HASH(CAST(__v AS TEXT) || CAST(seed AS TEXT)))) AS min_h FROM (SELECT LIST({value}) AS vals), RANGE(0, {num_perm}) AS t(seed))",
6397                    value = value_sql,
6398                    num_perm = num_perm_sql
6399                );
6400
6401                Self::parse_as_subquery(&template)
6402            }
6403
6404            // MINHASH_COMBINE(sig) -> merge minhash JSON signatures in DuckDB
6405            "MINHASH_COMBINE" if f.args.len() == 1 => {
6406                let sig_sql = Self::expr_to_sql(&f.args[0]);
6407                let template = format!(
6408                    "SELECT JSON_OBJECT('state', LIST(min_h ORDER BY idx NULLS FIRST), 'type', 'minhash', 'version', 1) FROM (SELECT pos AS idx, MIN(val) AS min_h FROM UNNEST(LIST({sig})) AS _(sig) JOIN UNNEST(CAST(sig -> '$.state' AS UBIGINT[])) WITH ORDINALITY AS t(val, pos) ON TRUE GROUP BY pos)",
6409                    sig = sig_sql
6410                );
6411                Self::parse_as_subquery(&template)
6412            }
6413
6414            // APPROXIMATE_SIMILARITY(sig) -> jaccard estimate from minhash signatures
6415            "APPROXIMATE_SIMILARITY" if f.args.len() == 1 => {
6416                let sig_sql = Self::expr_to_sql(&f.args[0]);
6417                let template = format!(
6418                    "SELECT CAST(SUM(CASE WHEN num_distinct = 1 THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*) FROM (SELECT pos, COUNT(DISTINCT h) AS num_distinct FROM (SELECT h, pos FROM UNNEST(LIST({sig})) AS _(sig) JOIN UNNEST(CAST(sig -> '$.state' AS UBIGINT[])) WITH ORDINALITY AS s(h, pos) ON TRUE) GROUP BY pos)",
6419                    sig = sig_sql
6420                );
6421                Self::parse_as_subquery(&template)
6422            }
6423
6424            // ARRAYS_ZIP(a1, a2, ...) -> struct list construction in DuckDB
6425            "ARRAYS_ZIP" if !f.args.is_empty() => {
6426                let args = f.args;
6427                let n = args.len();
6428                let is_null = |expr: Expression| {
6429                    Expression::IsNull(Box::new(crate::expressions::IsNull {
6430                        this: expr,
6431                        not: false,
6432                        postfix_form: false,
6433                    }))
6434                };
6435                let length_of = |expr: Expression| {
6436                    Expression::Function(Box::new(Function::new("LENGTH".to_string(), vec![expr])))
6437                };
6438                let eq_zero = |expr: Expression| {
6439                    Expression::Eq(Box::new(BinaryOp {
6440                        left: expr,
6441                        right: Expression::number(0),
6442                        left_comments: Vec::new(),
6443                        operator_comments: Vec::new(),
6444                        trailing_comments: Vec::new(),
6445                        inferred_type: None,
6446                    }))
6447                };
6448                let and_expr = |left: Expression, right: Expression| {
6449                    Expression::And(Box::new(BinaryOp {
6450                        left,
6451                        right,
6452                        left_comments: Vec::new(),
6453                        operator_comments: Vec::new(),
6454                        trailing_comments: Vec::new(),
6455                        inferred_type: None,
6456                    }))
6457                };
6458                let or_expr = |left: Expression, right: Expression| {
6459                    Expression::Or(Box::new(BinaryOp {
6460                        left,
6461                        right,
6462                        left_comments: Vec::new(),
6463                        operator_comments: Vec::new(),
6464                        trailing_comments: Vec::new(),
6465                        inferred_type: None,
6466                    }))
6467                };
6468
6469                let null_cond = args.iter().cloned().map(is_null).reduce(or_expr).unwrap();
6470                let empty_cond = args
6471                    .iter()
6472                    .cloned()
6473                    .map(|a| eq_zero(length_of(a)))
6474                    .reduce(and_expr)
6475                    .unwrap();
6476
6477                let null_struct = Expression::Struct(Box::new(Struct {
6478                    fields: (1..=n)
6479                        .map(|i| {
6480                            (
6481                                Some(format!("${}", i)),
6482                                Expression::Null(crate::expressions::Null),
6483                            )
6484                        })
6485                        .collect(),
6486                }));
6487                let empty_result = Expression::Array(Box::new(crate::expressions::Array {
6488                    expressions: vec![null_struct],
6489                    inferred_type: None,
6490                }));
6491
6492                let range_upper = if n == 1 {
6493                    length_of(args[0].clone())
6494                } else {
6495                    let length_null_cond = args
6496                        .iter()
6497                        .cloned()
6498                        .map(|a| is_null(length_of(a)))
6499                        .reduce(or_expr)
6500                        .unwrap();
6501                    let greatest_len = Expression::Greatest(Box::new(VarArgFunc {
6502                        expressions: args.iter().cloned().map(length_of).collect(),
6503                        original_name: None,
6504                        inferred_type: None,
6505                    }));
6506                    Expression::Case(Box::new(Case {
6507                        operand: None,
6508                        whens: vec![(length_null_cond, Expression::Null(crate::expressions::Null))],
6509                        else_: Some(greatest_len),
6510                        comments: Vec::new(),
6511                        inferred_type: None,
6512                    }))
6513                };
6514
6515                let range_expr = Expression::Function(Box::new(Function::new(
6516                    "RANGE".to_string(),
6517                    vec![Expression::number(0), range_upper],
6518                )));
6519
6520                let i_ident = Identifier::new("__i");
6521                let i_ref = Expression::boxed_column(Column {
6522                    table: None,
6523                    name: i_ident.clone(),
6524                    join_mark: false,
6525                    trailing_comments: Vec::new(),
6526                    span: None,
6527                    inferred_type: None,
6528                });
6529                let i_plus_one = Expression::Add(Box::new(BinaryOp {
6530                    left: i_ref,
6531                    right: Expression::number(1),
6532                    left_comments: Vec::new(),
6533                    operator_comments: Vec::new(),
6534                    trailing_comments: Vec::new(),
6535                    inferred_type: None,
6536                }));
6537                let empty_array = Expression::Array(Box::new(crate::expressions::Array {
6538                    expressions: vec![],
6539                    inferred_type: None,
6540                }));
6541                let zipped_struct = Expression::Struct(Box::new(Struct {
6542                    fields: args
6543                        .iter()
6544                        .enumerate()
6545                        .map(|(i, a)| {
6546                            let coalesced = Expression::Coalesce(Box::new(VarArgFunc {
6547                                expressions: vec![a.clone(), empty_array.clone()],
6548                                original_name: None,
6549                                inferred_type: None,
6550                            }));
6551                            let item =
6552                                Expression::Subscript(Box::new(crate::expressions::Subscript {
6553                                    this: coalesced,
6554                                    index: i_plus_one.clone(),
6555                                }));
6556                            (Some(format!("${}", i + 1)), item)
6557                        })
6558                        .collect(),
6559                }));
6560                let lambda_i = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
6561                    parameters: vec![i_ident],
6562                    body: zipped_struct,
6563                    colon: false,
6564                    parameter_types: Vec::new(),
6565                }));
6566                let zipped_result = Expression::Function(Box::new(Function::new(
6567                    "LIST_TRANSFORM".to_string(),
6568                    vec![range_expr, lambda_i],
6569                )));
6570
6571                Ok(Expression::Case(Box::new(Case {
6572                    operand: None,
6573                    whens: vec![
6574                        (null_cond, Expression::Null(crate::expressions::Null)),
6575                        (empty_cond, empty_result),
6576                    ],
6577                    else_: Some(zipped_result),
6578                    comments: Vec::new(),
6579                    inferred_type: None,
6580                })))
6581            }
6582
6583            // STRTOK(str, delim, pos) -> complex CASE expression
6584            // Snowflake's STRTOK treats each character in delim as a separate delimiter,
6585            // so we use REGEXP_SPLIT_TO_ARRAY with a character class regex.
6586            "STRTOK" if f.args.len() == 3 => {
6587                let mut args = f.args.into_iter();
6588                let str_arg = args.next().unwrap();
6589                let delim_arg = args.next().unwrap();
6590                let pos_arg = args.next().unwrap();
6591
6592                // Helper: create empty string literal ''
6593                let empty_str = || Expression::string("".to_string());
6594                // Helper: create NULL literal
6595                let null_expr = || Expression::Null(crate::expressions::Null);
6596
6597                // WHEN delim = '' AND str = '' THEN NULL
6598                let when1_cond = Expression::And(Box::new(BinaryOp::new(
6599                    Expression::Eq(Box::new(BinaryOp::new(delim_arg.clone(), empty_str()))),
6600                    Expression::Eq(Box::new(BinaryOp::new(str_arg.clone(), empty_str()))),
6601                )));
6602
6603                // WHEN delim = '' AND pos = 1 THEN str
6604                let when2_cond = Expression::And(Box::new(BinaryOp::new(
6605                    Expression::Eq(Box::new(BinaryOp::new(delim_arg.clone(), empty_str()))),
6606                    Expression::Eq(Box::new(BinaryOp::new(
6607                        pos_arg.clone(),
6608                        Expression::number(1),
6609                    ))),
6610                )));
6611
6612                // WHEN delim = '' THEN NULL
6613                let when3_cond =
6614                    Expression::Eq(Box::new(BinaryOp::new(delim_arg.clone(), empty_str())));
6615
6616                // WHEN pos < 0 THEN NULL
6617                let when4_cond = Expression::Lt(Box::new(BinaryOp::new(
6618                    pos_arg.clone(),
6619                    Expression::number(0),
6620                )));
6621
6622                // WHEN str IS NULL OR delim IS NULL OR pos IS NULL THEN NULL
6623                let str_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6624                    this: str_arg.clone(),
6625                    not: false,
6626                    postfix_form: false,
6627                }));
6628                let delim_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6629                    this: delim_arg.clone(),
6630                    not: false,
6631                    postfix_form: false,
6632                }));
6633                let pos_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
6634                    this: pos_arg.clone(),
6635                    not: false,
6636                    postfix_form: false,
6637                }));
6638                let when5_cond = Expression::Or(Box::new(BinaryOp::new(
6639                    Expression::Or(Box::new(BinaryOp::new(str_is_null, delim_is_null))),
6640                    pos_is_null,
6641                )));
6642
6643                // Inner CASE for the regex pattern:
6644                // CASE WHEN delim = '' THEN ''
6645                //      ELSE '[' || REGEXP_REPLACE(delim, '([\[\]^.\-*+?(){}|$\\])', '\\\1', 'g') || ']'
6646                // END
6647                let regex_replace = Expression::Function(Box::new(Function::new(
6648                    "REGEXP_REPLACE".to_string(),
6649                    vec![
6650                        delim_arg.clone(),
6651                        Expression::string(r"([\[\]^.\-*+?(){}|$\\])".to_string()),
6652                        Expression::string(r"\\\1".to_string()),
6653                        Expression::string("g".to_string()),
6654                    ],
6655                )));
6656
6657                // '[' || REGEXP_REPLACE(...) || ']'
6658                let concat_regex = Expression::DPipe(Box::new(crate::expressions::DPipe {
6659                    this: Box::new(Expression::DPipe(Box::new(crate::expressions::DPipe {
6660                        this: Box::new(Expression::string("[".to_string())),
6661                        expression: Box::new(regex_replace),
6662                        safe: None,
6663                    }))),
6664                    expression: Box::new(Expression::string("]".to_string())),
6665                    safe: None,
6666                }));
6667
6668                let inner_case = Expression::Case(Box::new(Case {
6669                    operand: None,
6670                    whens: vec![(
6671                        Expression::Eq(Box::new(BinaryOp::new(delim_arg.clone(), empty_str()))),
6672                        empty_str(),
6673                    )],
6674                    else_: Some(concat_regex),
6675                    comments: Vec::new(),
6676                    inferred_type: None,
6677                }));
6678
6679                // REGEXP_SPLIT_TO_ARRAY(str, <inner_case>)
6680                let regexp_split = Expression::Function(Box::new(Function::new(
6681                    "REGEXP_SPLIT_TO_ARRAY".to_string(),
6682                    vec![str_arg.clone(), inner_case],
6683                )));
6684
6685                // Lambda: x -> NOT x = ''
6686                let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
6687                    parameters: vec![Identifier::new("x".to_string())],
6688                    body: Expression::Not(Box::new(crate::expressions::UnaryOp {
6689                        this: Expression::Eq(Box::new(BinaryOp::new(
6690                            Expression::boxed_column(Column {
6691                                table: None,
6692                                name: Identifier::new("x".to_string()),
6693                                join_mark: false,
6694                                trailing_comments: Vec::new(),
6695                                span: None,
6696                                inferred_type: None,
6697                            }),
6698                            empty_str(),
6699                        ))),
6700                        inferred_type: None,
6701                    })),
6702                    colon: false,
6703                    parameter_types: Vec::new(),
6704                }));
6705
6706                // LIST_FILTER(<regexp_split>, <lambda>)
6707                let list_filter = Expression::Function(Box::new(Function::new(
6708                    "LIST_FILTER".to_string(),
6709                    vec![regexp_split, lambda],
6710                )));
6711
6712                // LIST_FILTER(...)[pos]
6713                let subscripted = Expression::Subscript(Box::new(crate::expressions::Subscript {
6714                    this: list_filter,
6715                    index: pos_arg.clone(),
6716                }));
6717
6718                Ok(Expression::Case(Box::new(Case {
6719                    operand: None,
6720                    whens: vec![
6721                        (when1_cond, null_expr()),
6722                        (when2_cond, str_arg.clone()),
6723                        (when3_cond, null_expr()),
6724                        (when4_cond, null_expr()),
6725                        (when5_cond, null_expr()),
6726                    ],
6727                    else_: Some(subscripted),
6728                    comments: Vec::new(),
6729                    inferred_type: None,
6730                })))
6731            }
6732
6733            // Pass through everything else
6734            _ => Ok(Expression::Function(Box::new(f))),
6735        }
6736    }
6737
6738    /// Convert Snowflake date format to DuckDB strptime format
6739    fn convert_snowflake_date_format(&self, fmt: Expression) -> Expression {
6740        match fmt {
6741            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
6742                let Literal::String(s) = lit.as_ref() else {
6743                    unreachable!()
6744                };
6745                let converted = Self::snowflake_to_strptime(&s);
6746                Expression::Literal(Box::new(Literal::String(converted)))
6747            }
6748            _ => fmt,
6749        }
6750    }
6751
6752    /// Convert Snowflake time format to DuckDB strptime format
6753    fn convert_snowflake_time_format(&self, fmt: Expression) -> Expression {
6754        match fmt {
6755            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
6756                let Literal::String(s) = lit.as_ref() else {
6757                    unreachable!()
6758                };
6759                let converted = Self::snowflake_to_strptime(&s);
6760                Expression::Literal(Box::new(Literal::String(converted)))
6761            }
6762            _ => fmt,
6763        }
6764    }
6765
6766    /// Token-based conversion from Snowflake format strings (both original and normalized) to DuckDB strptime format.
6767    /// Handles both uppercase Snowflake originals (YYYY, MM, DD) and normalized lowercase forms (yyyy, mm, DD).
6768    fn snowflake_to_strptime(s: &str) -> String {
6769        let mut result = String::new();
6770        let mut cursor = crate::format_tokens::FormatTokenCursor::new(s);
6771        while let Some(ch) = cursor.peek() {
6772            // Compound patterns first
6773            if cursor.consume_prefix("HH24MISS", true) {
6774                result.push_str("%H%M%S");
6775            } else if cursor.consume_prefix("MMMM", true) {
6776                result.push_str("%B");
6777            } else if cursor.consume_prefix("YYYY", true) {
6778                result.push_str("%Y");
6779            } else if cursor.consume_prefix("YY", true) {
6780                result.push_str("%y");
6781            } else if cursor.consume_prefix("MON", true) {
6782                result.push_str("%b");
6783            } else if cursor.consume_prefix("HH24", true) {
6784                result.push_str("%H");
6785            } else if cursor.consume_prefix("HH12", true) {
6786                result.push_str("%I");
6787            } else if cursor.consume_prefix("HH", true) {
6788                result.push_str("%I");
6789            } else if cursor.consume_prefix("MISS", true) {
6790                result.push_str("%M%S");
6791            } else if cursor.consume_prefix("MI", true) {
6792                result.push_str("%M");
6793            } else if cursor.consume_prefix("MM", true) {
6794                result.push_str("%m");
6795            } else if cursor.consume_prefix("DD", true) {
6796                result.push_str("%d");
6797            } else if cursor.consume_prefix("DY", true) {
6798                result.push_str("%a");
6799            } else if cursor.consume_prefix("SS", true) {
6800                result.push_str("%S");
6801            } else if cursor.consume_prefix("FF", true) {
6802                // Preserve the existing one-digit precision rule: FF7-FF9 use
6803                // nanoseconds; FF0-FF6 and bare FF use microseconds.
6804                if let Some(digit) = cursor.peek().filter(|c| c.is_ascii_digit()) {
6805                    result.push_str(if digit >= '7' { "%n" } else { "%f" });
6806                    cursor.next_char();
6807                } else {
6808                    result.push_str("%f");
6809                }
6810            } else if cursor.consume_prefix("PM", true) || cursor.consume_prefix("AM", true) {
6811                result.push_str("%p");
6812            } else if cursor.consume_prefix("TZH", true) {
6813                result.push_str("%z");
6814            } else if cursor.consume_prefix("TZM", true) {
6815                // TZM is part of timezone, skip
6816            } else {
6817                result.push(ch);
6818                cursor.next_char();
6819            }
6820        }
6821        result
6822    }
6823
6824    /// Convert BigQuery format string to DuckDB strptime format
6825    /// BigQuery: %E6S -> DuckDB: %S.%f (seconds with microseconds)
6826    fn convert_bq_to_strptime_format(&self, fmt: Expression) -> Expression {
6827        match fmt {
6828            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
6829                let Literal::String(s) = lit.as_ref() else {
6830                    unreachable!()
6831                };
6832                let converted = s.replace("%E6S", "%S.%f").replace("%E*S", "%S.%f");
6833                Expression::Literal(Box::new(Literal::String(converted)))
6834            }
6835            _ => fmt,
6836        }
6837    }
6838
6839    /// Transform DATE_PART(unit, expr) for DuckDB
6840    fn transform_date_part(&self, args: Vec<Expression>) -> Result<Expression> {
6841        let mut args = args;
6842        let unit_expr = args.remove(0);
6843        let date_expr = args.remove(0);
6844        let unit_name = match &unit_expr {
6845            Expression::Column(c) => c.name.name.to_uppercase(),
6846            Expression::Identifier(i) => i.name.to_uppercase(),
6847            Expression::Var(v) => v.this.to_uppercase(),
6848            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
6849                let Literal::String(s) = lit.as_ref() else {
6850                    unreachable!()
6851                };
6852                s.to_uppercase()
6853            }
6854            _ => {
6855                return Ok(Expression::Function(Box::new(Function::new(
6856                    "DATE_PART".to_string(),
6857                    vec![unit_expr, date_expr],
6858                ))))
6859            }
6860        };
6861        match unit_name.as_str() {
6862            "EPOCH_SECOND" | "EPOCH" => Ok(Expression::Cast(Box::new(Cast {
6863                this: Expression::Function(Box::new(Function::new(
6864                    "EPOCH".to_string(),
6865                    vec![date_expr],
6866                ))),
6867                to: DataType::BigInt { length: None },
6868                trailing_comments: Vec::new(),
6869                double_colon_syntax: false,
6870                format: None,
6871                default: None,
6872                inferred_type: None,
6873            }))),
6874            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => Ok(Expression::Function(Box::new(
6875                Function::new("EPOCH_MS".to_string(), vec![date_expr]),
6876            ))),
6877            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => Ok(Expression::Function(Box::new(
6878                Function::new("EPOCH_US".to_string(), vec![date_expr]),
6879            ))),
6880            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => Ok(Expression::Function(Box::new(
6881                Function::new("EPOCH_NS".to_string(), vec![date_expr]),
6882            ))),
6883            "DAYOFWEEKISO" | "DAYOFWEEK_ISO" => Ok(Expression::Extract(Box::new(
6884                crate::expressions::ExtractFunc {
6885                    this: date_expr,
6886                    field: crate::expressions::DateTimeField::Custom("ISODOW".to_string()),
6887                },
6888            ))),
6889            "YEAROFWEEK" | "YEAROFWEEKISO" => Ok(Expression::Cast(Box::new(Cast {
6890                this: Expression::Function(Box::new(Function::new(
6891                    "STRFTIME".to_string(),
6892                    vec![
6893                        date_expr,
6894                        Expression::Literal(Box::new(Literal::String("%G".to_string()))),
6895                    ],
6896                ))),
6897                to: DataType::Int {
6898                    length: None,
6899                    integer_spelling: false,
6900                },
6901                trailing_comments: Vec::new(),
6902                double_colon_syntax: false,
6903                format: None,
6904                default: None,
6905                inferred_type: None,
6906            }))),
6907            "WEEKISO" => Ok(Expression::Cast(Box::new(Cast {
6908                this: Expression::Function(Box::new(Function::new(
6909                    "STRFTIME".to_string(),
6910                    vec![
6911                        date_expr,
6912                        Expression::Literal(Box::new(Literal::String("%V".to_string()))),
6913                    ],
6914                ))),
6915                to: DataType::Int {
6916                    length: None,
6917                    integer_spelling: false,
6918                },
6919                trailing_comments: Vec::new(),
6920                double_colon_syntax: false,
6921                format: None,
6922                default: None,
6923                inferred_type: None,
6924            }))),
6925            "NANOSECOND" | "NANOSECONDS" | "NS" => Ok(Expression::Cast(Box::new(Cast {
6926                this: Expression::Function(Box::new(Function::new(
6927                    "STRFTIME".to_string(),
6928                    vec![
6929                        Expression::Cast(Box::new(Cast {
6930                            this: date_expr,
6931                            to: DataType::Custom {
6932                                name: "TIMESTAMP_NS".to_string(),
6933                            },
6934                            trailing_comments: Vec::new(),
6935                            double_colon_syntax: false,
6936                            format: None,
6937                            default: None,
6938                            inferred_type: None,
6939                        })),
6940                        Expression::Literal(Box::new(Literal::String("%n".to_string()))),
6941                    ],
6942                ))),
6943                to: DataType::BigInt { length: None },
6944                trailing_comments: Vec::new(),
6945                double_colon_syntax: false,
6946                format: None,
6947                default: None,
6948                inferred_type: None,
6949            }))),
6950            "DAYOFMONTH" => Ok(Expression::Extract(Box::new(
6951                crate::expressions::ExtractFunc {
6952                    this: date_expr,
6953                    field: crate::expressions::DateTimeField::Day,
6954                },
6955            ))),
6956            _ => {
6957                let field = match unit_name.as_str() {
6958                    "YEAR" | "YY" | "YYYY" => crate::expressions::DateTimeField::Year,
6959                    "MONTH" | "MON" | "MM" => crate::expressions::DateTimeField::Month,
6960                    "DAY" | "DD" | "D" => crate::expressions::DateTimeField::Day,
6961                    "HOUR" | "HH" => crate::expressions::DateTimeField::Hour,
6962                    "MINUTE" | "MI" | "MIN" => crate::expressions::DateTimeField::Minute,
6963                    "SECOND" | "SEC" | "SS" => crate::expressions::DateTimeField::Second,
6964                    "MILLISECOND" | "MS" => crate::expressions::DateTimeField::Millisecond,
6965                    "MICROSECOND" | "US" => crate::expressions::DateTimeField::Microsecond,
6966                    "QUARTER" | "QTR" => crate::expressions::DateTimeField::Quarter,
6967                    "WEEK" | "WK" => crate::expressions::DateTimeField::Week,
6968                    "DAYOFWEEK" | "DOW" => crate::expressions::DateTimeField::DayOfWeek,
6969                    "DAYOFYEAR" | "DOY" => crate::expressions::DateTimeField::DayOfYear,
6970                    "TIMEZONE_HOUR" => crate::expressions::DateTimeField::TimezoneHour,
6971                    "TIMEZONE_MINUTE" => crate::expressions::DateTimeField::TimezoneMinute,
6972                    _ => crate::expressions::DateTimeField::Custom(unit_name),
6973                };
6974                Ok(Expression::Extract(Box::new(
6975                    crate::expressions::ExtractFunc {
6976                        this: date_expr,
6977                        field,
6978                    },
6979                )))
6980            }
6981        }
6982    }
6983
6984    /// Transform DATEADD(unit, amount, date) for DuckDB
6985    fn transform_dateadd(&self, args: Vec<Expression>) -> Result<Expression> {
6986        let mut args = args;
6987        let unit_expr = args.remove(0);
6988        let amount = args.remove(0);
6989        let date = args.remove(0);
6990        let unit_name = match &unit_expr {
6991            Expression::Column(c) => c.name.name.to_uppercase(),
6992            Expression::Identifier(i) => i.name.to_uppercase(),
6993            Expression::Var(v) => v.this.to_uppercase(),
6994            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
6995                let Literal::String(s) = lit.as_ref() else {
6996                    unreachable!()
6997                };
6998                s.to_uppercase()
6999            }
7000            _ => String::new(),
7001        };
7002        if unit_name == "NANOSECOND" || unit_name == "NS" {
7003            let epoch_ns = Expression::Function(Box::new(Function::new(
7004                "EPOCH_NS".to_string(),
7005                vec![Expression::Cast(Box::new(Cast {
7006                    this: date,
7007                    to: DataType::Custom {
7008                        name: "TIMESTAMP_NS".to_string(),
7009                    },
7010                    trailing_comments: Vec::new(),
7011                    double_colon_syntax: false,
7012                    format: None,
7013                    default: None,
7014                    inferred_type: None,
7015                }))],
7016            )));
7017            return Ok(Expression::Function(Box::new(Function::new(
7018                "MAKE_TIMESTAMP_NS".to_string(),
7019                vec![Expression::Add(Box::new(BinaryOp {
7020                    left: epoch_ns,
7021                    right: amount,
7022                    left_comments: Vec::new(),
7023                    operator_comments: Vec::new(),
7024                    trailing_comments: Vec::new(),
7025                    inferred_type: None,
7026                }))],
7027            ))));
7028        }
7029        let (interval_unit, multiplied_amount) = match unit_name.as_str() {
7030            "YEAR" | "YY" | "YYYY" => (IntervalUnit::Year, amount),
7031            "MONTH" | "MON" | "MM" => (IntervalUnit::Month, amount),
7032            "DAY" | "DD" | "D" => (IntervalUnit::Day, amount),
7033            "HOUR" | "HH" => (IntervalUnit::Hour, amount),
7034            "MINUTE" | "MI" | "MIN" => (IntervalUnit::Minute, amount),
7035            "SECOND" | "SEC" | "SS" => (IntervalUnit::Second, amount),
7036            "MILLISECOND" | "MS" => (IntervalUnit::Millisecond, amount),
7037            "MICROSECOND" | "US" => (IntervalUnit::Microsecond, amount),
7038            "WEEK" | "WK" => (
7039                IntervalUnit::Day,
7040                Expression::Mul(Box::new(BinaryOp {
7041                    left: amount,
7042                    right: Expression::number(7),
7043                    left_comments: Vec::new(),
7044                    operator_comments: Vec::new(),
7045                    trailing_comments: Vec::new(),
7046                    inferred_type: None,
7047                })),
7048            ),
7049            "QUARTER" | "QTR" => (
7050                IntervalUnit::Month,
7051                Expression::Mul(Box::new(BinaryOp {
7052                    left: amount,
7053                    right: Expression::number(3),
7054                    left_comments: Vec::new(),
7055                    operator_comments: Vec::new(),
7056                    trailing_comments: Vec::new(),
7057                    inferred_type: None,
7058                })),
7059            ),
7060            _ => (IntervalUnit::Day, amount),
7061        };
7062        Ok(Expression::Add(Box::new(BinaryOp {
7063            left: date,
7064            right: Expression::Interval(Box::new(Interval {
7065                this: Some(multiplied_amount),
7066                unit: Some(IntervalUnitSpec::Simple {
7067                    unit: interval_unit,
7068                    use_plural: false,
7069                }),
7070            })),
7071            left_comments: Vec::new(),
7072            operator_comments: Vec::new(),
7073            trailing_comments: Vec::new(),
7074            inferred_type: None,
7075        })))
7076    }
7077
7078    /// Transform DATEDIFF(unit, start, end) for DuckDB
7079    fn transform_datediff(&self, args: Vec<Expression>) -> Result<Expression> {
7080        let mut args = args;
7081        let unit_expr = args.remove(0);
7082        let start = args.remove(0);
7083        let end = args.remove(0);
7084        let unit_name = match &unit_expr {
7085            Expression::Column(c) => c.name.name.to_uppercase(),
7086            Expression::Identifier(i) => i.name.to_uppercase(),
7087            Expression::Var(v) => v.this.to_uppercase(),
7088            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
7089                let Literal::String(s) = lit.as_ref() else {
7090                    unreachable!()
7091                };
7092                s.to_uppercase()
7093            }
7094            _ => String::new(),
7095        };
7096        if unit_name == "NANOSECOND" || unit_name == "NS" {
7097            let epoch_end = Expression::Function(Box::new(Function::new(
7098                "EPOCH_NS".to_string(),
7099                vec![Expression::Cast(Box::new(Cast {
7100                    this: end,
7101                    to: DataType::Custom {
7102                        name: "TIMESTAMP_NS".to_string(),
7103                    },
7104                    trailing_comments: Vec::new(),
7105                    double_colon_syntax: false,
7106                    format: None,
7107                    default: None,
7108                    inferred_type: None,
7109                }))],
7110            )));
7111            let epoch_start = Expression::Function(Box::new(Function::new(
7112                "EPOCH_NS".to_string(),
7113                vec![Expression::Cast(Box::new(Cast {
7114                    this: start,
7115                    to: DataType::Custom {
7116                        name: "TIMESTAMP_NS".to_string(),
7117                    },
7118                    trailing_comments: Vec::new(),
7119                    double_colon_syntax: false,
7120                    format: None,
7121                    default: None,
7122                    inferred_type: None,
7123                }))],
7124            )));
7125            return Ok(Expression::Sub(Box::new(BinaryOp {
7126                left: epoch_end,
7127                right: epoch_start,
7128                left_comments: Vec::new(),
7129                operator_comments: Vec::new(),
7130                trailing_comments: Vec::new(),
7131                inferred_type: None,
7132            })));
7133        }
7134        if unit_name == "WEEK" || unit_name == "WK" {
7135            let trunc_start = Expression::Function(Box::new(Function::new(
7136                "DATE_TRUNC".to_string(),
7137                vec![
7138                    Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
7139                    Expression::Cast(Box::new(Cast {
7140                        this: start,
7141                        to: DataType::Date,
7142                        trailing_comments: Vec::new(),
7143                        double_colon_syntax: false,
7144                        format: None,
7145                        default: None,
7146                        inferred_type: None,
7147                    })),
7148                ],
7149            )));
7150            let trunc_end = Expression::Function(Box::new(Function::new(
7151                "DATE_TRUNC".to_string(),
7152                vec![
7153                    Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
7154                    Expression::Cast(Box::new(Cast {
7155                        this: end,
7156                        to: DataType::Date,
7157                        trailing_comments: Vec::new(),
7158                        double_colon_syntax: false,
7159                        format: None,
7160                        default: None,
7161                        inferred_type: None,
7162                    })),
7163                ],
7164            )));
7165            return Ok(Expression::Function(Box::new(Function::new(
7166                "DATE_DIFF".to_string(),
7167                vec![
7168                    Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
7169                    trunc_start,
7170                    trunc_end,
7171                ],
7172            ))));
7173        }
7174        let cast_if_string = |e: Expression| -> Expression {
7175            match &e {
7176                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
7177                    Expression::Cast(Box::new(Cast {
7178                        this: e,
7179                        to: DataType::Date,
7180                        trailing_comments: Vec::new(),
7181                        double_colon_syntax: false,
7182                        format: None,
7183                        default: None,
7184                        inferred_type: None,
7185                    }))
7186                }
7187                _ => e,
7188            }
7189        };
7190        let start = cast_if_string(start);
7191        let end = cast_if_string(end);
7192        Ok(Expression::Function(Box::new(Function::new(
7193            "DATE_DIFF".to_string(),
7194            vec![
7195                Expression::Literal(Box::new(Literal::String(unit_name))),
7196                start,
7197                end,
7198            ],
7199        ))))
7200    }
7201
7202    fn transform_aggregate_function(
7203        &self,
7204        f: Box<crate::expressions::AggregateFunction>,
7205    ) -> Result<Expression> {
7206        let name_upper = f.name.to_uppercase();
7207        match name_upper.as_str() {
7208            // Keep the established canonical transpilation for a plain DuckDB
7209            // LIST aggregate now that the parser represents it as an aggregate
7210            // node. Modifier-bearing LIST calls retain their aggregate shape so
7211            // DISTINCT, ORDER BY, FILTER, and LIMIT are not discarded.
7212            "LIST"
7213                if f.args.len() == 1
7214                    && !f.distinct
7215                    && f.filter.is_none()
7216                    && f.order_by.is_empty()
7217                    && f.limit.is_none()
7218                    && f.ignore_nulls.is_none() =>
7219            {
7220                Ok(Expression::Function(Box::new(Function::new(
7221                    "ARRAY_AGG".to_string(),
7222                    f.args,
7223                ))))
7224            }
7225
7226            // GROUP_CONCAT -> LISTAGG
7227            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
7228                Function::new("LISTAGG".to_string(), f.args),
7229            ))),
7230
7231            // LISTAGG is native to DuckDB
7232            "LISTAGG" => Ok(Expression::AggregateFunction(f)),
7233
7234            // STRING_AGG -> LISTAGG
7235            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
7236                Function::new("LISTAGG".to_string(), f.args),
7237            ))),
7238
7239            // ARRAY_AGG -> list (or array_agg, both work)
7240            "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
7241                "list".to_string(),
7242                f.args,
7243            )))),
7244
7245            // LOGICAL_OR -> BOOL_OR with CAST to BOOLEAN
7246            "LOGICAL_OR" if !f.args.is_empty() => {
7247                let arg = f.args.into_iter().next().unwrap();
7248                Ok(Expression::Function(Box::new(Function::new(
7249                    "BOOL_OR".to_string(),
7250                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
7251                        this: arg,
7252                        to: crate::expressions::DataType::Boolean,
7253                        trailing_comments: Vec::new(),
7254                        double_colon_syntax: false,
7255                        format: None,
7256                        default: None,
7257                        inferred_type: None,
7258                    }))],
7259                ))))
7260            }
7261
7262            // LOGICAL_AND -> BOOL_AND with CAST to BOOLEAN
7263            "LOGICAL_AND" if !f.args.is_empty() => {
7264                let arg = f.args.into_iter().next().unwrap();
7265                Ok(Expression::Function(Box::new(Function::new(
7266                    "BOOL_AND".to_string(),
7267                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
7268                        this: arg,
7269                        to: crate::expressions::DataType::Boolean,
7270                        trailing_comments: Vec::new(),
7271                        double_colon_syntax: false,
7272                        format: None,
7273                        default: None,
7274                        inferred_type: None,
7275                    }))],
7276                ))))
7277            }
7278
7279            // SKEW -> SKEWNESS
7280            "SKEW" => Ok(Expression::Function(Box::new(Function::new(
7281                "SKEWNESS".to_string(),
7282                f.args,
7283            )))),
7284
7285            // REGR_VALX(y, x) -> CASE WHEN y IS NULL THEN CAST(NULL AS DOUBLE) ELSE x END
7286            "REGR_VALX" if f.args.len() == 2 => {
7287                let mut args = f.args;
7288                let y = args.remove(0);
7289                let x = args.remove(0);
7290                Ok(Expression::Case(Box::new(Case {
7291                    operand: None,
7292                    whens: vec![(
7293                        Expression::IsNull(Box::new(crate::expressions::IsNull {
7294                            this: y,
7295                            not: false,
7296                            postfix_form: false,
7297                        })),
7298                        Expression::Cast(Box::new(Cast {
7299                            this: Expression::Null(crate::expressions::Null),
7300                            to: DataType::Double {
7301                                precision: None,
7302                                scale: None,
7303                            },
7304                            trailing_comments: Vec::new(),
7305                            double_colon_syntax: false,
7306                            format: None,
7307                            default: None,
7308                            inferred_type: None,
7309                        })),
7310                    )],
7311                    else_: Some(x),
7312                    comments: Vec::new(),
7313                    inferred_type: None,
7314                })))
7315            }
7316
7317            // REGR_VALY(y, x) -> CASE WHEN x IS NULL THEN CAST(NULL AS DOUBLE) ELSE y END
7318            "REGR_VALY" if f.args.len() == 2 => {
7319                let mut args = f.args;
7320                let y = args.remove(0);
7321                let x = args.remove(0);
7322                Ok(Expression::Case(Box::new(Case {
7323                    operand: None,
7324                    whens: vec![(
7325                        Expression::IsNull(Box::new(crate::expressions::IsNull {
7326                            this: x,
7327                            not: false,
7328                            postfix_form: false,
7329                        })),
7330                        Expression::Cast(Box::new(Cast {
7331                            this: Expression::Null(crate::expressions::Null),
7332                            to: DataType::Double {
7333                                precision: None,
7334                                scale: None,
7335                            },
7336                            trailing_comments: Vec::new(),
7337                            double_colon_syntax: false,
7338                            format: None,
7339                            default: None,
7340                            inferred_type: None,
7341                        })),
7342                    )],
7343                    else_: Some(y),
7344                    comments: Vec::new(),
7345                    inferred_type: None,
7346                })))
7347            }
7348
7349            // BOOLAND_AGG -> BOOL_AND(CAST(arg AS BOOLEAN))
7350            "BOOLAND_AGG" if !f.args.is_empty() => {
7351                let arg = f.args.into_iter().next().unwrap();
7352                Ok(Expression::Function(Box::new(Function::new(
7353                    "BOOL_AND".to_string(),
7354                    vec![Expression::Cast(Box::new(Cast {
7355                        this: arg,
7356                        to: DataType::Boolean,
7357                        trailing_comments: Vec::new(),
7358                        double_colon_syntax: false,
7359                        format: None,
7360                        default: None,
7361                        inferred_type: None,
7362                    }))],
7363                ))))
7364            }
7365
7366            // BOOLOR_AGG -> BOOL_OR(CAST(arg AS BOOLEAN))
7367            "BOOLOR_AGG" if !f.args.is_empty() => {
7368                let arg = f.args.into_iter().next().unwrap();
7369                Ok(Expression::Function(Box::new(Function::new(
7370                    "BOOL_OR".to_string(),
7371                    vec![Expression::Cast(Box::new(Cast {
7372                        this: arg,
7373                        to: DataType::Boolean,
7374                        trailing_comments: Vec::new(),
7375                        double_colon_syntax: false,
7376                        format: None,
7377                        default: None,
7378                        inferred_type: None,
7379                    }))],
7380                ))))
7381            }
7382
7383            // BOOLXOR_AGG(c) -> COUNT_IF(CAST(c AS BOOLEAN)) = 1
7384            "BOOLXOR_AGG" if !f.args.is_empty() => {
7385                let arg = f.args.into_iter().next().unwrap();
7386                Ok(Expression::Eq(Box::new(BinaryOp {
7387                    left: Expression::Function(Box::new(Function::new(
7388                        "COUNT_IF".to_string(),
7389                        vec![Expression::Cast(Box::new(Cast {
7390                            this: arg,
7391                            to: DataType::Boolean,
7392                            trailing_comments: Vec::new(),
7393                            double_colon_syntax: false,
7394                            format: None,
7395                            default: None,
7396                            inferred_type: None,
7397                        }))],
7398                    ))),
7399                    right: Expression::number(1),
7400                    left_comments: Vec::new(),
7401                    operator_comments: Vec::new(),
7402                    trailing_comments: Vec::new(),
7403                    inferred_type: None,
7404                })))
7405            }
7406
7407            // MAX_BY -> ARG_MAX
7408            "MAX_BY" if f.args.len() == 2 => Ok(Expression::AggregateFunction(Box::new(
7409                crate::expressions::AggregateFunction {
7410                    name: "ARG_MAX".to_string(),
7411                    ..(*f)
7412                },
7413            ))),
7414
7415            // MIN_BY -> ARG_MIN
7416            "MIN_BY" if f.args.len() == 2 => Ok(Expression::AggregateFunction(Box::new(
7417                crate::expressions::AggregateFunction {
7418                    name: "ARG_MIN".to_string(),
7419                    ..(*f)
7420                },
7421            ))),
7422
7423            // CORR - pass through (DuckDB handles NaN natively)
7424            "CORR" if f.args.len() == 2 => Ok(Expression::AggregateFunction(f)),
7425
7426            // BITMAP_CONSTRUCT_AGG(v) -> complex DuckDB subquery emulation
7427            "BITMAP_CONSTRUCT_AGG" if f.args.len() == 1 => {
7428                let v_sql = Self::expr_to_sql(&f.args[0]);
7429
7430                let template = format!(
7431                    "SELECT CASE WHEN l IS NULL OR LENGTH(l) = 0 THEN NULL WHEN LENGTH(l) <> LENGTH(LIST_FILTER(l, __v -> __v BETWEEN 0 AND 32767)) THEN NULL WHEN LENGTH(l) < 5 THEN UNHEX(PRINTF('%04X', LENGTH(l)) || h || REPEAT('00', GREATEST(0, 4 - LENGTH(l)) * 2)) ELSE UNHEX('08000000000000000000' || h) END FROM (SELECT l, COALESCE(LIST_REDUCE(LIST_TRANSFORM(l, __x -> PRINTF('%02X%02X', CAST(__x AS INT) & 255, (CAST(__x AS INT) >> 8) & 255)), (__a, __b) -> __a || __b, ''), '') AS h FROM (SELECT LIST_SORT(LIST_DISTINCT(LIST({v}) FILTER(WHERE NOT {v} IS NULL))) AS l))",
7432                    v = v_sql
7433                );
7434
7435                Self::parse_as_subquery(&template)
7436            }
7437
7438            // Pass through everything else
7439            _ => Ok(Expression::AggregateFunction(f)),
7440        }
7441    }
7442
7443    /// Convert Presto/MySQL format string to DuckDB format string
7444    /// DuckDB uses strftime/strptime C-style format specifiers
7445    /// Key difference: %i (Presto minutes) -> %M (DuckDB minutes)
7446    fn convert_format_to_duckdb(expr: &Expression) -> Expression {
7447        if let Expression::Literal(lit) = expr {
7448            if let Literal::String(s) = lit.as_ref() {
7449                let duckdb_fmt = Self::presto_to_duckdb_format(s);
7450                Expression::Literal(Box::new(Literal::String(duckdb_fmt)))
7451            } else {
7452                expr.clone()
7453            }
7454        } else {
7455            expr.clone()
7456        }
7457    }
7458
7459    /// Convert Presto format specifiers to DuckDB strftime format
7460    fn presto_to_duckdb_format(fmt: &str) -> String {
7461        let mut result = String::new();
7462        let chars: Vec<char> = fmt.chars().collect();
7463        let mut i = 0;
7464        while i < chars.len() {
7465            if chars[i] == '%' && i + 1 < chars.len() {
7466                match chars[i + 1] {
7467                    'i' => {
7468                        // Presto %i (minutes) -> DuckDB %M (minutes)
7469                        result.push_str("%M");
7470                        i += 2;
7471                    }
7472                    'T' => {
7473                        // Presto %T (time shorthand %H:%M:%S)
7474                        result.push_str("%H:%M:%S");
7475                        i += 2;
7476                    }
7477                    'F' => {
7478                        // Presto %F (date shorthand %Y-%m-%d)
7479                        result.push_str("%Y-%m-%d");
7480                        i += 2;
7481                    }
7482                    _ => {
7483                        result.push('%');
7484                        result.push(chars[i + 1]);
7485                        i += 2;
7486                    }
7487                }
7488            } else {
7489                result.push(chars[i]);
7490                i += 1;
7491            }
7492        }
7493        result
7494    }
7495}
7496
7497#[cfg(test)]
7498mod tests {
7499    use super::*;
7500    use crate::dialects::Dialect;
7501    use crate::expressions::JoinKind;
7502
7503    #[test]
7504    fn test_date_time_format_conversion_preserves_unicode() {
7505        for literal in ["é", "年", "🦀", "e\u{301}", "ß", "ſ"] {
7506            assert_eq!(DuckDBDialect::snowflake_to_strptime(literal), literal);
7507            assert_eq!(
7508                DuckDBDialect::snowflake_to_strptime(&format!("{literal}YYYY{literal}mM{literal}")),
7509                format!("{literal}%Y{literal}%m{literal}")
7510            );
7511        }
7512        for (input, expected) in [
7513            ("", ""),
7514            (
7515                "HH24MISS MMMM YYYY YY MON HH24 HH12 HH MISS MI MM DD DY SS PM AM TZH TZM",
7516                "%H%M%S %B %Y %y %b %H %I %I %M%S %M %m %d %a %S %p %p %z ",
7517            ),
7518            ("FF FF0 FF1 FF6 FF7 FF9 FF69", "%f %f %f %f %n %n %f9"),
7519            ("éFF7年YYYY", "é%n年%Y"),
7520            ("\"å¹´\"YYYY %q", "\"å¹´\"%Y %q"),
7521        ] {
7522            assert_eq!(
7523                DuckDBDialect::snowflake_to_strptime(input),
7524                expected,
7525                "{input}"
7526            );
7527        }
7528    }
7529
7530    fn transpile_to_duckdb(sql: &str) -> String {
7531        transpile_to_duckdb_from(sql, DialectType::Generic)
7532    }
7533
7534    fn transpile_to_duckdb_from(sql: &str, read: DialectType) -> String {
7535        let dialect = Dialect::get(read);
7536        let result = dialect
7537            .transpile(sql, DialectType::DuckDB)
7538            .expect("Transpile failed");
7539        result[0].clone()
7540    }
7541
7542    #[test]
7543    fn test_athena_max_by_preserves_filter() {
7544        let sql = "SELECT max_by(value,seq) FILTER (WHERE keep) AS answer FROM (VALUES ('kept',1,true),('ignored',2,false)) t(value,seq,keep)";
7545        assert_eq!(
7546            transpile_to_duckdb_from(sql, DialectType::Athena),
7547            "SELECT ARG_MAX_NULL(value, seq) FILTER(WHERE keep) AS answer FROM (VALUES ('kept', 1, TRUE), ('ignored', 2, FALSE)) AS t(value, seq, keep)"
7548        );
7549
7550        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
7551            for (source, target) in [("MAX_BY", "ARG_MAX"), ("MIN_BY", "ARG_MIN")] {
7552                for args in ["value, seq", "value, seq, 2"] {
7553                    let target = if args == "value, seq" {
7554                        format!("{target}_NULL")
7555                    } else {
7556                        target.to_string()
7557                    };
7558                    for suffix in ["", " OVER (PARTITION BY category)"] {
7559                        let sql =
7560                            format!("SELECT {source}({args}) FILTER (WHERE keep){suffix} FROM t");
7561                        assert_eq!(
7562                            transpile_to_duckdb_from(&sql, read),
7563                            format!("SELECT {target}({args}) FILTER(WHERE keep){suffix} FROM t"),
7564                            "{read:?}: {sql}"
7565                        );
7566                    }
7567                }
7568            }
7569        }
7570    }
7571
7572    #[test]
7573    fn test_athena_max_by_preserves_null_winner() {
7574        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
7575            for (source, target) in [("MAX_BY", "ARG_MAX_NULL"), ("MIN_BY", "ARG_MIN_NULL")] {
7576                let sql = format!(
7577                    "SELECT {source}(value, seq) AS answer FROM (VALUES ('older', 1), (CAST(NULL AS VARCHAR), 2)) t(value, seq)"
7578                );
7579                assert_eq!(
7580                    transpile_to_duckdb_from(&sql, read),
7581                    format!("SELECT {target}(value, seq) AS answer FROM (VALUES ('older', 1), (CAST(NULL AS TEXT), 2)) AS t(value, seq)"),
7582                    "{read:?}: {sql}"
7583                );
7584                assert_eq!(
7585                    transpile_to_duckdb_from(
7586                        &format!("SELECT {source}(DISTINCT value, seq) FILTER (WHERE keep) FROM t"),
7587                        read,
7588                    ),
7589                    format!("SELECT {target}(DISTINCT value, seq) FILTER(WHERE keep) FROM t")
7590                );
7591            }
7592        }
7593
7594        // DuckDB's native aliases intentionally skip NULL values.
7595        for (source, target) in [("MAX_BY", "ARG_MAX"), ("MIN_BY", "ARG_MIN")] {
7596            assert_eq!(
7597                transpile_to_duckdb_from(
7598                    &format!("SELECT {source}(value, seq) FROM t"),
7599                    DialectType::DuckDB,
7600                ),
7601                format!("SELECT {target}(value, seq) FROM t")
7602            );
7603        }
7604    }
7605
7606    #[test]
7607    fn test_athena_regexp_extract_preserves_null_and_empty_matches() {
7608        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
7609            for args in [
7610                "'no-match', '([0-9]+)', 1",
7611                "'abc', '(z*)a', 1",
7612                "'abc', '(z)?a', 1",
7613                "'abc', '(a)(b)(c)'",
7614                "'abc', '(a)(b)(c)', 2",
7615                "'12 34', '[0-9]+'",
7616                "'', '()'",
7617                "NULL, '([0-9]+)', 1",
7618                "'abc', NULL, 1",
7619                "value, pattern, group_index",
7620            ] {
7621                assert_eq!(
7622                    transpile_to_duckdb_from(
7623                        &format!("SELECT REGEXP_EXTRACT({args}) AS answer"),
7624                        read,
7625                    ),
7626                    format!("SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL({args}), 1) AS answer"),
7627                    "{read:?}: {args}"
7628                );
7629            }
7630        }
7631
7632        assert_eq!(
7633            transpile_to_duckdb_from(
7634                "SELECT REGEXP_EXTRACT('no-match', '([0-9]+)', 1)",
7635                DialectType::DuckDB,
7636            ),
7637            "SELECT REGEXP_EXTRACT('no-match', '([0-9]+)', 1)"
7638        );
7639    }
7640
7641    #[test]
7642    fn test_athena_regexp_replace_capture_references() {
7643        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
7644            for (sql, expected) in [
7645                (
7646                    "SELECT regexp_replace('2026.09 2027.10', '([0-9]+)[.]([0-9]+)', '$1-$2') AS answer",
7647                    r"SELECT REGEXP_REPLACE('2026.09 2027.10', '([0-9]+)[.]([0-9]+)', '\1-\2', 'g') AS answer",
7648                ),
7649                (
7650                    "SELECT regexp_replace(value, pattern, '$0/$1/$2') FROM t",
7651                    r"SELECT REGEXP_REPLACE(value, pattern, '\0/\1/\2', 'g') FROM t",
7652                ),
7653                (
7654                    r"SELECT regexp_replace('aa', '(a)', '\$1')",
7655                    "SELECT REGEXP_REPLACE('aa', '(a)', '$1', 'g')",
7656                ),
7657                (
7658                    "SELECT regexp_replace('a1b2', '[0-9]')",
7659                    "SELECT REGEXP_REPLACE('a1b2', '[0-9]', '', 'g')",
7660                ),
7661                (
7662                    "SELECT regexp_replace('aa', 'a', 'x')",
7663                    "SELECT REGEXP_REPLACE('aa', 'a', 'x', 'g')",
7664                ),
7665            ] {
7666                assert_eq!(transpile_to_duckdb_from(sql, read), expected, "{read:?}: {sql}");
7667            }
7668        }
7669
7670        assert_eq!(
7671            transpile_to_duckdb_from(
7672                "SELECT REGEXP_REPLACE('aa', '(a)', '$1')",
7673                DialectType::DuckDB,
7674            ),
7675            "SELECT REGEXP_REPLACE('aa', '(a)', '$1')"
7676        );
7677    }
7678
7679    // Optional execution assertions complement the SQL assertions on machines
7680    // with DuckDB installed, without making it a dependency of the Rust suite.
7681    fn assert_duckdb_value(sql: &str, expected: &str) {
7682        let Ok(engine) = std::env::var("POLYGLOT_DUCKDB") else {
7683            return;
7684        };
7685        for timezone in ["UTC", "America/New_York"] {
7686            let query = format!("SET TimeZone = '{timezone}'; {sql}");
7687            let output = std::process::Command::new(&engine)
7688                .args([
7689                    "-init",
7690                    "/dev/null",
7691                    "-noheader",
7692                    "-list",
7693                    "-nullvalue",
7694                    "NULL",
7695                    ":memory:",
7696                    &query,
7697                ])
7698                .output()
7699                .expect("run POLYGLOT_DUCKDB");
7700            assert!(
7701                output.status.success(),
7702                "{query}: {}",
7703                String::from_utf8_lossy(&output.stderr)
7704            );
7705            assert_eq!(
7706                String::from_utf8(output.stdout).unwrap().trim_end(),
7707                expected,
7708                "{query}"
7709            );
7710        }
7711    }
7712
7713    #[test]
7714    fn test_bigquery_numeric_precision_to_duckdb_479() {
7715        for (sql, expected_sql, expected_value) in [
7716            (
7717                "SELECT CAST(1.005 AS NUMERIC(10, 2)) AS a",
7718                "SELECT CAST(1.005 AS DECIMAL(10, 2)) AS a",
7719                "1.01",
7720            ),
7721            (
7722                "SELECT CAST(0.1249 AS NUMERIC)",
7723                "SELECT CAST(0.1249 AS DECIMAL(38, 9))",
7724                "0.124900000",
7725            ),
7726            (
7727                "SELECT ROUND(CAST(0.1249 AS NUMERIC), 2)",
7728                "SELECT ROUND(CAST(0.1249 AS DECIMAL(38, 9)), 2)",
7729                "0.12",
7730            ),
7731            (
7732                "SELECT SAFE_CAST('0.1249' AS NUMERIC)",
7733                "SELECT TRY_CAST('0.1249' AS DECIMAL(38, 9))",
7734                "0.124900000",
7735            ),
7736            (
7737                "SELECT NUMERIC '0.000000001'",
7738                "SELECT CAST('0.000000001' AS DECIMAL(38, 9))",
7739                "0.000000001",
7740            ),
7741        ] {
7742            let output = transpile_to_duckdb_from(sql, DialectType::BigQuery);
7743            assert_eq!(output, expected_sql, "{sql}");
7744            assert_duckdb_value(&output, expected_value);
7745        }
7746        for (sql, expected) in [
7747            ("SELECT CAST(-1.005 AS DECIMAL(10, 2))", "-1.01"),
7748            ("SELECT CAST(1.6 AS NUMERIC(10))", "2"),
7749            ("SELECT ROUND(CAST(-0.1249 AS NUMERIC), 2)", "-0.12"),
7750            ("SELECT SAFE_CAST('invalid' AS NUMERIC)", "NULL"),
7751            (
7752                "SELECT CAST('12345678901234567890123456789.123456789' AS NUMERIC)",
7753                "12345678901234567890123456789.123456789",
7754            ),
7755            (
7756                "SELECT COALESCE(CAST(NULL AS NUMERIC), DECIMAL '0.1249')",
7757                "0.124900000",
7758            ),
7759            (
7760                "SELECT CAST(['0.1249'] AS ARRAY<NUMERIC>)[OFFSET(0)]",
7761                "0.124900000",
7762            ),
7763            ("SELECT ABS(CAST(-0.1249 AS NUMERIC))", "0.124900000"),
7764            (
7765                "SELECT ROUND(NUMERIC '2.25', 1, 'ROUND_HALF_AWAY_FROM_ZERO')",
7766                "2.3",
7767            ),
7768            ("SELECT ROUND(NUMERIC '2.25', 1, 'ROUND_HALF_EVEN')", "2.2"),
7769            (
7770                "SELECT ROUND(NUMERIC '0.1249', 2, 'ROUND_HALF_AWAY_FROM_ZERO')",
7771                "0.12",
7772            ),
7773            (
7774                "SELECT ROUND(NUMERIC '0.1349', 2, 'ROUND_HALF_EVEN')",
7775                "0.13",
7776            ),
7777        ] {
7778            let output = transpile_to_duckdb_from(sql, DialectType::BigQuery);
7779            assert_duckdb_value(&output, expected);
7780        }
7781        let ddl = transpile_to_duckdb_from(
7782            "CREATE TABLE t (a NUMERIC, b NUMERIC(10, 2), c ARRAY<NUMERIC>, d STRUCT<n NUMERIC>)",
7783            DialectType::BigQuery,
7784        );
7785        assert_duckdb_value(
7786            &format!("{ddl}; INSERT INTO t VALUES ('0.1249', 1.005, ['0.1249'], {{'n': '0.1249'}}); SELECT a, b, c[1], d.n FROM t"),
7787            "0.124900000|1.01|0.124900000|0.124900000",
7788        );
7789        assert_eq!(
7790            transpile_to_duckdb_from("SELECT CAST(0.1249 AS NUMERIC)", DialectType::DuckDB),
7791            "SELECT CAST(0.1249 AS DECIMAL(18, 3))"
7792        );
7793    }
7794
7795    #[test]
7796    fn test_bigquery_named_parameters_to_duckdb_478() {
7797        let unnest = transpile_to_duckdb_from(
7798            "SELECT * FROM UNNEST(@sites) AS site",
7799            DialectType::BigQuery,
7800        );
7801        assert_eq!(unnest, "SELECT * FROM UNNEST($sites) AS _t0(site)");
7802        assert_duckdb_value(
7803            &format!("PREPARE q AS {unnest}; EXECUTE q(sites := ['alpha', 'beta'])"),
7804            "alpha\nbeta",
7805        );
7806        let membership = transpile_to_duckdb_from(
7807            "SELECT x FROM t WHERE id IN UNNEST(@ids)",
7808            DialectType::BigQuery,
7809        );
7810        assert!(!membership.contains("ABS("), "{membership}");
7811        assert!(
7812            membership.contains("ARRAY_CONTAINS($ids, id)"),
7813            "{membership}"
7814        );
7815        for (ids, expected) in [
7816            ("[2]", "b"),
7817            ("[]", ""),
7818            ("NULL::INTEGER[]", ""),
7819            ("[NULL, 1]", "a"),
7820        ] {
7821            assert_duckdb_value(
7822                &format!("CREATE TABLE t AS SELECT 1 AS id, 'a' AS x UNION ALL SELECT 2, 'b'; PREPARE q AS {membership}; EXECUTE q(ids := {ids})"), expected,
7823            );
7824        }
7825        for (sql, expected_sql, binding, value) in [
7826            ("SELECT @name + 1", "SELECT $name + 1", "name := 6", "7"),
7827            ("SELECT @select", "SELECT $select", "\"select\" := 7", "7"),
7828            ("SELECT @`a b`", "SELECT $\"a b\"", "\"a b\" := 7", "7"),
7829            (
7830                "SELECT @`a\"b`",
7831                "SELECT $\"a\"\"b\"",
7832                "\"a\"\"b\" := 7",
7833                "7",
7834            ),
7835            (
7836                "SELECT @param.dataField",
7837                "SELECT $param.dataField",
7838                "param := {'dataField': 7}",
7839                "7",
7840            ),
7841            (
7842                "SELECT @ids[OFFSET(0)]",
7843                "SELECT $ids[1]",
7844                "ids := [7, 8]",
7845                "7",
7846            ),
7847        ] {
7848            let output = transpile_to_duckdb_from(sql, DialectType::BigQuery);
7849            assert_eq!(output, expected_sql, "{sql}");
7850            assert_eq!(
7851                transpile_to_duckdb_from(&output, DialectType::DuckDB),
7852                output
7853            );
7854            assert_duckdb_value(
7855                &format!("PREPARE q AS {output}; EXECUTE q({binding})"),
7856                value,
7857            );
7858        }
7859        for dialect in [
7860            DialectType::PostgreSQL,
7861            DialectType::DuckDB,
7862            DialectType::Vertica,
7863        ] {
7864            assert_eq!(
7865                transpile_to_duckdb_from("SELECT @(-7)", dialect),
7866                "SELECT ABS((-7))"
7867            );
7868            let output = transpile_to_duckdb_from("SELECT @n FROM (SELECT -7 AS n) AS t", dialect);
7869            assert!(output.contains("ABS(n)"), "{dialect:?}: {output}");
7870            assert_duckdb_value(&output, "7");
7871        }
7872        let reverse = Dialect::get(DialectType::DuckDB)
7873            .transpile("SELECT $\"a b\"", DialectType::BigQuery)
7874            .unwrap();
7875        assert_eq!(reverse[0], "SELECT @`a b`");
7876    }
7877
7878    #[test]
7879    fn test_bigquery_variable_interval_to_duckdb_477() {
7880        for (sql, expected_sql, value) in [
7881            (
7882                "SELECT DATE_ADD(DATE '2026-01-01', INTERVAL n MONTH) AS d FROM (SELECT 2 AS n)",
7883                "SELECT CAST('2026-01-01' AS DATE) + INTERVAL (n) MONTH AS d FROM (SELECT 2 AS n)",
7884                "2026-03-01 00:00:00",
7885            ),
7886            (
7887                "SELECT DATE_ADD(DATE '2026-01-01', INTERVAL DATE_DIFF(DATE '2026-03-15', DATE '2026-01-01', MONTH) MONTH) AS d",
7888                "SELECT CAST('2026-01-01' AS DATE) + INTERVAL (DATE_DIFF('MONTH', CAST('2026-01-01' AS DATE), CAST('2026-03-15' AS DATE))) MONTH AS d",
7889                "2026-03-01 00:00:00",
7890            ),
7891        ] {
7892            let output = transpile_to_duckdb_from(sql, DialectType::BigQuery);
7893            assert_eq!(output, expected_sql);
7894            assert_duckdb_value(&output, value);
7895        }
7896        for (amount, expected) in [
7897            ("(n)", "2026-03-01 00:00:00"),
7898            ("(n + 1)", "2026-04-01 00:00:00"),
7899            ("ABS(-n)", "2026-03-01 00:00:00"),
7900            ("CASE WHEN n > 0 THEN n ELSE 1 END", "2026-03-01 00:00:00"),
7901            ("-n", "2025-11-01 00:00:00"),
7902            ("NULL", "NULL"),
7903        ] {
7904            let sql = format!(
7905                "SELECT DATE_ADD(DATE '2026-01-01', INTERVAL {amount} MONTH) FROM (SELECT 2 AS n)"
7906            );
7907            let output = transpile_to_duckdb_from(&sql, DialectType::BigQuery);
7908            assert_duckdb_value(&output, expected);
7909        }
7910        for (sql, expected) in [
7911            ("SELECT INTERVAL (n) MONTH FROM (SELECT 2 AS n)", "2 months"),
7912            (
7913                "SELECT INTERVAL (ABS(n)) MONTH FROM (SELECT -2 AS n)",
7914                "2 months",
7915            ),
7916            (
7917                "SELECT INTERVAL (s) FROM (SELECT '1 month' AS s)",
7918                "1 month",
7919            ),
7920            ("SELECT INTERVAL '2' MONTH", "2 months"),
7921        ] {
7922            let output = transpile_to_duckdb_from(sql, DialectType::DuckDB);
7923            assert!(!output.contains("INTERVAL (("), "{output}");
7924            assert_duckdb_value(&output, expected);
7925        }
7926        let output = transpile_to_duckdb_from(
7927            "SELECT DATE_SUB(DATE '2026-01-01', INTERVAL DATE_DIFF(DATE '2026-03-15', DATE '2026-01-01', MONTH) MONTH)",
7928            DialectType::BigQuery,
7929        );
7930        assert_duckdb_value(&output, "2025-11-01 00:00:00");
7931    }
7932
7933    #[test]
7934    fn test_bigquery_date_timezone_to_duckdb_476() {
7935        let source = Dialect::get(DialectType::BigQuery);
7936        for (argument, zone, expected) in [
7937            (
7938                "TIMESTAMP '2026-01-31 23:30:00+00'",
7939                "'Europe/Berlin'",
7940                "2026-02-01",
7941            ),
7942            (
7943                "TIMESTAMP '2016-12-25 05:30:00+07'",
7944                "'America/Los_Angeles'",
7945                "2016-12-24",
7946            ),
7947            (
7948                "TIMESTAMP '2026-07-31 22:30:00+00'",
7949                "'Europe/Berlin'",
7950                "2026-08-01",
7951            ),
7952            (
7953                "TIMESTAMP '2026-01-31 23:30:00-05:00'",
7954                "'UTC'",
7955                "2026-02-01",
7956            ),
7957            (
7958                "TIMESTAMP '2026-01-31T23:30:00Z'",
7959                "'Europe/Berlin'",
7960                "2026-02-01",
7961            ),
7962            (
7963                "TIMESTAMP '2026-01-31 23:30:00 America/New_York'",
7964                "'UTC'",
7965                "2026-02-01",
7966            ),
7967            (
7968                "TIMESTAMP '2016-12-25'",
7969                "'America/Los_Angeles'",
7970                "2016-12-24",
7971            ),
7972            (
7973                "TIMESTAMP '2026-01-31 23:30:00'",
7974                "'Europe/Berlin'",
7975                "2026-02-01",
7976            ),
7977            ("'2026-01-31 23:30:00'", "'Europe/Berlin'", "2026-02-01"),
7978            (
7979                "TIMESTAMP('2026-01-31 23:30:00')",
7980                "'Europe/Berlin'",
7981                "2026-02-01",
7982            ),
7983            (
7984                "CAST('2026-01-31 23:30:00' AS TIMESTAMP)",
7985                "'Europe/Berlin'",
7986                "2026-02-01",
7987            ),
7988            (
7989                "COALESCE(NULL, TIMESTAMP '2026-01-31 23:30:00')",
7990                "'Europe/Berlin'",
7991                "2026-02-01",
7992            ),
7993            (
7994                "TIMESTAMP_MILLIS(1769902200000)",
7995                "'Europe/Berlin'",
7996                "2026-02-01",
7997            ),
7998            (
7999                "TIMESTAMP_MICROS(1769902200000000)",
8000                "'Europe/Berlin'",
8001                "2026-02-01",
8002            ),
8003            (
8004                "TIMESTAMP_SECONDS(1769902200)",
8005                "'Europe/Berlin'",
8006                "2026-02-01",
8007            ),
8008            (
8009                "TIMESTAMP '2026-01-31 20:00:00+00'",
8010                "'+05:30'",
8011                "2026-02-01",
8012            ),
8013            ("TIMESTAMP '2026-02-01 01:00:00+00'", "'-08'", "2026-01-31"),
8014            ("NULL", "'Europe/Berlin'", "NULL"),
8015            ("TIMESTAMP '2026-01-31 23:30:00+00'", "NULL", "NULL"),
8016        ] {
8017            let input = format!("SELECT DATE({argument}, {zone}) AS d");
8018            let output = source
8019                .transpile(&input, DialectType::DuckDB)
8020                .unwrap()
8021                .remove(0);
8022            assert!(!output.contains("STRPTIME"), "{input}: {output}");
8023            assert!(output.contains("AT TIME ZONE"), "{input}: {output}");
8024            assert_duckdb_value(&output, expected);
8025        }
8026        let input = "SELECT DATE(TIMESTAMP '2026-01-31 23:30:00+00', 'Europe/Berlin') AS d";
8027        assert_eq!(source.transpile(input, DialectType::DuckDB).unwrap()[0],
8028            "SELECT CAST(CAST('2026-01-31 23:30:00+00' AS TIMESTAMPTZ) AT TIME ZONE 'Europe/Berlin' AS DATE) AS d");
8029
8030        let input = "SELECT DATE(ts, zone) AS d FROM (SELECT TIMESTAMP '2026-01-31 23:30:00+00' AS ts, 'Europe/Berlin' AS zone) AS t";
8031        let output = source
8032            .transpile(input, DialectType::DuckDB)
8033            .unwrap()
8034            .remove(0);
8035        assert!(
8036            output.contains("CAST(ts AS TIMESTAMPTZ) AT TIME ZONE zone"),
8037            "{output}"
8038        );
8039        assert_duckdb_value(&output, "2026-02-01");
8040
8041        // Keep other DATE overloads and source dialect meanings intact.
8042        for (sql, read, expected) in [
8043            (
8044                "SELECT DATE(2026, 2, 1)",
8045                DialectType::BigQuery,
8046                "2026-02-01",
8047            ),
8048            (
8049                "SELECT DATE(DATETIME '2026-02-01 12:00:00')",
8050                DialectType::BigQuery,
8051                "2026-02-01",
8052            ),
8053            (
8054                "SELECT DATE('2026-02-01', 'YYYY-MM-DD')",
8055                DialectType::Snowflake,
8056                "2026-02-01",
8057            ),
8058        ] {
8059            let output = transpile_to_duckdb_from(sql, read);
8060            assert_duckdb_value(&output, expected);
8061        }
8062        let identity = source
8063            .transpile(
8064                "SELECT DATE(ts, 'Europe/Berlin') FROM t",
8065                DialectType::BigQuery,
8066            )
8067            .unwrap();
8068        assert_eq!(identity[0], "SELECT DATE(ts, 'Europe/Berlin') FROM t");
8069    }
8070
8071    #[test]
8072    fn test_athena_to_iso8601_exact_translation() {
8073        use crate::dialects::TranspileOptions;
8074        use crate::generator::UnsupportedLevel;
8075
8076        for read in [DialectType::Athena, DialectType::Trino] {
8077            for (argument, target_argument, format, expected_value) in [
8078                (
8079                    "DATE '2026-05-26'",
8080                    "CAST('2026-05-26' AS DATE)",
8081                    "%Y-%m-%d",
8082                    "2026-05-26",
8083                ),
8084                (
8085                    "TIMESTAMP '2026-05-26 02:08:02'",
8086                    "CAST('2026-05-26 02:08:02' AS TIMESTAMP(0))",
8087                    "%Y-%m-%dT%H:%M:%S",
8088                    "2026-05-26T02:08:02",
8089                ),
8090                (
8091                    "TIMESTAMP '2026-05-26 02:08:02.930'",
8092                    "CAST('2026-05-26 02:08:02.930' AS TIMESTAMP(3))",
8093                    "%Y-%m-%dT%H:%M:%S.%g",
8094                    "2026-05-26T02:08:02.930",
8095                ),
8096                (
8097                    "TIMESTAMP '2026-05-26 02:08:02.123456'",
8098                    "CAST('2026-05-26 02:08:02.123456' AS TIMESTAMP(6))",
8099                    "%Y-%m-%dT%H:%M:%S.%f",
8100                    "2026-05-26T02:08:02.123456",
8101                ),
8102                (
8103                    "TIMESTAMP '2026-05-26 02:08:02.123456789'",
8104                    "CAST('2026-05-26 02:08:02.123456789' AS TIMESTAMP(9))",
8105                    "%Y-%m-%dT%H:%M:%S.%n",
8106                    "2026-05-26T02:08:02.123456789",
8107                ),
8108                (
8109                    "TIMESTAMP '1969-12-31 23:59:59.123456789'",
8110                    "CAST('1969-12-31 23:59:59.123456789' AS TIMESTAMP(9))",
8111                    "%Y-%m-%dT%H:%M:%S.%n",
8112                    "1969-12-31T23:59:59.123456789",
8113                ),
8114                (
8115                    "CAST('2026-05-26 02:08:02.930' AS TIMESTAMP)",
8116                    "CAST('2026-05-26 02:08:02.930' AS TIMESTAMP(3))",
8117                    "%Y-%m-%dT%H:%M:%S.%g",
8118                    "2026-05-26T02:08:02.930",
8119                ),
8120                (
8121                    "CAST(TIMESTAMP '2026-05-26 02:08:02.930' AS TIMESTAMP(6))",
8122                    "CAST(CAST('2026-05-26 02:08:02.930' AS TIMESTAMP(3)) AS TIMESTAMP(6))",
8123                    "%Y-%m-%dT%H:%M:%S.%f",
8124                    "2026-05-26T02:08:02.930000",
8125                ),
8126                (
8127                    "CAST(CASE WHEN TRUE THEN TIMESTAMP '2026-05-26 02:08:02.123456789' ELSE NULL END AS TIMESTAMP(9))",
8128                    "CAST(CASE WHEN TRUE THEN CAST('2026-05-26 02:08:02.123456789' AS TIMESTAMP(9)) ELSE NULL END AS TIMESTAMP(9))",
8129                    "%Y-%m-%dT%H:%M:%S.%n",
8130                    "2026-05-26T02:08:02.123456789",
8131                ),
8132                (
8133                    "(TIMESTAMP '2026-05-26 02:08:02.930')",
8134                    "(CAST('2026-05-26 02:08:02.930' AS TIMESTAMP(3)))",
8135                    "%Y-%m-%dT%H:%M:%S.%g",
8136                    "2026-05-26T02:08:02.930",
8137                ),
8138                (
8139                    "CAST(NULL AS DATE)",
8140                    "CAST(NULL AS DATE)",
8141                    "%Y-%m-%d",
8142                    "NULL",
8143                ),
8144                (
8145                    "CAST(NULL AS TIMESTAMP(3))",
8146                    "CAST(NULL AS TIMESTAMP(3))",
8147                    "%Y-%m-%dT%H:%M:%S.%g",
8148                    "NULL",
8149                ),
8150                (
8151                    "TRY_CAST('invalid' AS TIMESTAMP(3))",
8152                    "TRY_CAST('invalid' AS TIMESTAMP(3))",
8153                    "%Y-%m-%dT%H:%M:%S.%g",
8154                    "NULL",
8155                ),
8156            ] {
8157                let sql = format!("SELECT TO_ISO8601({argument}) AS answer");
8158                for level in [
8159                    UnsupportedLevel::Ignore,
8160                    UnsupportedLevel::Warn,
8161                    UnsupportedLevel::Raise,
8162                    UnsupportedLevel::Immediate,
8163                ] {
8164                    let output = Dialect::get(read)
8165                        .transpile_with(
8166                            &sql,
8167                            DialectType::DuckDB,
8168                            TranspileOptions {
8169                                unsupported_level: level,
8170                                ..Default::default()
8171                            },
8172                        )
8173                        .unwrap();
8174                    assert_eq!(
8175                        output[0],
8176                        format!("SELECT STRFTIME({target_argument}, '{format}') AS answer"),
8177                        "{read:?}/{level:?}: {sql}"
8178                    );
8179                    if level == UnsupportedLevel::Ignore {
8180                        assert_duckdb_value(&output[0], expected_value);
8181                    }
8182                }
8183            }
8184        }
8185        // DATE has no session-dependent timestamp behavior in Presto either.
8186        let output = Dialect::get(DialectType::Presto)
8187            .transpile_with(
8188                "SELECT TO_ISO8601(DATE '2026-05-26')",
8189                DialectType::DuckDB,
8190                TranspileOptions::strict(),
8191            )
8192            .unwrap();
8193        assert_duckdb_value(&output[0], "2026-05-26");
8194        assert_eq!(
8195            output[0],
8196            "SELECT STRFTIME(CAST('2026-05-26' AS DATE), '%Y-%m-%d')"
8197        );
8198    }
8199
8200    #[test]
8201    fn test_athena_to_iso8601_permissive_translation() {
8202        use crate::dialects::TranspileOptions;
8203        use crate::generator::UnsupportedLevel;
8204
8205        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
8206            let mut cases = vec![
8207                ("TIMESTAMP '2026-05-26 02:08:02.93'", "CAST('2026-05-26 02:08:02.93' AS TIMESTAMP(3))", false, "2026-05-26T02:08:02.930000000"),
8208                ("TIMESTAMP '2026-05-26 02:08:02.930 +02:00'", "CAST(CAST('2026-05-26 02:08:02.930+02:00' AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(3))", true, "2026-05-26T00:08:02.930000000Z"),
8209                ("TIMESTAMP '2026-05-26 02:08:02.930 Z'", "CAST(CAST('2026-05-26 02:08:02.930Z' AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(3))", true, "2026-05-26T02:08:02.930000000Z"),
8210                ("TIMESTAMP '2026-05-26 02:08:02.930 America/New_York'", "CAST(CAST('2026-05-26 02:08:02.930 America/New_York' AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(3))", true, "2026-05-26T06:08:02.930000000Z"),
8211                ("CAST(NULL AS TIMESTAMP WITH TIME ZONE)", "CAST(CAST(NULL AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(3))", true, "NULL"),
8212                ("TRY_CAST('invalid' AS TIMESTAMP WITH TIME ZONE)", "CAST(TRY_CAST('invalid' AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(3))", true, "NULL"),
8213            ];
8214            if read == DialectType::Presto {
8215                cases.push((
8216                    "TIMESTAMP '2026-05-26 02:08:02.930'",
8217                    "CAST('2026-05-26 02:08:02.930' AS TIMESTAMP(3))",
8218                    false,
8219                    "2026-05-26T02:08:02.930000000",
8220                ));
8221            } else {
8222                cases.extend([
8223                    ("TIMESTAMP '2026-05-26 02:08:02.1234567'", "CAST('2026-05-26 02:08:02.1234567' AS TIMESTAMP(9))", false, "2026-05-26T02:08:02.123456700"),
8224                    ("TIMESTAMP '1969-12-31 23:59:59.123456 -02:00'", "CAST(CAST('1969-12-31 23:59:59.123456-02:00' AS TIMESTAMPTZ) AT TIME ZONE 'UTC' AS TIMESTAMP(6))", true, "1970-01-01T01:59:59.123456000Z"),
8225                ]);
8226            }
8227            for (argument, target_argument, timezone, expected_value) in cases {
8228                let sql = format!("SELECT TO_ISO8601({argument}) AS answer");
8229                for level in [
8230                    UnsupportedLevel::Ignore,
8231                    UnsupportedLevel::Warn,
8232                    UnsupportedLevel::Raise,
8233                    UnsupportedLevel::Immediate,
8234                ] {
8235                    let result = Dialect::get(read).transpile_with(
8236                        &sql,
8237                        DialectType::DuckDB,
8238                        TranspileOptions {
8239                            unsupported_level: level,
8240                            ..Default::default()
8241                        },
8242                    );
8243                    if matches!(level, UnsupportedLevel::Raise | UnsupportedLevel::Immediate) {
8244                        assert!(
8245                            matches!(result, Err(crate::error::Error::Unsupported { .. })),
8246                            "{read:?}/{level:?}: {sql}: {result:?}"
8247                        );
8248                    } else {
8249                        let output = result.unwrap();
8250                        let suffix = if timezone { " || 'Z'" } else { "" };
8251                        assert_eq!(output[0], format!("SELECT STRFTIME({target_argument}, '%Y-%m-%dT%H:%M:%S.%n'){suffix} AS answer"), "{read:?}/{level:?}: {sql}");
8252                        if level == UnsupportedLevel::Ignore {
8253                            assert_duckdb_value(&output[0], expected_value);
8254                        }
8255                    }
8256                }
8257            }
8258        }
8259    }
8260
8261    #[test]
8262    fn test_athena_to_iso8601_rejects_unverified_conversions() {
8263        use crate::dialects::TranspileOptions;
8264        use crate::generator::UnsupportedLevel;
8265
8266        for read in [DialectType::Athena, DialectType::Presto, DialectType::Trino] {
8267            let dialect = Dialect::get(read);
8268            for argument in [
8269                "value",
8270                "NULL",
8271                "'2026-05-26'",
8272                "42",
8273                "CAST(value AS VARCHAR)",
8274                "TIMESTAMP '2026-05-26 02:08:02.123456789123'",
8275                "TIMESTAMP '2026-05-26 02:08:02.123456789 +02:00'",
8276                "CAST(value AS TIMESTAMP(2))",
8277                "CAST(value AS TIMESTAMP(12))",
8278                "CAST(TIMESTAMP '2026-05-26 02:08:02.123456789' AS TIMESTAMP(6))",
8279                "CAST('2026-05-26 02:08:02.930999' AS TIMESTAMP(3))",
8280                "CAST(TIMESTAMP '2026-05-26 00:08:02.930 +02:00' AS DATE)",
8281                "CAST(TIMESTAMP '2026-05-26 00:08:02.930 +02:00' AS TIMESTAMP(3))",
8282                "DISTINCT DATE '2026-05-26'",
8283                "DATE '2026-05-26', DATE '2026-05-27'",
8284                "",
8285            ] {
8286                let sql = format!("SELECT TO_ISO8601({argument}) AS answer FROM t");
8287                for level in [
8288                    UnsupportedLevel::Ignore,
8289                    UnsupportedLevel::Warn,
8290                    UnsupportedLevel::Raise,
8291                    UnsupportedLevel::Immediate,
8292                ] {
8293                    let error = dialect
8294                        .transpile_with(
8295                            &sql,
8296                            DialectType::DuckDB,
8297                            TranspileOptions {
8298                                unsupported_level: level,
8299                                ..Default::default()
8300                            },
8301                        )
8302                        .expect_err("unsupported TO_ISO8601 must not emit invalid DuckDB SQL");
8303                    assert!(
8304                        matches!(error, crate::error::Error::Unsupported { .. }),
8305                        "{read:?}/{level:?}: {sql}: {error}"
8306                    );
8307                    assert!(error.to_string().contains("TO_ISO8601"));
8308                }
8309            }
8310
8311            assert_eq!(
8312                dialect
8313                    .transpile("SELECT TO_ISO8601(value) FROM t", read)
8314                    .unwrap()[0],
8315                "SELECT TO_ISO8601(value) FROM t"
8316            );
8317            assert_eq!(
8318                dialect
8319                    .transpile_with(
8320                        "SELECT custom.TO_ISO8601(value) FROM t",
8321                        DialectType::DuckDB,
8322                        TranspileOptions::strict()
8323                    )
8324                    .unwrap()[0],
8325                "SELECT custom.TO_ISO8601(value) FROM t"
8326            );
8327        }
8328    }
8329
8330    #[test]
8331    fn test_athena_to_iso8601_annotated_argument() {
8332        for (data_type, format) in [
8333            (DataType::Date, "%Y-%m-%d"),
8334            (
8335                DataType::Timestamp {
8336                    precision: Some(6),
8337                    timezone: false,
8338                },
8339                "%Y-%m-%dT%H:%M:%S.%f",
8340            ),
8341        ] {
8342            let mut argument = Expression::column("value");
8343            argument.set_inferred_type(data_type);
8344            let expression = Expression::Function(Box::new(Function::new(
8345                "TO_ISO8601".to_string(),
8346                vec![argument],
8347            )));
8348            let normalized = crate::dialects::normalization::normalize(
8349                expression,
8350                DialectType::Trino,
8351                DialectType::DuckDB,
8352                true,
8353            )
8354            .unwrap();
8355            assert_eq!(
8356                Dialect::get(DialectType::DuckDB)
8357                    .generate(&normalized)
8358                    .unwrap(),
8359                format!("STRFTIME(value, '{format}')")
8360            );
8361        }
8362        let mut argument = Expression::column("value");
8363        argument.set_inferred_type(DataType::Timestamp {
8364            precision: None,
8365            timezone: false,
8366        });
8367        for strict in [false, true] {
8368            let expression = Expression::Function(Box::new(Function::new(
8369                "TO_ISO8601".to_string(),
8370                vec![argument.clone()],
8371            )));
8372            assert!(crate::dialects::normalization::normalize(
8373                expression,
8374                DialectType::Trino,
8375                DialectType::DuckDB,
8376                strict
8377            )
8378            .is_err());
8379        }
8380    }
8381
8382    #[test]
8383    fn test_athena_to_iso8601_evaluates_argument_once() {
8384        let output = Dialect::get(DialectType::Athena).transpile_with(
8385            "SELECT TO_ISO8601(CAST(CASE WHEN NEXTVAL('calls') = 1 THEN '2026-05-26 02:08:02.930' ELSE '2000-01-01 00:00:00.000' END AS TIMESTAMP(3)))",
8386            DialectType::DuckDB,
8387            crate::dialects::TranspileOptions::strict(),
8388        ).unwrap();
8389        assert_eq!(output[0].matches("NEXTVAL").count(), 1);
8390        assert_duckdb_value(
8391            &format!(
8392                "CREATE SEQUENCE calls; {}; SELECT CURRVAL('calls');",
8393                output[0]
8394            ),
8395            "2026-05-26T02:08:02.930\n1",
8396        );
8397    }
8398
8399    #[test]
8400    fn test_ifnull_to_coalesce() {
8401        let result = transpile_to_duckdb("SELECT IFNULL(a, b)");
8402        assert!(
8403            result.contains("COALESCE"),
8404            "Expected COALESCE, got: {}",
8405            result
8406        );
8407    }
8408
8409    #[test]
8410    fn test_nvl_to_coalesce() {
8411        let result = transpile_to_duckdb("SELECT NVL(a, b)");
8412        assert!(
8413            result.contains("COALESCE"),
8414            "Expected COALESCE, got: {}",
8415            result
8416        );
8417    }
8418
8419    #[test]
8420    fn test_basic_select() {
8421        let result = transpile_to_duckdb("SELECT a, b FROM users WHERE id = 1");
8422        assert!(result.contains("SELECT"));
8423        assert!(result.contains("FROM users"));
8424    }
8425
8426    #[test]
8427    fn test_duckdb_keyword_relation_aliases() {
8428        let dialect = Dialect::get(DialectType::DuckDB);
8429        for alias in [
8430            "top",
8431            "TOP",
8432            "ToP",
8433            "first",
8434            "last",
8435            "begin",
8436            "type",
8437            "\"top\"",
8438            "\"where\"",
8439        ] {
8440            for template in [
8441                "SELECT {alias}.item_id FROM ranked_items {as}{alias}",
8442                "WITH ranked_items AS (SELECT 1 AS item_id) SELECT {alias}.item_id FROM ranked_items {as}{alias}",
8443                "WITH ranked_items AS (SELECT 1 AS item_id), selected_items AS (SELECT 1 AS item_id) SELECT {alias}.item_id FROM selected_items JOIN ranked_items {as}{alias} ON {alias}.item_id = selected_items.item_id",
8444                "SELECT {alias}.item_id FROM (SELECT 1 AS item_id) {as}{alias}",
8445                "SELECT {alias}.item_id FROM range(1) {as}{alias}(item_id)",
8446                "SELECT {alias}.item_id FROM (VALUES (1)) {as}{alias}(item_id)",
8447            ] {
8448                let template = template.replace("{alias}", alias);
8449                let explicit_sql = template.replace("{as}", "AS ");
8450                let expected = transpile_to_duckdb_from(&explicit_sql, DialectType::DuckDB);
8451                for as_keyword in ["", "AS "] {
8452                    let sql = template.replace("{as}", as_keyword);
8453                    let statements = dialect
8454                        .parse(&sql)
8455                        .unwrap_or_else(|error| panic!("Failed to parse {sql:?}: {error}"));
8456                    assert_eq!(statements.len(), 1, "{sql}");
8457                    let Expression::Select(select) = &statements[0] else {
8458                        panic!("Expected SELECT for {sql}");
8459                    };
8460                    let relation = select.joins.last().map_or_else(
8461                        || &select.from.as_ref().unwrap().expressions[0],
8462                        |join| &join.this,
8463                    );
8464                    let (identifier, columns) = match relation {
8465                        Expression::Table(table) => (table.alias.as_ref().unwrap(), &table.column_aliases),
8466                        Expression::Subquery(subquery) => (subquery.alias.as_ref().unwrap(), &subquery.column_aliases),
8467                        Expression::Alias(alias) => (&alias.alias, &alias.column_aliases),
8468                        _ => panic!("Expected aliased relation for {sql}"),
8469                    };
8470                    assert_eq!(identifier.name, alias.trim_matches('"'), "{sql}");
8471                    assert_eq!(identifier.quoted, alias.starts_with('"'), "{sql}");
8472                    if template.contains("(item_id)") {
8473                        assert_eq!(columns.len(), 1, "{sql}");
8474                        assert_eq!(columns[0].name, "item_id", "{sql}");
8475                    }
8476                    assert_eq!(
8477                        transpile_to_duckdb_from(&sql, DialectType::DuckDB),
8478                        expected,
8479                        "{sql}"
8480                    );
8481                }
8482            }
8483        }
8484    }
8485
8486    #[test]
8487    fn test_duckdb_keyword_relation_aliases_preserve_clause_boundaries() {
8488        let dialect = Dialect::get(DialectType::DuckDB);
8489        for alias in ["", " top"] {
8490            let sql = format!(
8491                "SELECT item_id, ROW_NUMBER() OVER w AS rn FROM ranked_items{alias} \
8492                 WHERE item_id > 0 GROUP BY item_id HAVING COUNT(*) > 0 \
8493                 WINDOW w AS (ORDER BY item_id) QUALIFY rn = 1 \
8494                 ORDER BY item_id NULLS FIRST, rn NULLS LAST LIMIT 5 OFFSET 1"
8495            );
8496            let statements = dialect.parse(&sql).unwrap();
8497            let Expression::Select(select) = &statements[0] else {
8498                panic!("Expected SELECT for {sql}");
8499            };
8500            assert!(select.where_clause.is_some());
8501            assert!(select.group_by.is_some());
8502            assert!(select.having.is_some());
8503            assert!(select.windows.is_some());
8504            assert!(select.qualify.is_some());
8505            let ordering = &select.order_by.as_ref().unwrap().expressions;
8506            assert_eq!(ordering.len(), 2);
8507            assert_eq!(ordering[0].nulls_first, Some(true));
8508            assert_eq!(ordering[1].nulls_first, Some(false));
8509            assert!(select.limit.is_some());
8510            assert!(select.offset.is_some());
8511            let Expression::Table(table) = &select.from.as_ref().unwrap().expressions[0] else {
8512                panic!("Expected table source for {sql}");
8513            };
8514            assert_eq!(
8515                table.alias.as_ref().map(|a| a.name.as_str()),
8516                if alias.is_empty() { None } else { Some("top") }
8517            );
8518
8519            for (join, kind) in [
8520                ("LEFT JOIN selected_items ON TRUE", JoinKind::Left),
8521                ("POSITIONAL JOIN selected_items", JoinKind::Positional),
8522            ] {
8523                let sql = format!("SELECT * FROM ranked_items{alias} {join}");
8524                let statements = dialect.parse(&sql).unwrap();
8525                let Expression::Select(select) = &statements[0] else {
8526                    panic!("Expected SELECT for {sql}");
8527                };
8528                assert_eq!(select.joins.len(), 1, "{sql}");
8529                assert_eq!(select.joins[0].kind, kind, "{sql}");
8530                let Expression::Table(table) = &select.from.as_ref().unwrap().expressions[0] else {
8531                    panic!("Expected table source for {sql}");
8532                };
8533                assert_eq!(
8534                    table.alias.as_ref().map(|a| a.name.as_str()),
8535                    if alias.is_empty() { None } else { Some("top") }
8536                );
8537            }
8538        }
8539
8540        // Truly reserved DuckDB words must not become implicit relation aliases.
8541        for alias in ["end", "where", "qualify", "select", "join", "limit"] {
8542            let sql = format!("SELECT * FROM ranked_items {alias}");
8543            assert!(dialect.parse(&sql).is_err(), "Unexpectedly accepted {sql}");
8544        }
8545    }
8546
8547    #[test]
8548    fn test_extract_quoted_date_parts() {
8549        for (quoted, canonical) in [("year", "YEAR"), ("month", "MONTH"), ("day", "DAY")] {
8550            let sql = format!("SELECT EXTRACT('{quoted}' FROM created_at) FROM events");
8551            assert_eq!(
8552                transpile_to_duckdb_from(&sql, DialectType::DuckDB),
8553                format!("SELECT EXTRACT({canonical} FROM created_at) FROM events")
8554            );
8555        }
8556
8557        let unquoted = "SELECT EXTRACT(YEAR FROM created_at) FROM events";
8558        assert_eq!(
8559            transpile_to_duckdb_from(unquoted, DialectType::DuckDB),
8560            unquoted
8561        );
8562    }
8563
8564    #[test]
8565    fn test_group_concat_to_listagg() {
8566        let result = transpile_to_duckdb("SELECT GROUP_CONCAT(name)");
8567        assert!(
8568            result.contains("LISTAGG"),
8569            "Expected LISTAGG, got: {}",
8570            result
8571        );
8572    }
8573
8574    #[test]
8575    fn test_listagg_preserved() {
8576        let result = transpile_to_duckdb("SELECT LISTAGG(name)");
8577        assert!(
8578            result.contains("LISTAGG"),
8579            "Expected LISTAGG, got: {}",
8580            result
8581        );
8582    }
8583
8584    #[test]
8585    fn test_ordered_string_agg_uses_duckdb_listagg_order_syntax() {
8586        let result = transpile_to_duckdb_from(
8587            "SELECT string_agg(nm, ',' ORDER BY id) AS v FROM t",
8588            DialectType::PostgreSQL,
8589        );
8590        assert_eq!(result, "SELECT LISTAGG(nm, ',' ORDER BY id) AS v FROM t");
8591    }
8592
8593    #[test]
8594    fn test_ordered_listagg_uses_duckdb_order_syntax() {
8595        let result = transpile_to_duckdb_from(
8596            "SELECT LISTAGG(col, '|SEPARATOR|') WITHIN GROUP (ORDER BY col2) FROM t",
8597            DialectType::Snowflake,
8598        );
8599        assert_eq!(
8600            result,
8601            "SELECT LISTAGG(col, '|SEPARATOR|' ORDER BY col2) FROM t"
8602        );
8603    }
8604
8605    #[test]
8606    fn test_ordered_group_concat_uses_duckdb_listagg_order_syntax() {
8607        let result = transpile_to_duckdb_from(
8608            "SELECT GROUP_CONCAT(nm ORDER BY id SEPARATOR ',') AS v FROM t",
8609            DialectType::MySQL,
8610        );
8611        assert_eq!(result, "SELECT LISTAGG(nm, ',' ORDER BY id) AS v FROM t");
8612    }
8613
8614    #[test]
8615    fn test_date_format_to_strftime() {
8616        let result = transpile_to_duckdb("SELECT DATE_FORMAT(d, '%Y-%m-%d')");
8617        // Generator uppercases function names
8618        assert!(
8619            result.to_uppercase().contains("STRFTIME"),
8620            "Expected STRFTIME, got: {}",
8621            result
8622        );
8623    }
8624
8625    #[test]
8626    fn test_regexp_like_to_regexp_matches() {
8627        let result = transpile_to_duckdb("SELECT REGEXP_LIKE(name, 'pattern')");
8628        // Generator uppercases function names
8629        assert!(
8630            result.to_uppercase().contains("REGEXP_MATCHES"),
8631            "Expected REGEXP_MATCHES, got: {}",
8632            result
8633        );
8634    }
8635
8636    #[test]
8637    fn test_double_quote_identifiers() {
8638        // DuckDB uses double quotes for identifiers
8639        let dialect = Dialect::get(DialectType::DuckDB);
8640        let config = dialect.generator_config();
8641        assert_eq!(config.identifier_quote, '"');
8642    }
8643
8644    /// Helper for DuckDB identity tests (parse with DuckDB, generate with DuckDB)
8645    fn duckdb_identity(sql: &str) -> String {
8646        let dialect = Dialect::get(DialectType::DuckDB);
8647        let ast = dialect.parse(sql).expect("Parse failed");
8648        let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
8649        dialect.generate(&transformed).expect("Generate failed")
8650    }
8651
8652    #[test]
8653    fn test_interval_quoting() {
8654        // Test 137: INTERVAL value should be quoted for DuckDB
8655        let result = duckdb_identity("SELECT DATE_ADD(CAST('2020-01-01' AS DATE), INTERVAL 1 DAY)");
8656        assert_eq!(
8657            result, "SELECT CAST('2020-01-01' AS DATE) + INTERVAL '1' DAY",
8658            "Interval value should be quoted as string"
8659        );
8660    }
8661
8662    #[test]
8663    fn test_struct_pack_to_curly_brace() {
8664        // Test 221: STRUCT_PACK should become curly brace notation
8665        let result = duckdb_identity("CAST([STRUCT_PACK(a := 1)] AS STRUCT(a BIGINT)[])");
8666        assert_eq!(
8667            result, "CAST([{'a': 1}] AS STRUCT(a BIGINT)[])",
8668            "STRUCT_PACK should be transformed to curly brace notation"
8669        );
8670    }
8671
8672    #[test]
8673    fn test_struct_pack_nested() {
8674        // Test 220: Nested STRUCT_PACK
8675        let result = duckdb_identity("CAST([[STRUCT_PACK(a := 1)]] AS STRUCT(a BIGINT)[][])");
8676        assert_eq!(
8677            result, "CAST([[{'a': 1}]] AS STRUCT(a BIGINT)[][])",
8678            "Nested STRUCT_PACK should be transformed"
8679        );
8680    }
8681
8682    #[test]
8683    fn test_struct_pack_cast() {
8684        // Test 222: STRUCT_PACK with :: cast
8685        let result = duckdb_identity("STRUCT_PACK(a := 'b')::json");
8686        assert_eq!(
8687            result, "CAST({'a': 'b'} AS JSON)",
8688            "STRUCT_PACK with cast should be transformed"
8689        );
8690    }
8691
8692    #[test]
8693    fn test_list_value_to_bracket() {
8694        // Test 309: LIST_VALUE should become bracket notation
8695        let result = duckdb_identity("SELECT LIST_VALUE(1)[i]");
8696        assert_eq!(
8697            result, "SELECT [1][i]",
8698            "LIST_VALUE should be transformed to bracket notation"
8699        );
8700    }
8701
8702    #[test]
8703    fn test_list_value_in_struct_literal() {
8704        // Test 310: LIST_VALUE inside struct literal
8705        let result = duckdb_identity("{'x': LIST_VALUE(1)[i]}");
8706        assert_eq!(
8707            result, "{'x': [1][i]}",
8708            "LIST_VALUE inside struct literal should be transformed"
8709        );
8710    }
8711
8712    #[test]
8713    fn test_struct_pack_simple() {
8714        // Simple STRUCT_PACK without nesting
8715        let result = duckdb_identity("SELECT STRUCT_PACK(a := 1)");
8716        eprintln!("STRUCT_PACK result: {}", result);
8717        assert!(
8718            result.contains("{"),
8719            "Expected curly brace, got: {}",
8720            result
8721        );
8722    }
8723
8724    #[test]
8725    fn test_not_in_position() {
8726        // Test 78: NOT IN should become NOT (...) IN (...)
8727        // DuckDB prefers `NOT (expr) IN (list)` over `expr NOT IN (list)`
8728        let result = duckdb_identity(
8729            "SELECT col FROM t WHERE JSON_EXTRACT_STRING(col, '$.id') NOT IN ('b')",
8730        );
8731        assert_eq!(
8732            result, "SELECT col FROM t WHERE NOT (col ->> '$.id') IN ('b')",
8733            "NOT IN should have NOT moved outside and JSON expression wrapped"
8734        );
8735    }
8736
8737    #[test]
8738    fn test_unnest_comma_join_to_join_on_true() {
8739        // Test 310: Comma-join with UNNEST should become JOIN ... ON TRUE
8740        let result = duckdb_identity(
8741            "WITH _data AS (SELECT [{'a': 1, 'b': 2}, {'a': 2, 'b': 3}] AS col) SELECT t.col['b'] FROM _data, UNNEST(_data.col) AS t(col) WHERE t.col['a'] = 1",
8742        );
8743        assert_eq!(
8744            result,
8745            "WITH _data AS (SELECT [{'a': 1, 'b': 2}, {'a': 2, 'b': 3}] AS col) SELECT t.col['b'] FROM _data JOIN UNNEST(_data.col) AS t(col) ON TRUE WHERE t.col['a'] = 1",
8746            "Comma-join with UNNEST should become JOIN ON TRUE"
8747        );
8748    }
8749}