Skip to main content

polyglot_sql/dialects/
duckdb.rs

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