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