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