Skip to main content

polyglot_sql/dialects/
duckdb.rs

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