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