Skip to main content

polyglot_sql/dialects/
duckdb.rs

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