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