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