Skip to main content

polyglot_sql/dialects/
duckdb.rs

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