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