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