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