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