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::{Alias, BinaryOp, Case, Cast, CeilFunc, Column, DataType, Expression, Function, FunctionParameter, Identifier, Interval, IntervalUnit, IntervalUnitSpec, JsonExtractFunc, JSONPath, JSONPathKey, JSONPathRoot, JSONPathSubscript, Literal, Paren, Struct, Subquery, UnaryFunc, VarArgFunc, WindowFunction};
15use crate::generator::GeneratorConfig;
16use crate::tokens::TokenizerConfig;
17
18/// Normalize a JSON path for DuckDB arrow syntax.
19/// Converts string keys like 'foo' to '$.foo' and numeric indexes like 0 to '$[0]'.
20/// This matches Python sqlglot's to_json_path() behavior.
21fn normalize_json_path(path: Expression) -> Expression {
22    match &path {
23        // String literal: 'foo' -> JSONPath with $.foo
24        Expression::Literal(Literal::String(s)) => {
25            // Skip paths that are already normalized (start with $ or /)
26            // Also skip JSON pointer syntax and back-of-list syntax [#-i]
27            if s.starts_with('$') || s.starts_with('/') || s.contains("[#") {
28                return path;
29            }
30            // Create JSONPath expression: $.key
31            Expression::JSONPath(Box::new(JSONPath {
32                expressions: vec![
33                    Expression::JSONPathRoot(JSONPathRoot),
34                    Expression::JSONPathKey(Box::new(JSONPathKey {
35                        this: Box::new(Expression::Literal(Literal::String(s.clone()))),
36                    })),
37                ],
38                escape: None,
39            }))
40        }
41        // Number literal: 0 -> JSONPath with $[0]
42        Expression::Literal(Literal::Number(n)) => {
43            // Create JSONPath expression: $[n]
44            Expression::JSONPath(Box::new(JSONPath {
45                expressions: vec![
46                    Expression::JSONPathRoot(JSONPathRoot),
47                    Expression::JSONPathSubscript(Box::new(JSONPathSubscript {
48                        this: Box::new(Expression::Literal(Literal::Number(n.clone()))),
49                    })),
50                ],
51                escape: None,
52            }))
53        }
54        // Already a JSONPath or other expression - return as is
55        _ => path,
56    }
57}
58
59/// Helper to wrap JSON arrow expressions in parentheses when they appear
60/// in contexts that require it (Binary, In, Not expressions)
61/// This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior
62fn wrap_if_json_arrow(expr: Expression) -> Expression {
63    match &expr {
64        Expression::JsonExtract(f) if f.arrow_syntax => {
65            Expression::Paren(Box::new(Paren {
66                this: expr,
67                trailing_comments: Vec::new(),
68            }))
69        }
70        Expression::JsonExtractScalar(f) if f.arrow_syntax => {
71            Expression::Paren(Box::new(Paren {
72                this: expr,
73                trailing_comments: Vec::new(),
74            }))
75        }
76        _ => expr,
77    }
78}
79
80/// DuckDB dialect
81pub struct DuckDBDialect;
82
83impl DialectImpl for DuckDBDialect {
84    fn dialect_type(&self) -> DialectType {
85        DialectType::DuckDB
86    }
87
88    fn tokenizer_config(&self) -> TokenizerConfig {
89        let mut config = TokenizerConfig::default();
90        // DuckDB uses double quotes for identifiers
91        config.identifiers.insert('"', '"');
92        // DuckDB supports nested comments
93        config.nested_comments = true;
94        config
95    }
96
97    fn generator_config(&self) -> GeneratorConfig {
98        use crate::generator::IdentifierQuoteStyle;
99        GeneratorConfig {
100            identifier_quote: '"',
101            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
102            dialect: Some(DialectType::DuckDB),
103            // DuckDB-specific settings from Python sqlglot
104            parameter_token: "$",
105            named_placeholder_token: "$",
106            join_hints: false,
107            table_hints: false,
108            query_hints: false,
109            limit_fetch_style: crate::generator::LimitFetchStyle::Limit,
110            struct_delimiter: ("(", ")"),
111            rename_table_with_db: false,
112            nvl2_supported: false,
113            semi_anti_join_with_side: false,
114            tablesample_keywords: "TABLESAMPLE",
115            tablesample_seed_keyword: "REPEATABLE",
116            last_day_supports_date_part: false,
117            json_key_value_pair_sep: ",",
118            ignore_nulls_in_func: true,
119            json_path_bracketed_key_supported: false,
120            supports_create_table_like: false,
121            multi_arg_distinct: false,
122            quantified_no_paren_space: true,
123            can_implement_array_any: true,
124            supports_to_number: false,
125            supports_window_exclude: true,
126            copy_has_into_keyword: false,
127            star_except: "EXCLUDE",
128            pad_fill_pattern_is_required: true,
129            array_concat_is_var_len: false,
130            array_size_dim_required: None,
131            normalize_extract_date_parts: true,
132            supports_like_quantifiers: false,
133            // DuckDB supports TRY_CAST
134            try_supported: true,
135            // DuckDB uses curly brace notation for struct literals: {'a': 1}
136            struct_curly_brace_notation: true,
137            // DuckDB uses bracket-only notation for arrays: [1, 2, 3]
138            array_bracket_only: true,
139            ..Default::default()
140        }
141    }
142
143    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
144        match expr {
145            // ===== Data Type Mappings =====
146            Expression::DataType(dt) => self.transform_data_type(dt),
147
148            // ===== Operator transformations =====
149            // BitwiseXor -> XOR() function in DuckDB
150            Expression::BitwiseXor(op) => Ok(Expression::Function(Box::new(crate::expressions::Function::new(
151                "XOR",
152                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 { original_name: None,
164                expressions: vec![f.this, f.expression],
165            }))),
166
167            // NVL -> COALESCE in DuckDB
168            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc { original_name: None,
169                expressions: vec![f.this, f.expression],
170            }))),
171
172            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
173            Expression::Coalesce(mut f) => {
174                f.original_name = None;
175                Ok(Expression::Coalesce(f))
176            }
177
178            // GROUP_CONCAT -> LISTAGG in DuckDB
179            Expression::GroupConcat(f) => Ok(Expression::ListAgg(Box::new(crate::expressions::ListAggFunc {
180                this: f.this,
181                separator: f.separator,
182                on_overflow: None,
183                order_by: f.order_by,
184                distinct: f.distinct,
185                filter: f.filter,
186            }))),
187
188            // LISTAGG is native in DuckDB - keep as-is
189            Expression::ListAgg(f) => Ok(Expression::ListAgg(f)),
190
191            // STRING_AGG -> LISTAGG in DuckDB (normalize to LISTAGG)
192            Expression::StringAgg(f) => Ok(Expression::ListAgg(Box::new(crate::expressions::ListAggFunc {
193                this: f.this,
194                separator: f.separator,
195                on_overflow: None,
196                order_by: f.order_by,
197                distinct: f.distinct,
198                filter: f.filter,
199            }))),
200
201            // TryCast -> TRY_CAST (DuckDB supports TRY_CAST)
202            Expression::TryCast(c) => Ok(Expression::TryCast(c)),
203
204            // SafeCast -> TRY_CAST in DuckDB
205            Expression::SafeCast(c) => Ok(Expression::TryCast(c)),
206
207            // ILIKE is native to DuckDB (PostgreSQL-compatible)
208            Expression::ILike(op) => Ok(Expression::ILike(op)),
209
210            // EXPLODE -> UNNEST in DuckDB
211            Expression::Explode(f) => Ok(Expression::Unnest(Box::new(
212                crate::expressions::UnnestFunc {
213                    this: f.this,
214                    expressions: Vec::new(),
215                    with_ordinality: false,
216                    alias: None,
217                    offset_alias: None,
218                },
219            ))),
220
221            // UNNEST is native to DuckDB
222            Expression::Unnest(f) => Ok(Expression::Unnest(f)),
223
224            // ArrayContainedBy (<@) -> ArrayContainsAll (@>) with swapped operands
225            // a <@ b becomes b @> a
226            Expression::ArrayContainedBy(op) => Ok(Expression::ArrayContainsAll(Box::new(BinaryOp {
227                left: op.right,
228                right: op.left,
229                left_comments: Vec::new(),
230                operator_comments: Vec::new(),
231                trailing_comments: Vec::new(),
232            }))),
233
234            // DATE_ADD -> date + INTERVAL in DuckDB
235            Expression::DateAdd(f) => {
236                // Reconstruct INTERVAL expression from value and unit
237                let interval_expr = if matches!(&f.interval, Expression::Interval(_)) {
238                    f.interval
239                } else {
240                    Expression::Interval(Box::new(Interval {
241                        this: Some(f.interval),
242                        unit: Some(IntervalUnitSpec::Simple { unit: f.unit, use_plural: false }),
243                    }))
244                };
245                Ok(Expression::Add(Box::new(BinaryOp {
246                    left: f.this,
247                    right: interval_expr,
248                    left_comments: Vec::new(),
249                    operator_comments: Vec::new(),
250                    trailing_comments: Vec::new(),
251                })))
252            }
253
254            // DATE_SUB -> date - INTERVAL in DuckDB
255            Expression::DateSub(f) => {
256                // Reconstruct INTERVAL expression from value and unit
257                let interval_expr = if matches!(&f.interval, Expression::Interval(_)) {
258                    f.interval
259                } else {
260                    Expression::Interval(Box::new(Interval {
261                        this: Some(f.interval),
262                        unit: Some(IntervalUnitSpec::Simple { unit: f.unit, use_plural: false }),
263                    }))
264                };
265                Ok(Expression::Sub(Box::new(BinaryOp {
266                    left: f.this,
267                    right: interval_expr,
268                    left_comments: Vec::new(),
269                    operator_comments: Vec::new(),
270                    trailing_comments: Vec::new(),
271                })))
272            }
273
274            // GenerateSeries with 1 arg -> GENERATE_SERIES(0, n)
275            Expression::GenerateSeries(mut f) => {
276                // If only end is set (no start), add 0 as start
277                if f.start.is_none() && f.end.is_some() {
278                    f.start = Some(Box::new(Expression::number(0)));
279                }
280                Ok(Expression::GenerateSeries(f))
281            }
282
283            // ===== Array/List functions =====
284            // ArrayAppend -> LIST_APPEND
285            Expression::ArrayAppend(f) => Ok(Expression::Function(Box::new(Function::new(
286                "LIST_APPEND".to_string(),
287                vec![f.this, f.expression],
288            )))),
289
290            // ArrayPrepend -> LIST_PREPEND
291            Expression::ArrayPrepend(f) => Ok(Expression::Function(Box::new(Function::new(
292                "LIST_PREPEND".to_string(),
293                vec![f.this, f.expression],
294            )))),
295
296            // ArrayUniqueAgg -> LIST
297            Expression::ArrayUniqueAgg(f) => Ok(Expression::Function(Box::new(Function::new(
298                "LIST".to_string(),
299                vec![f.this],
300            )))),
301
302            // Split -> STR_SPLIT
303            Expression::Split(f) => Ok(Expression::Function(Box::new(Function::new(
304                "STR_SPLIT".to_string(),
305                vec![f.this, f.delimiter],
306            )))),
307
308            // RANDOM is native to DuckDB
309            Expression::Random(_) => Ok(Expression::Random(crate::expressions::Random)),
310
311            // Rand with seed -> keep as Rand so NORMAL/UNIFORM handlers can extract the seed
312            // Rand without seed -> Random
313            Expression::Rand(r) => {
314                if r.seed.is_some() {
315                    Ok(Expression::Rand(r))
316                } else {
317                    Ok(Expression::Random(crate::expressions::Random))
318                }
319            }
320
321            // ===== Boolean aggregates =====
322            // LogicalAnd -> BOOL_AND with CAST to BOOLEAN
323            Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
324                "BOOL_AND".to_string(),
325                vec![Expression::Cast(Box::new(crate::expressions::Cast {
326                    this: f.this,
327                    to: crate::expressions::DataType::Boolean,
328                    trailing_comments: Vec::new(),
329                    double_colon_syntax: false,
330                    format: None,
331                    default: None,
332                }))],
333            )))),
334
335            // LogicalOr -> BOOL_OR with CAST to BOOLEAN
336            Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
337                "BOOL_OR".to_string(),
338                vec![Expression::Cast(Box::new(crate::expressions::Cast {
339                    this: f.this,
340                    to: crate::expressions::DataType::Boolean,
341                    trailing_comments: Vec::new(),
342                    double_colon_syntax: false,
343                    format: None,
344                    default: None,
345                }))],
346            )))),
347
348            // ===== Approximate functions =====
349            // ApproxDistinct -> APPROX_COUNT_DISTINCT
350            Expression::ApproxDistinct(f) => Ok(Expression::Function(Box::new(Function::new(
351                "APPROX_COUNT_DISTINCT".to_string(),
352                vec![f.this],
353            )))),
354
355            // ===== Variance =====
356            // VarPop -> VAR_POP
357            Expression::VarPop(f) => Ok(Expression::Function(Box::new(Function::new(
358                "VAR_POP".to_string(),
359                vec![f.this],
360            )))),
361
362            // ===== Date/time functions =====
363            // DayOfMonth -> DAYOFMONTH
364            Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
365                "DAYOFMONTH".to_string(),
366                vec![f.this],
367            )))),
368
369            // DayOfWeek -> DAYOFWEEK
370            Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
371                "DAYOFWEEK".to_string(),
372                vec![f.this],
373            )))),
374
375            // DayOfWeekIso -> ISODOW
376            Expression::DayOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
377                "ISODOW".to_string(),
378                vec![f.this],
379            )))),
380
381            // DayOfYear -> DAYOFYEAR
382            Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
383                "DAYOFYEAR".to_string(),
384                vec![f.this],
385            )))),
386
387            // WeekOfYear -> WEEKOFYEAR
388            Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
389                "WEEKOFYEAR".to_string(),
390                vec![f.this],
391            )))),
392
393            // ===== Time conversion functions =====
394            // TimeStrToUnix -> EPOCH
395            Expression::TimeStrToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
396                "EPOCH".to_string(),
397                vec![f.this],
398            )))),
399
400            // TimeToUnix -> EPOCH
401            Expression::TimeToUnix(f) => Ok(Expression::Function(Box::new(Function::new(
402                "EPOCH".to_string(),
403                vec![f.this],
404            )))),
405
406            // UnixMicros -> EPOCH_US
407            Expression::UnixMicros(f) => Ok(Expression::Function(Box::new(Function::new(
408                "EPOCH_US".to_string(),
409                vec![f.this],
410            )))),
411
412            // UnixMillis -> EPOCH_MS
413            Expression::UnixMillis(f) => Ok(Expression::Function(Box::new(Function::new(
414                "EPOCH_MS".to_string(),
415                vec![f.this],
416            )))),
417
418            // TimestampDiff -> DATE_DIFF
419            Expression::TimestampDiff(f) => Ok(Expression::Function(Box::new(Function::new(
420                "DATE_DIFF".to_string(),
421                vec![*f.this, *f.expression],
422            )))),
423
424            // ===== Hash functions =====
425            // SHA -> SHA1
426            Expression::SHA(f) => Ok(Expression::Function(Box::new(Function::new(
427                "SHA1".to_string(),
428                vec![f.this],
429            )))),
430
431            // MD5Digest -> UNHEX(MD5(...))
432            Expression::MD5Digest(f) => Ok(Expression::Function(Box::new(Function::new(
433                "UNHEX".to_string(),
434                vec![*f.this],
435            )))),
436
437            // SHA1Digest -> UNHEX
438            Expression::SHA1Digest(f) => Ok(Expression::Function(Box::new(Function::new(
439                "UNHEX".to_string(),
440                vec![f.this],
441            )))),
442
443            // SHA2Digest -> UNHEX
444            Expression::SHA2Digest(f) => Ok(Expression::Function(Box::new(Function::new(
445                "UNHEX".to_string(),
446                vec![*f.this],
447            )))),
448
449            // ===== Vector/Distance functions =====
450            // CosineDistance -> LIST_COSINE_DISTANCE
451            Expression::CosineDistance(f) => Ok(Expression::Function(Box::new(Function::new(
452                "LIST_COSINE_DISTANCE".to_string(),
453                vec![*f.this, *f.expression],
454            )))),
455
456            // EuclideanDistance -> LIST_DISTANCE
457            Expression::EuclideanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
458                "LIST_DISTANCE".to_string(),
459                vec![*f.this, *f.expression],
460            )))),
461
462            // ===== Numeric checks =====
463            // IsInf -> ISINF
464            Expression::IsInf(f) => Ok(Expression::Function(Box::new(Function::new(
465                "ISINF".to_string(),
466                vec![f.this],
467            )))),
468
469            // IsNan -> ISNAN
470            Expression::IsNan(f) => Ok(Expression::Function(Box::new(Function::new(
471                "ISNAN".to_string(),
472                vec![f.this],
473            )))),
474
475            // ===== Pattern matching =====
476            // RegexpLike (~) -> REGEXP_FULL_MATCH in DuckDB
477            Expression::RegexpLike(f) => Ok(Expression::Function(Box::new(Function::new(
478                "REGEXP_FULL_MATCH".to_string(),
479                vec![f.this, f.pattern],
480            )))),
481
482
483            // ===== Time functions =====
484            // CurrentTime -> CURRENT_TIME (no parens in DuckDB)
485            Expression::CurrentTime(_) => Ok(Expression::Function(Box::new(Function {
486                name: "CURRENT_TIME".to_string(),
487                args: vec![],
488                distinct: false,
489                trailing_comments: vec![],
490                use_bracket_syntax: false,
491                no_parens: true,
492                quoted: false,
493            }))),
494
495            // ===== Return statement =====
496            // ReturnStmt -> just output the inner expression
497            Expression::ReturnStmt(e) => Ok(*e),
498
499            // ===== DDL Column Constraints =====
500            // CommentColumnConstraint -> ignored (DuckDB doesn't support column comments this way)
501            Expression::CommentColumnConstraint(_) => Ok(Expression::Literal(
502                crate::expressions::Literal::String(String::new()),
503            )),
504
505            // JsonExtract -> use arrow syntax (->) in DuckDB with normalized JSON path
506            Expression::JsonExtract(mut f) => {
507                f.arrow_syntax = true;
508                f.path = normalize_json_path(f.path);
509                Ok(Expression::JsonExtract(f))
510            }
511
512            // JsonExtractScalar -> use arrow syntax (->>) in DuckDB with normalized JSON path
513            Expression::JsonExtractScalar(mut f) => {
514                f.arrow_syntax = true;
515                f.path = normalize_json_path(f.path);
516                Ok(Expression::JsonExtractScalar(f))
517            }
518
519            // CARDINALITY -> ARRAY_LENGTH in DuckDB
520            Expression::Cardinality(f) => Ok(Expression::Function(Box::new(Function::new(
521                "ARRAY_LENGTH".to_string(),
522                vec![f.this],
523            )))),
524
525            // ADD_MONTHS(date, n) -> convert to Function and handle in transform_function
526            Expression::AddMonths(f) => {
527                let func = Function::new("ADD_MONTHS".to_string(), vec![f.this, f.expression]);
528                self.transform_function(func)
529            }
530
531            // NEXT_DAY(date, day) -> convert to Function and handle in transform_function
532            Expression::NextDay(f) => {
533                let func = Function::new("NEXT_DAY".to_string(), vec![f.this, f.expression]);
534                self.transform_function(func)
535            }
536
537            // LAST_DAY(date, unit) -> convert to Function and handle in transform_function
538            Expression::LastDay(f) => {
539                if let Some(unit) = f.unit {
540                    let unit_str = match unit {
541                        crate::expressions::DateTimeField::Year => "YEAR",
542                        crate::expressions::DateTimeField::Month => "MONTH",
543                        crate::expressions::DateTimeField::Quarter => "QUARTER",
544                        crate::expressions::DateTimeField::Week => "WEEK",
545                        crate::expressions::DateTimeField::Day => "DAY",
546                        _ => "MONTH",
547                    };
548                    let func = Function::new("LAST_DAY".to_string(), vec![f.this, Expression::Identifier(Identifier { name: unit_str.to_string(), quoted: false, trailing_comments: Vec::new() })]);
549                    self.transform_function(func)
550                } else {
551                    // Single arg LAST_DAY - pass through
552                    Ok(Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![f.this]))))
553                }
554            }
555
556            // DAYNAME(expr) -> STRFTIME(expr, '%a')
557            Expression::Dayname(d) => {
558                Ok(Expression::Function(Box::new(Function::new("STRFTIME".to_string(), vec![
559                    *d.this,
560                    Expression::Literal(Literal::String("%a".to_string())),
561                ]))))
562            }
563
564            // MONTHNAME(expr) -> STRFTIME(expr, '%b')
565            Expression::Monthname(d) => {
566                Ok(Expression::Function(Box::new(Function::new("STRFTIME".to_string(), vec![
567                    *d.this,
568                    Expression::Literal(Literal::String("%b".to_string())),
569                ]))))
570            }
571
572            // FLOOR(x, scale) -> ROUND(FLOOR(x * POWER(10, scale)) / POWER(10, scale), scale)
573            Expression::Floor(f) if f.scale.is_some() => {
574                let x = f.this;
575                let scale = f.scale.unwrap();
576                let needs_cast = match &scale {
577                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
578                    _ => false,
579                };
580                let int_scale = if needs_cast {
581                    Expression::Cast(Box::new(Cast { this: scale.clone(), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))
582                } else {
583                    scale.clone()
584                };
585                let power_10 = Expression::Function(Box::new(Function::new("POWER".to_string(), vec![Expression::number(10), int_scale.clone()])));
586                let x_paren = match &x { Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => Expression::Paren(Box::new(Paren { this: x, trailing_comments: Vec::new() })), _ => x };
587                let multiplied = Expression::Mul(Box::new(BinaryOp { left: x_paren, right: power_10.clone(), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
588                let floored = Expression::Function(Box::new(Function::new("FLOOR".to_string(), vec![multiplied])));
589                let divided = Expression::Div(Box::new(BinaryOp { left: floored, right: power_10, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
590                Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![divided, int_scale]))))
591            }
592
593            // CEIL(x, scale) -> ROUND(CEIL(x * POWER(10, scale)) / POWER(10, scale), scale)
594            Expression::Ceil(f) if f.decimals.is_some() => {
595                let x = f.this;
596                let scale = f.decimals.unwrap();
597                let needs_cast = match &scale {
598                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
599                    _ => false,
600                };
601                let int_scale = if needs_cast {
602                    Expression::Cast(Box::new(Cast { this: scale.clone(), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))
603                } else {
604                    scale.clone()
605                };
606                let power_10 = Expression::Function(Box::new(Function::new("POWER".to_string(), vec![Expression::number(10), int_scale.clone()])));
607                let x_paren = match &x { Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => Expression::Paren(Box::new(Paren { this: x, trailing_comments: Vec::new() })), _ => x };
608                let multiplied = Expression::Mul(Box::new(BinaryOp { left: x_paren, right: power_10.clone(), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
609                let ceiled = Expression::Function(Box::new(Function::new("CEIL".to_string(), vec![multiplied])));
610                let divided = Expression::Div(Box::new(BinaryOp { left: ceiled, right: power_10, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
611                Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![divided, int_scale]))))
612            }
613
614            // ParseJson: handled by generator (outputs JSON() for DuckDB)
615
616            // TABLE(GENERATOR(ROWCOUNT => n)) -> RANGE(n) in DuckDB
617            // The TABLE() wrapper around GENERATOR is parsed as TableArgument
618            Expression::TableArgument(ta) if ta.prefix.to_uppercase() == "TABLE" => {
619                // Check if inner is a GENERATOR or RANGE function
620                match ta.this {
621                    Expression::Function(ref f) if f.name.to_uppercase() == "RANGE" => {
622                        // Already converted to RANGE, unwrap TABLE()
623                        Ok(ta.this)
624                    }
625                    Expression::Function(ref f) if f.name.to_uppercase() == "GENERATOR" => {
626                        // GENERATOR(ROWCOUNT => n) -> RANGE(n)
627                        let mut rowcount = None;
628                        for arg in &f.args {
629                            if let Expression::NamedArgument(na) = arg {
630                                if na.name.name.to_uppercase() == "ROWCOUNT" {
631                                    rowcount = Some(na.value.clone());
632                                }
633                            }
634                        }
635                        if let Some(n) = rowcount {
636                            Ok(Expression::Function(Box::new(Function::new("RANGE".to_string(), vec![n]))))
637                        } else {
638                            Ok(Expression::TableArgument(ta))
639                        }
640                    }
641                    _ => Ok(Expression::TableArgument(ta)),
642                }
643            }
644
645            // JSONExtract (variant_extract/colon accessor) -> arrow syntax in DuckDB
646            Expression::JSONExtract(e) if e.variant_extract.is_some() => {
647                let path = match *e.expression {
648                    Expression::Literal(Literal::String(s)) => {
649                        // Convert bracket notation ["key"] to quoted dot notation ."key"
650                        let s = Self::convert_bracket_to_quoted_path(&s);
651                        let normalized = if s.starts_with('$') {
652                            s
653                        } else if s.starts_with('[') {
654                            format!("${}", s)
655                        } else {
656                            format!("$.{}", s)
657                        };
658                        Expression::Literal(Literal::String(normalized))
659                    }
660                    other => other,
661                };
662                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
663                    this: *e.this,
664                    path,
665                    returning: None,
666                    arrow_syntax: true,
667                    hash_arrow_syntax: false,
668                    wrapper_option: None,
669                    quotes_option: None,
670                    on_scalar_string: false,
671                    on_error: None,
672                })))
673            }
674
675            // X'ABCD' -> UNHEX('ABCD') in DuckDB
676            Expression::Literal(Literal::HexString(s)) => {
677                Ok(Expression::Function(Box::new(Function::new(
678                    "UNHEX".to_string(),
679                    vec![Expression::Literal(Literal::String(s))],
680                ))))
681            }
682
683            // b'a' -> CAST(e'a' AS BLOB) in DuckDB
684            Expression::Literal(Literal::ByteString(s)) => {
685                Ok(Expression::Cast(Box::new(Cast {
686                    this: Expression::Literal(Literal::EscapeString(s)),
687                    to: DataType::VarBinary { length: None },
688                    trailing_comments: Vec::new(),
689                    double_colon_syntax: false,
690                    format: None,
691                    default: None,
692                })))
693            }
694
695            // CAST(x AS DECIMAL) -> CAST(x AS DECIMAL(18, 3)) in DuckDB (default precision)
696            // Exception: CAST(a // b AS DECIMAL) from DIV conversion keeps bare DECIMAL
697            Expression::Cast(mut c) => {
698                if matches!(&c.to, DataType::Decimal { precision: None, .. })
699                    && !matches!(&c.this, Expression::IntDiv(_))
700                {
701                    c.to = DataType::Decimal {
702                        precision: Some(18),
703                        scale: Some(3),
704                    };
705                }
706                let transformed_this = self.transform_expr(c.this)?;
707                c.this = transformed_this;
708                Ok(Expression::Cast(c))
709            }
710
711            // Generic function transformations
712            Expression::Function(f) => self.transform_function(*f),
713
714            // Generic aggregate function transformations
715            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
716
717            // WindowFunction with CASE-wrapped CORR: re-wrap so OVER is inside CASE
718            // Pattern: WindowFunction { this: CASE(ISNAN(CORR), NULL, CORR), over }
719            // Expected: CASE(ISNAN(WindowFunction(CORR, over)), NULL, WindowFunction(CORR, over))
720            Expression::WindowFunction(wf) => {
721                if let Expression::Case(case_box) = wf.this {
722                    let case = *case_box;
723                    // Detect the ISNAN(CORR) -> NULL pattern
724                    if case.whens.len() == 1 && matches!(&case.else_, Some(Expression::AggregateFunction(ref af)) if af.name.to_uppercase() == "CORR") {
725                        // Re-wrap: put the OVER on each CORR inside the CASE
726                        let over = wf.over;
727                        let new_else = case.else_.map(|e| {
728                            Expression::WindowFunction(Box::new(WindowFunction { this: e, over: over.clone(), keep: None }))
729                        });
730                        let new_whens = case.whens.into_iter().map(|(when_cond, when_result)| {
731                            // wrap the ISNAN arg (which is CORR) with OVER
732                            let new_cond = if let Expression::Function(func) = when_cond {
733                                if func.name.to_uppercase() == "ISNAN" && func.args.len() == 1 {
734                                    let inner = func.args.into_iter().next().unwrap();
735                                    let windowed = Expression::WindowFunction(Box::new(WindowFunction { this: inner, over: over.clone(), keep: None }));
736                                    Expression::Function(Box::new(Function::new("ISNAN".to_string(), vec![windowed])))
737                                } else {
738                                    Expression::Function(func)
739                                }
740                            } else {
741                                when_cond
742                            };
743                            (new_cond, when_result)
744                        }).collect();
745                        Ok(Expression::Case(Box::new(Case { operand: None, whens: new_whens, else_: new_else })))
746                    } else {
747                        Ok(Expression::WindowFunction(Box::new(WindowFunction { this: Expression::Case(Box::new(case)), over: wf.over, keep: wf.keep })))
748                    }
749                } else {
750                    Ok(Expression::WindowFunction(wf))
751                }
752            }
753
754            // ===== Context-aware JSON arrow wrapping =====
755            // When JSON arrow expressions appear in Binary/In/Not contexts,
756            // they need to be wrapped in parentheses for correct precedence.
757            // This matches Python sqlglot's WRAPPED_JSON_EXTRACT_EXPRESSIONS behavior.
758
759            // Binary operators that need JSON wrapping
760            Expression::Eq(op) => Ok(Expression::Eq(Box::new(BinaryOp {
761                left: wrap_if_json_arrow(op.left),
762                right: wrap_if_json_arrow(op.right),
763                ..*op
764            }))),
765            Expression::Neq(op) => Ok(Expression::Neq(Box::new(BinaryOp {
766                left: wrap_if_json_arrow(op.left),
767                right: wrap_if_json_arrow(op.right),
768                ..*op
769            }))),
770            Expression::Lt(op) => Ok(Expression::Lt(Box::new(BinaryOp {
771                left: wrap_if_json_arrow(op.left),
772                right: wrap_if_json_arrow(op.right),
773                ..*op
774            }))),
775            Expression::Lte(op) => Ok(Expression::Lte(Box::new(BinaryOp {
776                left: wrap_if_json_arrow(op.left),
777                right: wrap_if_json_arrow(op.right),
778                ..*op
779            }))),
780            Expression::Gt(op) => Ok(Expression::Gt(Box::new(BinaryOp {
781                left: wrap_if_json_arrow(op.left),
782                right: wrap_if_json_arrow(op.right),
783                ..*op
784            }))),
785            Expression::Gte(op) => Ok(Expression::Gte(Box::new(BinaryOp {
786                left: wrap_if_json_arrow(op.left),
787                right: wrap_if_json_arrow(op.right),
788                ..*op
789            }))),
790            Expression::And(op) => Ok(Expression::And(Box::new(BinaryOp {
791                left: wrap_if_json_arrow(op.left),
792                right: wrap_if_json_arrow(op.right),
793                ..*op
794            }))),
795            Expression::Or(op) => Ok(Expression::Or(Box::new(BinaryOp {
796                left: wrap_if_json_arrow(op.left),
797                right: wrap_if_json_arrow(op.right),
798                ..*op
799            }))),
800            Expression::Add(op) => Ok(Expression::Add(Box::new(BinaryOp {
801                left: wrap_if_json_arrow(op.left),
802                right: wrap_if_json_arrow(op.right),
803                ..*op
804            }))),
805            Expression::Sub(op) => Ok(Expression::Sub(Box::new(BinaryOp {
806                left: wrap_if_json_arrow(op.left),
807                right: wrap_if_json_arrow(op.right),
808                ..*op
809            }))),
810            Expression::Mul(op) => Ok(Expression::Mul(Box::new(BinaryOp {
811                left: wrap_if_json_arrow(op.left),
812                right: wrap_if_json_arrow(op.right),
813                ..*op
814            }))),
815            Expression::Div(op) => Ok(Expression::Div(Box::new(BinaryOp {
816                left: wrap_if_json_arrow(op.left),
817                right: wrap_if_json_arrow(op.right),
818                ..*op
819            }))),
820            Expression::Mod(op) => Ok(Expression::Mod(Box::new(BinaryOp {
821                left: wrap_if_json_arrow(op.left),
822                right: wrap_if_json_arrow(op.right),
823                ..*op
824            }))),
825            Expression::Concat(op) => Ok(Expression::Concat(Box::new(BinaryOp {
826                left: wrap_if_json_arrow(op.left),
827                right: wrap_if_json_arrow(op.right),
828                ..*op
829            }))),
830
831            // In expression - wrap the this part if it's JSON arrow
832            // Also transform `expr NOT IN (list)` to `NOT (expr) IN (list)` for DuckDB
833            Expression::In(mut i) => {
834                i.this = wrap_if_json_arrow(i.this);
835                if i.not {
836                    // Transform `expr NOT IN (list)` to `NOT (expr) IN (list)`
837                    i.not = false;
838                    Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
839                        this: Expression::In(i),
840                    })))
841                } else {
842                    Ok(Expression::In(i))
843                }
844            }
845
846            // Not expression - wrap the this part if it's JSON arrow
847            Expression::Not(mut n) => {
848                n.this = wrap_if_json_arrow(n.this);
849                Ok(Expression::Not(n))
850            }
851
852            // WithinGroup: PERCENTILE_CONT/DISC WITHIN GROUP (ORDER BY ...) -> QUANTILE_CONT/DISC(col, quantile ORDER BY ...)
853            Expression::WithinGroup(wg) => {
854                match &wg.this {
855                    Expression::PercentileCont(p) => {
856                        let column = wg.order_by.first().map(|o| o.this.clone()).unwrap_or_else(|| p.this.clone());
857                        let percentile = p.percentile.clone();
858                        let filter = p.filter.clone();
859                        Ok(Expression::AggregateFunction(Box::new(crate::expressions::AggregateFunction {
860                            name: "QUANTILE_CONT".to_string(),
861                            args: vec![column, percentile],
862                            distinct: false,
863                            filter,
864                            order_by: wg.order_by,
865                            limit: None,
866                            ignore_nulls: None,
867                        })))
868                    }
869                    Expression::PercentileDisc(p) => {
870                        let column = wg.order_by.first().map(|o| o.this.clone()).unwrap_or_else(|| p.this.clone());
871                        let percentile = p.percentile.clone();
872                        let filter = p.filter.clone();
873                        Ok(Expression::AggregateFunction(Box::new(crate::expressions::AggregateFunction {
874                            name: "QUANTILE_DISC".to_string(),
875                            args: vec![column, percentile],
876                            distinct: false,
877                            filter,
878                            order_by: wg.order_by,
879                            limit: None,
880                            ignore_nulls: None,
881                        })))
882                    }
883                    // Handle case where inner is AggregateFunction with PERCENTILE_CONT/DISC name
884                    Expression::AggregateFunction(af) if af.name == "PERCENTILE_CONT" || af.name == "PERCENTILE_DISC" => {
885                        let new_name = if af.name == "PERCENTILE_CONT" { "QUANTILE_CONT" } else { "QUANTILE_DISC" };
886                        let column = wg.order_by.first().map(|o| o.this.clone());
887                        let quantile = af.args.first().cloned();
888                        match (column, quantile) {
889                            (Some(col), Some(q)) => {
890                                Ok(Expression::AggregateFunction(Box::new(crate::expressions::AggregateFunction {
891                                    name: new_name.to_string(),
892                                    args: vec![col, q],
893                                    distinct: false,
894                                    filter: af.filter.clone(),
895                                    order_by: wg.order_by,
896                                    limit: None,
897                                    ignore_nulls: None,
898                                })))
899                            }
900                            _ => Ok(Expression::WithinGroup(wg)),
901                        }
902                    }
903                    _ => Ok(Expression::WithinGroup(wg)),
904                }
905            }
906
907            // ===== DuckDB @ prefix operator → ABS() =====
908            // In DuckDB, @expr means ABS(expr)
909            // Parser creates Column with name "@col" — strip the @ and wrap in ABS()
910            Expression::Column(ref c) if c.name.name.starts_with('@') && c.table.is_none() => {
911                let col_name = &c.name.name[1..]; // strip leading @
912                Ok(Expression::Abs(Box::new(UnaryFunc {
913                    this: Expression::Column(Column {
914                        name: Identifier::new(col_name),
915                        table: None,
916                        join_mark: false,
917                        trailing_comments: Vec::new(),
918                    }),
919                    original_name: None,
920                })))
921            }
922
923            // ===== SELECT-level transforms =====
924            // DuckDB colon alias syntax: `foo: bar` → `bar AS foo`
925            // Parser creates JSONExtract(this=foo, expression='bar', variant_extract=true)
926            // which needs to become Alias(this=Column(bar), alias=foo)
927            Expression::Select(mut select) => {
928                select.expressions = select.expressions.into_iter().map(|e| {
929                    match e {
930                        Expression::JSONExtract(ref je) if je.variant_extract.is_some() => {
931                            // JSONExtract(this=alias_name, expression='value', variant_extract=true) → value AS alias_name
932                            let alias_ident = match je.this.as_ref() {
933                                Expression::Identifier(ident) => Some(ident.clone()),
934                                Expression::Column(col) if col.table.is_none() => Some(col.name.clone()),
935                                _ => None,
936                            };
937                            let value_expr = match je.expression.as_ref() {
938                                Expression::Literal(Literal::String(s)) => {
939                                    // Convert string path to column reference
940                                    if s.contains('.') {
941                                        // t.col → Column { name: col, table: t }
942                                        let parts: Vec<&str> = s.splitn(2, '.').collect();
943                                        Some(Expression::Column(Column {
944                                            name: Identifier::new(parts[1]),
945                                            table: Some(Identifier::new(parts[0])),
946                                            join_mark: false,
947                                            trailing_comments: Vec::new(),
948                                        }))
949                                    } else {
950                                        Some(Expression::Column(Column {
951                                            name: Identifier::new(s.as_str()),
952                                            table: None,
953                                            join_mark: false,
954                                            trailing_comments: Vec::new(),
955                                        }))
956                                    }
957                                }
958                                _ => None,
959                            };
960
961                            if let (Some(alias), Some(value)) = (alias_ident, value_expr) {
962                                Expression::Alias(Box::new(Alias {
963                                    this: value,
964                                    alias,
965                                    column_aliases: Vec::new(),
966                                    pre_alias_comments: Vec::new(),
967                                    trailing_comments: Vec::new(),
968                                }))
969                            } else {
970                                e
971                            }
972                        }
973                        _ => e,
974                    }
975                }).collect();
976
977                // ===== DuckDB comma-join with UNNEST → JOIN ON TRUE =====
978                // Transform FROM t1, UNNEST(...) AS t2 → FROM t1 JOIN UNNEST(...) AS t2 ON TRUE
979                if let Some(ref mut from) = select.from {
980                    if from.expressions.len() > 1 {
981                        // Check if any expression after the first is UNNEST or Alias wrapping UNNEST
982                        let mut new_from_exprs = Vec::new();
983                        let mut new_joins = Vec::new();
984
985                        for (idx, expr) in from.expressions.drain(..).enumerate() {
986                            if idx == 0 {
987                                // First expression stays in FROM
988                                new_from_exprs.push(expr);
989                            } else {
990                                // Check if this is UNNEST or Alias(UNNEST)
991                                let is_unnest = match &expr {
992                                    Expression::Unnest(_) => true,
993                                    Expression::Alias(a) => matches!(a.this, Expression::Unnest(_)),
994                                    _ => false,
995                                };
996
997                                if is_unnest {
998                                    // Convert to JOIN ON TRUE
999                                    new_joins.push(crate::expressions::Join {
1000                                        this: expr,
1001                                        on: Some(Expression::Boolean(crate::expressions::BooleanLiteral { value: true })),
1002                                        using: Vec::new(),
1003                                        kind: crate::expressions::JoinKind::Inner,
1004                                        use_inner_keyword: false,
1005                                        use_outer_keyword: false,
1006                                        deferred_condition: false,
1007                                        join_hint: None,
1008                                        match_condition: None,
1009                                        pivots: Vec::new(),
1010                                    });
1011                                } else {
1012                                    // Keep non-UNNEST expressions in FROM (comma-separated)
1013                                    new_from_exprs.push(expr);
1014                                }
1015                            }
1016                        }
1017
1018                        from.expressions = new_from_exprs;
1019
1020                        // Prepend the new joins before any existing joins
1021                        new_joins.append(&mut select.joins);
1022                        select.joins = new_joins;
1023                    }
1024                }
1025
1026                Ok(Expression::Select(select))
1027            }
1028
1029            // ===== INTERVAL splitting =====
1030            // DuckDB requires INTERVAL '1' HOUR format, not INTERVAL '1 hour'
1031            // When we have INTERVAL 'value unit' (single string with embedded unit),
1032            // split it into INTERVAL 'value' UNIT
1033            Expression::Interval(interval) => {
1034                self.transform_interval(*interval)
1035            }
1036
1037            // DuckDB CREATE FUNCTION (macro syntax): strip param types, suppress RETURNS
1038            Expression::CreateFunction(mut cf) => {
1039                // Strip parameter data types (DuckDB macros don't use types)
1040                cf.parameters = cf.parameters.into_iter().map(|p| {
1041                    FunctionParameter {
1042                        name: p.name,
1043                        data_type: DataType::Custom { name: String::new() },
1044                        mode: None,
1045                        default: p.default,
1046                    }
1047                }).collect();
1048
1049                // For DuckDB macro syntax: suppress RETURNS output
1050                // Use a marker in returns_table_body to signal TABLE keyword in body
1051                let was_table_return = cf.returns_table_body.is_some()
1052                    || matches!(&cf.return_type, Some(DataType::Custom { ref name }) if name == "TABLE");
1053                cf.return_type = None;
1054                if was_table_return {
1055                    // Use empty marker to signal TABLE return without outputting RETURNS
1056                    cf.returns_table_body = Some(String::new());
1057                } else {
1058                    cf.returns_table_body = None;
1059                }
1060
1061                Ok(Expression::CreateFunction(cf))
1062            }
1063
1064            // ===== Snowflake-specific expression type transforms =====
1065
1066            // IFF(cond, true_val, false_val) -> CASE WHEN cond THEN true_val ELSE false_val END
1067            Expression::IfFunc(f) => {
1068                Ok(Expression::Case(Box::new(Case {
1069                    operand: None,
1070                    whens: vec![(f.condition, f.true_value)],
1071                    else_: f.false_value,
1072                })))
1073            }
1074
1075            // VAR_SAMP -> VARIANCE in DuckDB
1076            Expression::VarSamp(f) => Ok(Expression::Function(Box::new(Function::new(
1077                "VARIANCE".to_string(),
1078                vec![f.this],
1079            )))),
1080
1081            // 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
1082            Expression::Nvl2(f) => {
1083                let condition = Expression::IsNull(Box::new(crate::expressions::IsNull {
1084                    this: f.this,
1085                    not: true,
1086                    postfix_form: false,
1087                }));
1088                Ok(Expression::Case(Box::new(Case {
1089                    operand: None,
1090                    whens: vec![(condition, f.true_value)],
1091                    else_: Some(f.false_value),
1092                })))
1093            }
1094
1095            // Pass through everything else
1096            _ => Ok(expr),
1097        }
1098    }
1099}
1100
1101impl DuckDBDialect {
1102    /// Extract a numeric value from a literal expression, if possible
1103    fn extract_number_value(expr: &Expression) -> Option<f64> {
1104        match expr {
1105            Expression::Literal(Literal::Number(n)) => n.parse::<f64>().ok(),
1106            _ => None,
1107        }
1108    }
1109
1110    /// Convert an expression to a SQL string for template-based transformations
1111    fn expr_to_sql(expr: &Expression) -> String {
1112        crate::generator::Generator::sql(expr).unwrap_or_default()
1113    }
1114
1115    /// Extract the seed expression for random-based function emulations.
1116    /// Returns (seed_sql, is_random_no_seed) where:
1117    /// - For RANDOM(): ("RANDOM()", true)
1118    /// - For RANDOM(seed): ("seed", false) - extracts the seed
1119    /// - For literal seed: ("seed_value", false)
1120    fn extract_seed_info(gen: &Expression) -> (String, bool) {
1121        match gen {
1122            Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
1123                if func.args.is_empty() {
1124                    ("RANDOM()".to_string(), true)
1125                } else {
1126                    // RANDOM(seed) -> extract the seed
1127                    (Self::expr_to_sql(&func.args[0]), false)
1128                }
1129            }
1130            Expression::Rand(r) => {
1131                if let Some(ref seed) = r.seed {
1132                    // RANDOM(seed) / RAND(seed) -> extract the seed
1133                    (Self::expr_to_sql(seed), false)
1134                } else {
1135                    ("RANDOM()".to_string(), true)
1136                }
1137            }
1138            Expression::Random(_) => {
1139                ("RANDOM()".to_string(), true)
1140            }
1141            _ => (Self::expr_to_sql(gen), false),
1142        }
1143    }
1144
1145    /// Parse a SQL template string and wrap it in a Subquery (parenthesized expression).
1146    /// Uses a thread with larger stack to handle deeply nested template SQL in debug builds.
1147    fn parse_as_subquery(sql: &str) -> Result<Expression> {
1148        let sql_owned = sql.to_string();
1149        let handle = std::thread::Builder::new()
1150            .stack_size(16 * 1024 * 1024) // 16MB stack for complex templates
1151            .spawn(move || {
1152                match crate::parser::Parser::parse_sql(&sql_owned) {
1153                    Ok(stmts) => {
1154                        if let Some(stmt) = stmts.into_iter().next() {
1155                            Ok(Expression::Subquery(Box::new(Subquery {
1156                                this: stmt,
1157                                alias: None,
1158                                column_aliases: Vec::new(),
1159                                order_by: None,
1160                                limit: None,
1161                                offset: None,
1162                                distribute_by: None,
1163                                sort_by: None,
1164                                cluster_by: None,
1165                                lateral: false,
1166                                modifiers_inside: false,
1167                                trailing_comments: Vec::new(),
1168                            })))
1169                        } else {
1170                            Err(crate::error::Error::Generate("Failed to parse template SQL".to_string()))
1171                        }
1172                    }
1173                    Err(e) => Err(e),
1174                }
1175            })
1176            .map_err(|e| crate::error::Error::Internal(format!("Failed to spawn parser thread: {}", e)))?;
1177
1178        handle.join()
1179            .map_err(|_| crate::error::Error::Internal("Parser thread panicked".to_string()))?
1180    }
1181
1182    /// Convert bracket notation ["key with spaces"] to quoted dot notation ."key with spaces"
1183    /// in JSON path strings. This is needed because Snowflake uses bracket notation for keys
1184    /// with special characters, but DuckDB uses quoted dot notation.
1185    fn convert_bracket_to_quoted_path(path: &str) -> String {
1186        let mut result = String::new();
1187        let mut chars = path.chars().peekable();
1188        while let Some(c) = chars.next() {
1189            if c == '[' && chars.peek() == Some(&'"') {
1190                // Found [" - start of bracket notation
1191                chars.next(); // consume "
1192                let mut key = String::new();
1193                while let Some(kc) = chars.next() {
1194                    if kc == '"' && chars.peek() == Some(&']') {
1195                        chars.next(); // consume ]
1196                        break;
1197                    }
1198                    key.push(kc);
1199                }
1200                // Convert to quoted dot notation: ."key"
1201                if !result.is_empty() && !result.ends_with('.') {
1202                    result.push('.');
1203                }
1204                result.push('"');
1205                result.push_str(&key);
1206                result.push('"');
1207            } else {
1208                result.push(c);
1209            }
1210        }
1211        result
1212    }
1213
1214    /// Transform data types according to DuckDB TYPE_MAPPING
1215    fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
1216        use crate::expressions::DataType;
1217        let transformed = match dt {
1218            // BINARY -> VarBinary (DuckDB generator maps VarBinary to BLOB)
1219            DataType::Binary { .. } => DataType::VarBinary { length: None },
1220            // BLOB -> VarBinary (DuckDB generator maps VarBinary to BLOB)
1221            // This matches Python sqlglot's DuckDB parser mapping BLOB -> VARBINARY
1222            DataType::Blob => DataType::VarBinary { length: None },
1223            // CHAR -> TEXT
1224            DataType::Char { .. } => DataType::Text,
1225            // VARCHAR -> TEXT
1226            DataType::VarChar { .. } => DataType::Text,
1227            // FLOAT -> REAL (use real_spelling flag so generator can decide)
1228            DataType::Float { precision, scale, .. } => DataType::Float {
1229                precision, scale, real_spelling: true,
1230            },
1231            // JSONB -> JSON
1232            DataType::JsonB => DataType::Json,
1233            // Handle Custom type aliases used in DuckDB
1234            DataType::Custom { ref name } => {
1235                let upper = name.to_uppercase();
1236                match upper.as_str() {
1237                    // INT64 -> BIGINT
1238                    "INT64" | "INT8" => DataType::BigInt { length: None },
1239                    // INT32, INT4, SIGNED -> INT
1240                    "INT32" | "INT4" | "SIGNED" => DataType::Int { length: None, integer_spelling: false },
1241                    // INT16 -> SMALLINT
1242                    "INT16" => DataType::SmallInt { length: None },
1243                    // INT1 -> TINYINT
1244                    "INT1" => DataType::TinyInt { length: None },
1245                    // HUGEINT -> INT128
1246                    "HUGEINT" => DataType::Custom { name: "INT128".to_string() },
1247                    // UHUGEINT -> UINT128
1248                    "UHUGEINT" => DataType::Custom { name: "UINT128".to_string() },
1249                    // BPCHAR -> TEXT
1250                    "BPCHAR" => DataType::Text,
1251                    // CHARACTER VARYING, CHAR VARYING -> TEXT
1252                    "CHARACTER VARYING" | "CHAR VARYING" => DataType::Text,
1253                    // FLOAT4, REAL -> REAL
1254                    "FLOAT4" => DataType::Custom { name: "REAL".to_string() },
1255                    // LOGICAL -> BOOLEAN
1256                    "LOGICAL" => DataType::Boolean,
1257                    // TIMESTAMPNTZ / TIMESTAMP_NTZ -> TIMESTAMP
1258                    "TIMESTAMPNTZ" | "TIMESTAMP_NTZ" => DataType::Timestamp { precision: None, timezone: false },
1259                    // TIMESTAMP_US -> TIMESTAMP (DuckDB's default timestamp is microsecond precision)
1260                    "TIMESTAMP_US" => DataType::Timestamp { precision: None, timezone: false },
1261                    // TIMESTAMPLTZ / TIMESTAMPTZ / TIMESTAMP_LTZ / TIMESTAMP_TZ -> TIMESTAMPTZ
1262                    "TIMESTAMPLTZ" | "TIMESTAMP_LTZ" | "TIMESTAMPTZ" | "TIMESTAMP_TZ" => DataType::Timestamp { precision: None, timezone: true },
1263                    // DECFLOAT -> DECIMAL(38, 5) in DuckDB
1264                    "DECFLOAT" => DataType::Decimal { precision: Some(38), scale: Some(5) },
1265                    // Keep other custom types as-is
1266                    _ => dt,
1267                }
1268            }
1269            // Keep all other types as-is
1270            other => other,
1271        };
1272        Ok(Expression::DataType(transformed))
1273    }
1274
1275    /// Transform interval to split embedded value+unit strings (e.g., '1 hour' -> '1' HOUR)
1276    /// DuckDB requires INTERVAL 'value' UNIT format, not INTERVAL 'value unit' format
1277    fn transform_interval(&self, interval: Interval) -> Result<Expression> {
1278        // Only transform if:
1279        // 1. There's a string literal value
1280        // 2. There's no unit already specified
1281        if interval.unit.is_some() {
1282            // Already has a unit, keep as-is
1283            return Ok(Expression::Interval(Box::new(interval)));
1284        }
1285
1286        if let Some(Expression::Literal(Literal::String(ref s))) = interval.this {
1287            // Try to parse the string as "value unit" format
1288            if let Some((value, unit)) = Self::parse_interval_string(s) {
1289                // Create new interval with separated value and unit
1290                return Ok(Expression::Interval(Box::new(Interval {
1291                    this: Some(Expression::Literal(Literal::String(value.to_string()))),
1292                    unit: Some(IntervalUnitSpec::Simple {
1293                        unit,
1294                        use_plural: false, // DuckDB uses singular form
1295                    }),
1296                })));
1297            }
1298        }
1299
1300        // No transformation needed
1301        Ok(Expression::Interval(Box::new(interval)))
1302    }
1303
1304    /// Parse an interval string like "1 hour" into (value, unit)
1305    /// Returns None if the string doesn't match the expected format
1306    fn parse_interval_string(s: &str) -> Option<(&str, IntervalUnit)> {
1307        let s = s.trim();
1308
1309        // Find where the number ends and the unit begins
1310        // Number can be: optional -, digits, optional decimal point, more digits
1311        let mut num_end = 0;
1312        let mut chars = s.chars().peekable();
1313
1314        // Skip leading minus
1315        if chars.peek() == Some(&'-') {
1316            chars.next();
1317            num_end += 1;
1318        }
1319
1320        // Skip digits
1321        while let Some(&c) = chars.peek() {
1322            if c.is_ascii_digit() {
1323                chars.next();
1324                num_end += 1;
1325            } else {
1326                break;
1327            }
1328        }
1329
1330        // Skip optional decimal point and more digits
1331        if chars.peek() == Some(&'.') {
1332            chars.next();
1333            num_end += 1;
1334            while let Some(&c) = chars.peek() {
1335                if c.is_ascii_digit() {
1336                    chars.next();
1337                    num_end += 1;
1338                } else {
1339                    break;
1340                }
1341            }
1342        }
1343
1344        if num_end == 0 || (num_end == 1 && s.starts_with('-')) {
1345            return None; // No number found
1346        }
1347
1348        let value = &s[..num_end];
1349        let rest = s[num_end..].trim();
1350
1351        // Rest should be alphabetic (the unit)
1352        if rest.is_empty() || !rest.chars().all(|c| c.is_ascii_alphabetic()) {
1353            return None;
1354        }
1355
1356        // Map unit string to IntervalUnit
1357        let unit = match rest.to_uppercase().as_str() {
1358            "YEAR" | "YEARS" | "Y" => IntervalUnit::Year,
1359            "MONTH" | "MONTHS" | "MON" | "MONS" => IntervalUnit::Month,
1360            "DAY" | "DAYS" | "D" => IntervalUnit::Day,
1361            "HOUR" | "HOURS" | "H" | "HR" | "HRS" => IntervalUnit::Hour,
1362            "MINUTE" | "MINUTES" | "MIN" | "MINS" | "M" => IntervalUnit::Minute,
1363            "SECOND" | "SECONDS" | "SEC" | "SECS" | "S" => IntervalUnit::Second,
1364            "MILLISECOND" | "MILLISECONDS" | "MS" => IntervalUnit::Millisecond,
1365            "MICROSECOND" | "MICROSECONDS" | "US" => IntervalUnit::Microsecond,
1366            "QUARTER" | "QUARTERS" | "Q" => IntervalUnit::Quarter,
1367            "WEEK" | "WEEKS" | "W" => IntervalUnit::Week,
1368            _ => return None, // Unknown unit
1369        };
1370
1371        Some((value, unit))
1372    }
1373
1374    fn transform_function(&self, f: Function) -> Result<Expression> {
1375        let name_upper = f.name.to_uppercase();
1376        match name_upper.as_str() {
1377            // IFNULL -> COALESCE
1378            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc { original_name: None,
1379                expressions: f.args,
1380            }))),
1381
1382            // NVL -> COALESCE
1383            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc { original_name: None,
1384                expressions: f.args,
1385            }))),
1386
1387            // ISNULL -> COALESCE
1388            "ISNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc { original_name: None,
1389                expressions: f.args,
1390            }))),
1391
1392            // GROUP_CONCAT -> LISTAGG in DuckDB
1393            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1394                Function::new("LISTAGG".to_string(), f.args),
1395            ))),
1396
1397            // LISTAGG is native to DuckDB
1398            "LISTAGG" => Ok(Expression::Function(Box::new(f))),
1399
1400            // STRING_AGG -> LISTAGG in DuckDB
1401            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1402                Function::new("LISTAGG".to_string(), f.args),
1403            ))),
1404
1405            // SUBSTR -> SUBSTRING (both work)
1406            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
1407                "SUBSTRING".to_string(),
1408                f.args,
1409            )))),
1410
1411            // FLATTEN -> UNNEST in DuckDB
1412            "FLATTEN" => Ok(Expression::Function(Box::new(Function::new(
1413                "UNNEST".to_string(),
1414                f.args,
1415            )))),
1416
1417            // EXPLODE -> UNNEST
1418            "EXPLODE" => Ok(Expression::Function(Box::new(Function::new(
1419                "UNNEST".to_string(),
1420                f.args,
1421            )))),
1422
1423            // NOW -> CURRENT_TIMESTAMP
1424            "NOW" => Ok(Expression::CurrentTimestamp(
1425                crate::expressions::CurrentTimestamp { precision: None, sysdate: false },
1426            )),
1427
1428            // GETDATE -> CURRENT_TIMESTAMP
1429            "GETDATE" => Ok(Expression::CurrentTimestamp(
1430                crate::expressions::CurrentTimestamp { precision: None, sysdate: false },
1431            )),
1432
1433            // CURRENT_DATE is native
1434            "CURRENT_DATE" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
1435
1436            // TO_DATE with 1 arg -> CAST(x AS DATE)
1437            "TO_DATE" if f.args.len() == 1 => {
1438                let arg = f.args.into_iter().next().unwrap();
1439                Ok(Expression::Cast(Box::new(Cast {
1440                    this: arg,
1441                    to: DataType::Date,
1442                    trailing_comments: Vec::new(),
1443                    double_colon_syntax: false,
1444                    format: None,
1445                    default: None,
1446                })))
1447            }
1448
1449            // TO_TIMESTAMP is native in DuckDB (kept as-is for identity)
1450
1451            // DATE_FORMAT -> STRFTIME in DuckDB with format conversion
1452            "DATE_FORMAT" if f.args.len() >= 2 => {
1453                let mut args = f.args;
1454                args[1] = Self::convert_format_to_duckdb(&args[1]);
1455                Ok(Expression::Function(Box::new(Function::new(
1456                    "STRFTIME".to_string(),
1457                    args,
1458                ))))
1459            }
1460
1461            // DATE_PARSE -> STRPTIME in DuckDB with format conversion
1462            "DATE_PARSE" if f.args.len() >= 2 => {
1463                let mut args = f.args;
1464                args[1] = Self::convert_format_to_duckdb(&args[1]);
1465                Ok(Expression::Function(Box::new(Function::new(
1466                    "STRPTIME".to_string(),
1467                    args,
1468                ))))
1469            }
1470
1471            // FORMAT_DATE -> STRFTIME in DuckDB
1472            "FORMAT_DATE" if f.args.len() >= 2 => {
1473                let mut args = f.args;
1474                args[1] = Self::convert_format_to_duckdb(&args[1]);
1475                Ok(Expression::Function(Box::new(Function::new(
1476                    "STRFTIME".to_string(),
1477                    args,
1478                ))))
1479            }
1480
1481            // TO_CHAR -> STRFTIME in DuckDB
1482            "TO_CHAR" if f.args.len() >= 2 => {
1483                let mut args = f.args;
1484                args[1] = Self::convert_format_to_duckdb(&args[1]);
1485                Ok(Expression::Function(Box::new(Function::new(
1486                    "STRFTIME".to_string(),
1487                    args,
1488                ))))
1489            }
1490
1491            // EPOCH_MS is native to DuckDB
1492            "EPOCH_MS" => Ok(Expression::Function(Box::new(f))),
1493
1494            // EPOCH -> EPOCH (native)
1495            "EPOCH" => Ok(Expression::Function(Box::new(f))),
1496
1497            // FROM_UNIXTIME -> TO_TIMESTAMP in DuckDB
1498            "FROM_UNIXTIME" if !f.args.is_empty() => {
1499                Ok(Expression::Function(Box::new(Function::new(
1500                    "TO_TIMESTAMP".to_string(),
1501                    f.args,
1502                ))))
1503            }
1504
1505            // UNIX_TIMESTAMP -> EPOCH
1506            "UNIX_TIMESTAMP" => Ok(Expression::Function(Box::new(Function::new(
1507                "EPOCH".to_string(),
1508                f.args,
1509            )))),
1510
1511            // JSON_EXTRACT -> arrow operator (->)
1512            "JSON_EXTRACT" if f.args.len() == 2 => {
1513                let mut args = f.args;
1514                let path = args.pop().unwrap();
1515                let this = args.pop().unwrap();
1516                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1517                    this,
1518                    path,
1519                    returning: None,
1520                    arrow_syntax: true,
1521                    hash_arrow_syntax: false,
1522                    wrapper_option: None,
1523                    quotes_option: None,
1524                    on_scalar_string: false,
1525                    on_error: None,
1526                })))
1527            }
1528
1529            // JSON_EXTRACT_STRING -> arrow operator (->>)
1530            "JSON_EXTRACT_STRING" if f.args.len() == 2 => {
1531                let mut args = f.args;
1532                let path = args.pop().unwrap();
1533                let this = args.pop().unwrap();
1534                Ok(Expression::JsonExtractScalar(Box::new(JsonExtractFunc {
1535                    this,
1536                    path,
1537                    returning: None,
1538                    arrow_syntax: true,
1539                    hash_arrow_syntax: false,
1540                    wrapper_option: None,
1541                    quotes_option: None,
1542                    on_scalar_string: false,
1543                    on_error: None,
1544                })))
1545            }
1546
1547            // ARRAY_CONSTRUCT -> list_value or [a, b, c] syntax
1548            "ARRAY_CONSTRUCT" => Ok(Expression::Function(Box::new(Function::new(
1549                "list_value".to_string(),
1550                f.args,
1551            )))),
1552
1553            // ARRAY -> list_value
1554            // ARRAY -> list_value for non-subquery args, keep ARRAY for subquery args
1555            "ARRAY" => {
1556                // Check if any arg contains a query (subquery)
1557                let has_query = f.args.iter().any(|a| matches!(a, Expression::Subquery(_) | Expression::Select(_)));
1558                if has_query {
1559                    // Keep as ARRAY() for subquery args
1560                    Ok(Expression::Function(Box::new(Function::new(
1561                        "ARRAY".to_string(),
1562                        f.args,
1563                    ))))
1564                } else {
1565                    Ok(Expression::Function(Box::new(Function::new(
1566                        "list_value".to_string(),
1567                        f.args,
1568                    ))))
1569                }
1570            }
1571
1572            // LIST_VALUE -> Array literal notation [...]
1573            "LIST_VALUE" => Ok(Expression::Array(Box::new(crate::expressions::Array {
1574                expressions: f.args,
1575            }))),
1576
1577            // ARRAY_AGG -> LIST in DuckDB (or array_agg which is also supported)
1578            "ARRAY_AGG" => Ok(Expression::Function(Box::new(Function::new(
1579                "list".to_string(),
1580                f.args,
1581            )))),
1582
1583            // LIST_CONTAINS / ARRAY_CONTAINS -> keep normalized form
1584            "LIST_CONTAINS" | "ARRAY_CONTAINS" => Ok(Expression::Function(Box::new(Function::new(
1585                "ARRAY_CONTAINS".to_string(),
1586                f.args,
1587            )))),
1588
1589            // ARRAY_SIZE/CARDINALITY -> ARRAY_LENGTH in DuckDB
1590            "ARRAY_SIZE" | "CARDINALITY" => {
1591                Ok(Expression::Function(Box::new(Function::new(
1592                    "ARRAY_LENGTH".to_string(),
1593                    f.args,
1594                ))))
1595            }
1596
1597            // LEN -> LENGTH
1598            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
1599                f.args.into_iter().next().unwrap(),
1600            )))),
1601
1602            // CEILING -> CEIL (both work)
1603            "CEILING" if f.args.len() == 1 => Ok(Expression::Ceil(Box::new(CeilFunc {
1604                this: f.args.into_iter().next().unwrap(),
1605                decimals: None,
1606                to: None,
1607            }))),
1608
1609            // LOGICAL_OR -> BOOL_OR with CAST to BOOLEAN
1610            "LOGICAL_OR" if f.args.len() == 1 => {
1611                let arg = f.args.into_iter().next().unwrap();
1612                Ok(Expression::Function(Box::new(Function::new(
1613                    "BOOL_OR".to_string(),
1614                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
1615                        this: arg,
1616                        to: crate::expressions::DataType::Boolean,
1617                        trailing_comments: Vec::new(),
1618                        double_colon_syntax: false,
1619                        format: None,
1620                        default: None,
1621                    }))],
1622                ))))
1623            }
1624
1625            // LOGICAL_AND -> BOOL_AND with CAST to BOOLEAN
1626            "LOGICAL_AND" if f.args.len() == 1 => {
1627                let arg = f.args.into_iter().next().unwrap();
1628                Ok(Expression::Function(Box::new(Function::new(
1629                    "BOOL_AND".to_string(),
1630                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
1631                        this: arg,
1632                        to: crate::expressions::DataType::Boolean,
1633                        trailing_comments: Vec::new(),
1634                        double_colon_syntax: false,
1635                        format: None,
1636                        default: None,
1637                    }))],
1638                ))))
1639            }
1640
1641            // REGEXP_LIKE -> REGEXP_MATCHES in DuckDB
1642            "REGEXP_LIKE" => Ok(Expression::Function(Box::new(Function::new(
1643                "REGEXP_MATCHES".to_string(),
1644                f.args,
1645            )))),
1646
1647            // POSITION is native
1648            "POSITION" => Ok(Expression::Function(Box::new(f))),
1649
1650            // SPLIT -> STR_SPLIT in DuckDB
1651            "SPLIT" => Ok(Expression::Function(Box::new(Function::new(
1652                "STR_SPLIT".to_string(),
1653                f.args,
1654            )))),
1655
1656            // STRING_SPLIT -> STR_SPLIT in DuckDB
1657            "STRING_SPLIT" => Ok(Expression::Function(Box::new(Function::new(
1658                "STR_SPLIT".to_string(),
1659                f.args,
1660            )))),
1661
1662            // STRTOK_TO_ARRAY -> STR_SPLIT
1663            "STRTOK_TO_ARRAY" => Ok(Expression::Function(Box::new(Function::new(
1664                "STR_SPLIT".to_string(),
1665                f.args,
1666            )))),
1667
1668            // REGEXP_SPLIT -> STR_SPLIT_REGEX in DuckDB
1669            "REGEXP_SPLIT" => Ok(Expression::Function(Box::new(Function::new(
1670                "STR_SPLIT_REGEX".to_string(),
1671                f.args,
1672            )))),
1673
1674            // EDITDIST3 -> LEVENSHTEIN in DuckDB
1675            "EDITDIST3" => Ok(Expression::Function(Box::new(Function::new(
1676                "LEVENSHTEIN".to_string(),
1677                f.args,
1678            )))),
1679
1680            // JSON_EXTRACT_PATH -> arrow operator (->)
1681            "JSON_EXTRACT_PATH" if f.args.len() >= 2 => {
1682                let mut args = f.args;
1683                let this = args.remove(0);
1684                let path = args.remove(0);
1685                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
1686                    this,
1687                    path,
1688                    returning: None,
1689                    arrow_syntax: true,
1690                    hash_arrow_syntax: false,
1691                    wrapper_option: None,
1692                    quotes_option: None,
1693                    on_scalar_string: false,
1694                    on_error: None,
1695                })))
1696            }
1697
1698            // JSON_EXTRACT_PATH_TEXT -> arrow operator (->>)
1699            "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
1700                let mut args = f.args;
1701                let this = args.remove(0);
1702                let path = args.remove(0);
1703                Ok(Expression::JsonExtractScalar(Box::new(JsonExtractFunc {
1704                    this,
1705                    path,
1706                    returning: None,
1707                    arrow_syntax: true,
1708                    hash_arrow_syntax: false,
1709                    wrapper_option: None,
1710                    quotes_option: None,
1711                    on_scalar_string: false,
1712                    on_error: None,
1713                })))
1714            }
1715
1716            // DATE_ADD(date, interval) -> date + interval in DuckDB
1717            "DATE_ADD" if f.args.len() == 2 => {
1718                let mut args = f.args;
1719                let date = args.remove(0);
1720                let interval = args.remove(0);
1721                Ok(Expression::Add(Box::new(BinaryOp {
1722                    left: date,
1723                    right: interval,
1724                    left_comments: Vec::new(),
1725                    operator_comments: Vec::new(),
1726                    trailing_comments: Vec::new(),
1727                })))
1728            }
1729
1730            // DATE_SUB(date, interval) -> date - interval in DuckDB
1731            "DATE_SUB" if f.args.len() == 2 => {
1732                let mut args = f.args;
1733                let date = args.remove(0);
1734                let interval = args.remove(0);
1735                Ok(Expression::Sub(Box::new(BinaryOp {
1736                    left: date,
1737                    right: interval,
1738                    left_comments: Vec::new(),
1739                    operator_comments: Vec::new(),
1740                    trailing_comments: Vec::new(),
1741                })))
1742            }
1743
1744            // RANGE(n) -> RANGE(0, n) in DuckDB
1745            "RANGE" if f.args.len() == 1 => {
1746                let mut new_args = vec![Expression::number(0)];
1747                new_args.extend(f.args);
1748                Ok(Expression::Function(Box::new(Function::new(
1749                    "RANGE".to_string(),
1750                    new_args,
1751                ))))
1752            }
1753
1754            // GENERATE_SERIES(n) -> GENERATE_SERIES(0, n) in DuckDB
1755            "GENERATE_SERIES" if f.args.len() == 1 => {
1756                let mut new_args = vec![Expression::number(0)];
1757                new_args.extend(f.args);
1758                Ok(Expression::Function(Box::new(Function::new(
1759                    "GENERATE_SERIES".to_string(),
1760                    new_args,
1761                ))))
1762            }
1763
1764            // REGEXP_EXTRACT(str, pattern, 0) -> REGEXP_EXTRACT(str, pattern) in DuckDB
1765            // Drop the group argument when it's 0 (default)
1766            "REGEXP_EXTRACT" if f.args.len() == 3 => {
1767                // Check if the third argument is 0
1768                let drop_group = match &f.args[2] {
1769                    Expression::Literal(Literal::Number(n)) => n == "0",
1770                    _ => false,
1771                };
1772                if drop_group {
1773                    Ok(Expression::Function(Box::new(Function::new(
1774                        "REGEXP_EXTRACT".to_string(),
1775                        vec![f.args[0].clone(), f.args[1].clone()],
1776                    ))))
1777                } else {
1778                    Ok(Expression::Function(Box::new(f)))
1779                }
1780            }
1781
1782            // STRUCT_PACK(a := 1, b := 2) -> {'a': 1, 'b': 2} (DuckDB struct literal)
1783            "STRUCT_PACK" => {
1784                let mut fields = Vec::new();
1785                for arg in f.args {
1786                    match arg {
1787                        Expression::NamedArgument(na) => {
1788                            fields.push((Some(na.name.name.clone()), na.value));
1789                        }
1790                        // Non-named arguments get positional keys
1791                        other => {
1792                            fields.push((None, other));
1793                        }
1794                    }
1795                }
1796                Ok(Expression::Struct(Box::new(Struct { fields })))
1797            }
1798
1799            // REPLACE with 2 args -> add empty string 3rd arg
1800            "REPLACE" if f.args.len() == 2 => {
1801                let mut args = f.args;
1802                args.push(Expression::Literal(Literal::String(String::new())));
1803                Ok(Expression::Function(Box::new(Function::new(
1804                    "REPLACE".to_string(),
1805                    args,
1806                ))))
1807            }
1808
1809            // TO_UNIXTIME -> EPOCH in DuckDB
1810            "TO_UNIXTIME" => Ok(Expression::Function(Box::new(Function::new(
1811                "EPOCH".to_string(),
1812                f.args,
1813            )))),
1814
1815            // FROM_ISO8601_TIMESTAMP -> CAST(x AS TIMESTAMPTZ) in DuckDB
1816            "FROM_ISO8601_TIMESTAMP" if f.args.len() == 1 => {
1817                use crate::expressions::{Cast, DataType};
1818                Ok(Expression::Cast(Box::new(Cast {
1819                    this: f.args.into_iter().next().unwrap(),
1820                    to: DataType::Timestamp { precision: None, timezone: true },
1821                    trailing_comments: Vec::new(),
1822                    double_colon_syntax: false,
1823                    format: None,
1824                    default: None,
1825                })))
1826            }
1827
1828            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT in DuckDB
1829            "APPROX_DISTINCT" => {
1830                // Drop the accuracy parameter (second arg) if present
1831                let args = if f.args.len() > 1 {
1832                    vec![f.args.into_iter().next().unwrap()]
1833                } else {
1834                    f.args
1835                };
1836                Ok(Expression::Function(Box::new(Function::new(
1837                    "APPROX_COUNT_DISTINCT".to_string(),
1838                    args,
1839                ))))
1840            }
1841
1842            // ARRAY_SORT is native to DuckDB (but drop the lambda comparator)
1843            "ARRAY_SORT" => {
1844                let args = vec![f.args.into_iter().next().unwrap()];
1845                Ok(Expression::Function(Box::new(Function::new(
1846                    "ARRAY_SORT".to_string(),
1847                    args,
1848                )))
1849                )
1850            }
1851
1852            // TO_UTF8 -> ENCODE in DuckDB
1853            "TO_UTF8" => Ok(Expression::Function(Box::new(Function::new(
1854                "ENCODE".to_string(),
1855                f.args,
1856            )))),
1857
1858            // FROM_UTF8 -> DECODE in DuckDB
1859            "FROM_UTF8" => Ok(Expression::Function(Box::new(Function::new(
1860                "DECODE".to_string(),
1861                f.args,
1862            )))),
1863
1864            // ARBITRARY -> ANY_VALUE in DuckDB
1865            "ARBITRARY" => Ok(Expression::Function(Box::new(Function::new(
1866                "ANY_VALUE".to_string(),
1867                f.args,
1868            )))),
1869
1870            // MAX_BY -> ARG_MAX in DuckDB
1871            "MAX_BY" => Ok(Expression::Function(Box::new(Function::new(
1872                "ARG_MAX".to_string(),
1873                f.args,
1874            )))),
1875
1876            // MIN_BY -> ARG_MIN in DuckDB
1877            "MIN_BY" => Ok(Expression::Function(Box::new(Function::new(
1878                "ARG_MIN".to_string(),
1879                f.args,
1880            )))),
1881
1882            // ===== Snowflake-specific function transforms =====
1883
1884            "IFF" if f.args.len() == 3 => {
1885                let mut args = f.args;
1886                let cond = args.remove(0);
1887                let true_val = args.remove(0);
1888                let false_val = args.remove(0);
1889                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(cond, true_val)], else_: Some(false_val) })))
1890            }
1891            "SKEW" => Ok(Expression::Function(Box::new(Function::new("SKEWNESS".to_string(), f.args)))),
1892            "VAR_SAMP" => Ok(Expression::Function(Box::new(Function::new("VARIANCE".to_string(), f.args)))),
1893            "VARIANCE_POP" => Ok(Expression::Function(Box::new(Function::new("VAR_POP".to_string(), f.args)))),
1894            "REGR_VALX" if f.args.len() == 2 => {
1895                let mut args = f.args; let y = args.remove(0); let x = args.remove(0);
1896                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::IsNull(Box::new(crate::expressions::IsNull { this: y, not: false, postfix_form: false })), Expression::Cast(Box::new(Cast { this: Expression::Null(crate::expressions::Null), to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))], else_: Some(x) })))
1897            }
1898            "REGR_VALY" if f.args.len() == 2 => {
1899                let mut args = f.args; let y = args.remove(0); let x = args.remove(0);
1900                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::IsNull(Box::new(crate::expressions::IsNull { this: x, not: false, postfix_form: false })), Expression::Cast(Box::new(Cast { this: Expression::Null(crate::expressions::Null), to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))], else_: Some(y) })))
1901            }
1902            "BOOLNOT" if f.args.len() == 1 => {
1903                let arg = f.args.into_iter().next().unwrap();
1904                Ok(Expression::Not(Box::new(crate::expressions::UnaryOp { this: Expression::Paren(Box::new(Paren { this: arg, trailing_comments: Vec::new() })) })))
1905            }
1906            "BITMAP_BIT_POSITION" if f.args.len() == 1 => {
1907                let n = f.args.into_iter().next().unwrap();
1908                let case_expr = Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::Gt(Box::new(BinaryOp { left: n.clone(), right: Expression::number(0), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Sub(Box::new(BinaryOp { left: n.clone(), right: Expression::number(1), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))], else_: Some(Expression::Abs(Box::new(UnaryFunc { this: n, original_name: None }))) }));
1909                Ok(Expression::Mod(Box::new(BinaryOp { left: Expression::Paren(Box::new(Paren { this: case_expr, trailing_comments: Vec::new() })), right: Expression::number(32768), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
1910            }
1911            // GREATEST/LEAST - pass through (null-wrapping is handled by source dialect transforms)
1912            "GREATEST" | "LEAST" => Ok(Expression::Function(Box::new(f))),
1913            "GREATEST_IGNORE_NULLS" => Ok(Expression::Greatest(Box::new(VarArgFunc { expressions: f.args, original_name: None }))),
1914            "LEAST_IGNORE_NULLS" => Ok(Expression::Least(Box::new(VarArgFunc { expressions: f.args, original_name: None }))),
1915            "PARSE_JSON" => Ok(Expression::Function(Box::new(Function::new("JSON".to_string(), f.args)))),
1916            "OBJECT_CONSTRUCT_KEEP_NULL" => {
1917                // OBJECT_CONSTRUCT_KEEP_NULL -> JSON_OBJECT (preserves NULLs)
1918                Ok(Expression::Function(Box::new(Function::new("JSON_OBJECT".to_string(), f.args))))
1919            }
1920            "OBJECT_CONSTRUCT" => {
1921                // Convert to DuckDB struct literal: {'key1': val1, 'key2': val2}
1922                let args = f.args;
1923                if args.is_empty() {
1924                    // Empty OBJECT_CONSTRUCT() -> STRUCT_PACK() (no args)
1925                    Ok(Expression::Function(Box::new(Function::new("STRUCT_PACK".to_string(), vec![]))))
1926                } else {
1927                    // Build struct literal from key-value pairs
1928                    let mut fields = Vec::new();
1929                    let mut i = 0;
1930                    while i + 1 < args.len() {
1931                        let key = &args[i];
1932                        let value = args[i + 1].clone();
1933                        let key_name = match key {
1934                            Expression::Literal(Literal::String(s)) => Some(s.clone()),
1935                            _ => None,
1936                        };
1937                        fields.push((key_name, value));
1938                        i += 2;
1939                    }
1940                    Ok(Expression::Struct(Box::new(Struct { fields })))
1941                }
1942            }
1943            "IS_NULL_VALUE" if f.args.len() == 1 => {
1944                let arg = f.args.into_iter().next().unwrap();
1945                Ok(Expression::Eq(Box::new(BinaryOp { left: Expression::Function(Box::new(Function::new("JSON_TYPE".to_string(), vec![arg]))), right: Expression::Literal(Literal::String("NULL".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
1946            }
1947            "TRY_TO_DOUBLE" | "TRY_TO_NUMBER" | "TRY_TO_NUMERIC" | "TRY_TO_DECIMAL" if f.args.len() == 1 => {
1948                let arg = f.args.into_iter().next().unwrap();
1949                Ok(Expression::TryCast(Box::new(Cast { this: arg, to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1950            }
1951            "TRY_TO_TIME" if f.args.len() == 1 => {
1952                let arg = f.args.into_iter().next().unwrap();
1953                Ok(Expression::TryCast(Box::new(Cast { this: arg, to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1954            }
1955            "TRY_TO_TIME" if f.args.len() == 2 => {
1956                let mut args = f.args; let value = args.remove(0);
1957                let fmt = self.convert_snowflake_time_format(args.remove(0));
1958                Ok(Expression::TryCast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("TRY_STRPTIME".to_string(), vec![value, fmt]))), to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1959            }
1960            "TRY_TO_TIMESTAMP" if f.args.len() == 1 => {
1961                let arg = f.args.into_iter().next().unwrap();
1962                Ok(Expression::TryCast(Box::new(Cast { this: arg, to: DataType::Timestamp { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1963            }
1964            "TRY_TO_TIMESTAMP" if f.args.len() == 2 => {
1965                let mut args = f.args; let value = args.remove(0);
1966                let fmt = self.convert_snowflake_time_format(args.remove(0));
1967                Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("TRY_STRPTIME".to_string(), vec![value, fmt]))), to: DataType::Timestamp { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1968            }
1969            "TRY_TO_DATE" if f.args.len() == 1 => {
1970                let arg = f.args.into_iter().next().unwrap();
1971                Ok(Expression::TryCast(Box::new(Cast { this: arg, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
1972            }
1973            "DAYOFWEEKISO" | "DAYOFWEEK_ISO" => Ok(Expression::Function(Box::new(Function::new("ISODOW".to_string(), f.args)))),
1974            "YEAROFWEEK" | "YEAROFWEEKISO" if f.args.len() == 1 => {
1975                let arg = f.args.into_iter().next().unwrap();
1976                Ok(Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: arg, field: crate::expressions::DateTimeField::Custom("ISOYEAR".to_string()) })))
1977            }
1978            "WEEKISO" => Ok(Expression::Function(Box::new(Function::new("WEEKOFYEAR".to_string(), f.args)))),
1979            "TIME_FROM_PARTS" | "TIMEFROMPARTS" if f.args.len() == 3 => {
1980                let args_ref = &f.args;
1981                // Check if all args are in-range literals: h < 24, m < 60, s < 60
1982                let all_in_range = if let (
1983                    Some(h_val), Some(m_val), Some(s_val)
1984                ) = (
1985                    Self::extract_number_value(&args_ref[0]),
1986                    Self::extract_number_value(&args_ref[1]),
1987                    Self::extract_number_value(&args_ref[2]),
1988                ) {
1989                    h_val >= 0.0 && h_val < 24.0 && m_val >= 0.0 && m_val < 60.0 && s_val >= 0.0 && s_val < 60.0
1990                } else {
1991                    false
1992                };
1993                if all_in_range {
1994                    // Use MAKE_TIME for normal values
1995                    Ok(Expression::Function(Box::new(Function::new("MAKE_TIME".to_string(), f.args))))
1996                } else {
1997                    // TIME_FROM_PARTS(h, m, s) -> CAST('00:00:00' AS TIME) + INTERVAL ((h * 3600) + (m * 60) + s) SECOND
1998                    // Use arithmetic approach to handle out-of-range values (e.g., 100 minutes)
1999                    let mut args = f.args; let h = args.remove(0); let m = args.remove(0); let s = args.remove(0);
2000                    let seconds_expr = Expression::Add(Box::new(BinaryOp { left: Expression::Add(Box::new(BinaryOp { left: Expression::Paren(Box::new(Paren { this: Expression::Mul(Box::new(BinaryOp { left: h, right: Expression::number(3600), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), trailing_comments: Vec::new() })), right: Expression::Paren(Box::new(Paren { this: Expression::Mul(Box::new(BinaryOp { left: m, right: Expression::number(60), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), trailing_comments: Vec::new() })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), right: s, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2001                    let base_time = Expression::Cast(Box::new(Cast { this: Expression::Literal(Literal::String("00:00:00".to_string())), to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2002                    Ok(Expression::Add(Box::new(BinaryOp { left: base_time, right: Expression::Interval(Box::new(Interval { this: Some(Expression::Paren(Box::new(crate::expressions::Paren { this: seconds_expr, trailing_comments: Vec::new() }))), unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Second, use_plural: false }) })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2003                }
2004            }
2005            "TIME_FROM_PARTS" | "TIMEFROMPARTS" if f.args.len() == 4 => {
2006                let mut args = f.args; let h = args.remove(0); let m = args.remove(0); let s = args.remove(0); let ns = args.remove(0);
2007                let seconds_expr = Expression::Add(Box::new(BinaryOp { left: Expression::Add(Box::new(BinaryOp { left: Expression::Add(Box::new(BinaryOp { left: Expression::Paren(Box::new(Paren { this: Expression::Mul(Box::new(BinaryOp { left: h, right: Expression::number(3600), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), trailing_comments: Vec::new() })), right: Expression::Paren(Box::new(Paren { this: Expression::Mul(Box::new(BinaryOp { left: m, right: Expression::number(60), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), trailing_comments: Vec::new() })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), right: s, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), right: Expression::Paren(Box::new(Paren { this: Expression::Div(Box::new(BinaryOp { left: ns, right: Expression::Literal(Literal::Number("1000000000.0".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), trailing_comments: Vec::new() })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2008                let base_time = Expression::Cast(Box::new(Cast { this: Expression::Literal(Literal::String("00:00:00".to_string())), to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2009                Ok(Expression::Add(Box::new(BinaryOp { left: base_time, right: Expression::Interval(Box::new(Interval { this: Some(Expression::Paren(Box::new(crate::expressions::Paren { this: seconds_expr, trailing_comments: Vec::new() }))), unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Second, use_plural: false }) })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2010            }
2011            "TIMESTAMP_FROM_PARTS" | "TIMESTAMPFROMPARTS" if f.args.len() == 6 => Ok(Expression::Function(Box::new(Function::new("MAKE_TIMESTAMP".to_string(), f.args)))),
2012            "TIMESTAMP_FROM_PARTS" | "TIMESTAMPFROMPARTS" | "TIMESTAMP_NTZ_FROM_PARTS" if f.args.len() == 2 => {
2013                let mut args = f.args; let d = args.remove(0); let t = args.remove(0);
2014                Ok(Expression::Add(Box::new(BinaryOp { left: d, right: t, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2015            }
2016            "TIMESTAMP_LTZ_FROM_PARTS" if f.args.len() == 6 => {
2017                Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("MAKE_TIMESTAMP".to_string(), f.args))), to: DataType::Timestamp { precision: None, timezone: true }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2018            }
2019            "TIMESTAMP_TZ_FROM_PARTS" if f.args.len() == 8 => {
2020                let mut args = f.args;
2021                let ts_args = vec![args.remove(0), args.remove(0), args.remove(0), args.remove(0), args.remove(0), args.remove(0)];
2022                let _nano = args.remove(0); let tz = args.remove(0);
2023                Ok(Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: Expression::Function(Box::new(Function::new("MAKE_TIMESTAMP".to_string(), ts_args))), zone: tz })))
2024            }
2025            "BOOLAND_AGG" if f.args.len() == 1 => {
2026                let arg = f.args.into_iter().next().unwrap();
2027                Ok(Expression::Function(Box::new(Function::new("BOOL_AND".to_string(), vec![Expression::Cast(Box::new(Cast { this: arg, to: DataType::Boolean, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))]))))
2028            }
2029            "BOOLOR_AGG" if f.args.len() == 1 => {
2030                let arg = f.args.into_iter().next().unwrap();
2031                Ok(Expression::Function(Box::new(Function::new("BOOL_OR".to_string(), vec![Expression::Cast(Box::new(Cast { this: arg, to: DataType::Boolean, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))]))))
2032            }
2033            "NVL2" if f.args.len() == 3 => {
2034                let mut args = f.args; let a = args.remove(0); let b = args.remove(0); let c = args.remove(0);
2035                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::Not(Box::new(crate::expressions::UnaryOp { this: Expression::IsNull(Box::new(crate::expressions::IsNull { this: a, not: false, postfix_form: false })) })), b)], else_: Some(c) })))
2036            }
2037            "EQUAL_NULL" if f.args.len() == 2 => {
2038                let mut args = f.args; let a = args.remove(0); let b = args.remove(0);
2039                Ok(Expression::NullSafeEq(Box::new(BinaryOp { left: a, right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2040            }
2041            "EDITDISTANCE" => Ok(Expression::Function(Box::new(Function::new("LEVENSHTEIN".to_string(), f.args)))),
2042            "BITAND" if f.args.len() == 2 => {
2043                let mut args = f.args;
2044                let left = args.remove(0);
2045                let right = args.remove(0);
2046                // Wrap shift expressions in parentheses for correct precedence
2047                let wrap = |e: Expression| -> Expression {
2048                    match &e {
2049                        Expression::BitwiseLeftShift(_) | Expression::BitwiseRightShift(_) =>
2050                            Expression::Paren(Box::new(Paren { this: e, trailing_comments: Vec::new() })),
2051                        _ => e,
2052                    }
2053                };
2054                Ok(Expression::BitwiseAnd(Box::new(BinaryOp { left: wrap(left), right: wrap(right), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2055            }
2056            "BITOR" if f.args.len() == 2 => {
2057                let mut args = f.args;
2058                let left = args.remove(0);
2059                let right = args.remove(0);
2060                // Wrap shift expressions in parentheses for correct precedence
2061                let wrap = |e: Expression| -> Expression {
2062                    match &e {
2063                        Expression::BitwiseLeftShift(_) | Expression::BitwiseRightShift(_) =>
2064                            Expression::Paren(Box::new(Paren { this: e, trailing_comments: Vec::new() })),
2065                        _ => e,
2066                    }
2067                };
2068                Ok(Expression::BitwiseOr(Box::new(BinaryOp { left: wrap(left), right: wrap(right), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2069            }
2070            "BITXOR" if f.args.len() == 2 => { let mut args = f.args; Ok(Expression::BitwiseXor(Box::new(BinaryOp { left: args.remove(0), right: args.remove(0), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))) }
2071            "BITNOT" if f.args.len() == 1 => { let arg = f.args.into_iter().next().unwrap(); Ok(Expression::BitwiseNot(Box::new(crate::expressions::UnaryOp { this: Expression::Paren(Box::new(Paren { this: arg, trailing_comments: Vec::new() })) }))) }
2072            "BITSHIFTLEFT" if f.args.len() == 2 => {
2073                let mut args = f.args; let a = args.remove(0); let b = args.remove(0);
2074                // Check if first arg is BINARY/BLOB type (e.g., X'002A'::BINARY)
2075                let is_binary = if let Expression::Cast(ref c) = a {
2076                    matches!(&c.to, DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob)
2077                        || matches!(&c.to, DataType::Custom { name } if name == "BLOB")
2078                } else { false };
2079                if is_binary {
2080                    // CAST(CAST(a AS BIT) << b AS BLOB)
2081                    let cast_to_bit = Expression::Cast(Box::new(Cast { this: a, to: DataType::Custom { name: "BIT".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2082                    let shift = Expression::BitwiseLeftShift(Box::new(BinaryOp { left: cast_to_bit, right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2083                    Ok(Expression::Cast(Box::new(Cast { this: shift, to: DataType::Custom { name: "BLOB".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2084                } else {
2085                    Ok(Expression::BitwiseLeftShift(Box::new(BinaryOp { left: Expression::Cast(Box::new(Cast { this: a, to: DataType::Custom { name: "INT128".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })), right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2086                }
2087            }
2088            "BITSHIFTRIGHT" if f.args.len() == 2 => {
2089                let mut args = f.args; let a = args.remove(0); let b = args.remove(0);
2090                // Check if first arg is BINARY/BLOB type (e.g., X'002A'::BINARY)
2091                let is_binary = if let Expression::Cast(ref c) = a {
2092                    matches!(&c.to, DataType::Binary { .. } | DataType::VarBinary { .. } | DataType::Blob)
2093                        || matches!(&c.to, DataType::Custom { name } if name == "BLOB")
2094                } else { false };
2095                if is_binary {
2096                    // CAST(CAST(a AS BIT) >> b AS BLOB)
2097                    let cast_to_bit = Expression::Cast(Box::new(Cast { this: a, to: DataType::Custom { name: "BIT".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2098                    let shift = Expression::BitwiseRightShift(Box::new(BinaryOp { left: cast_to_bit, right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2099                    Ok(Expression::Cast(Box::new(Cast { this: shift, to: DataType::Custom { name: "BLOB".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2100                } else {
2101                    Ok(Expression::BitwiseRightShift(Box::new(BinaryOp { left: Expression::Cast(Box::new(Cast { this: a, to: DataType::Custom { name: "INT128".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })), right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
2102                }
2103            }
2104            "SQUARE" if f.args.len() == 1 => { let arg = f.args.into_iter().next().unwrap(); Ok(Expression::Function(Box::new(Function::new("POWER".to_string(), vec![arg, Expression::number(2)])))) }
2105            "UUID_STRING" => Ok(Expression::Function(Box::new(Function::new("UUID".to_string(), vec![])))),
2106            "ENDSWITH" => Ok(Expression::Function(Box::new(Function::new("ENDS_WITH".to_string(), f.args)))),
2107            // REGEXP_REPLACE: 'g' flag is handled by cross_dialect_normalize for source dialects
2108            // that default to global replacement (e.g., Snowflake). DuckDB defaults to first-match,
2109            // so no 'g' flag needed for DuckDB identity or PostgreSQL->DuckDB.
2110            "REGEXP_REPLACE" if f.args.len() == 2 => {
2111                // 2-arg form (subject, pattern) -> add empty replacement
2112                let mut args = f.args; args.push(Expression::Literal(Literal::String(String::new())));
2113                Ok(Expression::Function(Box::new(Function::new("REGEXP_REPLACE".to_string(), args))))
2114            }
2115            "DIV0" if f.args.len() == 2 => {
2116                let mut args = f.args; let a = args.remove(0); let b = args.remove(0);
2117                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::And(Box::new(BinaryOp { left: Expression::Eq(Box::new(BinaryOp { left: b.clone(), right: Expression::number(0), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), right: Expression::Not(Box::new(crate::expressions::UnaryOp { this: Expression::IsNull(Box::new(crate::expressions::IsNull { this: a.clone(), not: false, postfix_form: false })) })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::number(0))], else_: Some(Expression::Div(Box::new(BinaryOp { left: a, right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))) })))
2118            }
2119            "DIV0NULL" if f.args.len() == 2 => {
2120                let mut args = f.args; let a = args.remove(0); let b = args.remove(0);
2121                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::Or(Box::new(BinaryOp { left: Expression::Eq(Box::new(BinaryOp { left: b.clone(), right: Expression::number(0), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), right: Expression::IsNull(Box::new(crate::expressions::IsNull { this: b.clone(), not: false, postfix_form: false })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::number(0))], else_: Some(Expression::Div(Box::new(BinaryOp { left: a, right: b, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))) })))
2122            }
2123            "ZEROIFNULL" if f.args.len() == 1 => {
2124                let x = f.args.into_iter().next().unwrap();
2125                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::IsNull(Box::new(crate::expressions::IsNull { this: x.clone(), not: false, postfix_form: false })), Expression::number(0))], else_: Some(x) })))
2126            }
2127            "NULLIFZERO" if f.args.len() == 1 => {
2128                let x = f.args.into_iter().next().unwrap();
2129                Ok(Expression::Case(Box::new(Case { operand: None, whens: vec![(Expression::Eq(Box::new(BinaryOp { left: x.clone(), right: Expression::number(0), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Null(crate::expressions::Null))], else_: Some(x) })))
2130            }
2131            "TO_DOUBLE" if f.args.len() == 1 => { let arg = f.args.into_iter().next().unwrap(); Ok(Expression::Cast(Box::new(Cast { this: arg, to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))) }
2132            "DATE" if f.args.len() == 1 => { let arg = f.args.into_iter().next().unwrap(); Ok(Expression::Cast(Box::new(Cast { this: arg, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))) }
2133            "DATE" if f.args.len() == 2 => {
2134                let mut args = f.args; let value = args.remove(0); let fmt = self.convert_snowflake_date_format(args.remove(0));
2135                Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRPTIME".to_string(), vec![value, fmt]))), to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2136            }
2137            "SYSDATE" => Ok(Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: Expression::CurrentTimestamp(crate::expressions::CurrentTimestamp { precision: None, sysdate: false }), zone: Expression::Literal(Literal::String("UTC".to_string())) }))),
2138            "HEX_DECODE_BINARY" => Ok(Expression::Function(Box::new(Function::new("UNHEX".to_string(), f.args)))),
2139            "CONVERT_TIMEZONE" if f.args.len() == 3 => {
2140                let mut args = f.args; let src_tz = args.remove(0); let tgt_tz = args.remove(0); let ts = args.remove(0);
2141                let cast_ts = Expression::Cast(Box::new(Cast { this: ts, to: DataType::Timestamp { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2142                Ok(Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: cast_ts, zone: src_tz })), zone: tgt_tz })))
2143            }
2144            "CONVERT_TIMEZONE" if f.args.len() == 2 => {
2145                let mut args = f.args; let tgt_tz = args.remove(0); let ts = args.remove(0);
2146                let cast_ts = Expression::Cast(Box::new(Cast { this: ts, to: DataType::Timestamp { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2147                Ok(Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: cast_ts, zone: tgt_tz })))
2148            }
2149            "DATE_PART" | "DATEPART" if f.args.len() == 2 => self.transform_date_part(f.args),
2150            "DATEADD" | "TIMEADD" if f.args.len() == 3 => self.transform_dateadd(f.args),
2151            "TIMESTAMPADD" if f.args.len() == 3 => self.transform_dateadd(f.args),
2152            "DATEDIFF" | "TIMEDIFF" if f.args.len() == 3 => self.transform_datediff(f.args),
2153            "TIMESTAMPDIFF" if f.args.len() == 3 => self.transform_datediff(f.args),
2154            "CORR" if f.args.len() == 2 => {
2155                // DuckDB handles NaN natively - no ISNAN wrapping needed
2156                Ok(Expression::Function(Box::new(f)))
2157            }
2158            "TO_TIMESTAMP" | "TO_TIMESTAMP_NTZ" if f.args.len() == 2 => {
2159                let mut args = f.args; let value = args.remove(0); let second_arg = args.remove(0);
2160                match &second_arg {
2161                    Expression::Literal(Literal::Number(_)) => Ok(Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone { this: Expression::Function(Box::new(Function::new("TO_TIMESTAMP".to_string(), vec![Expression::Div(Box::new(BinaryOp { left: value, right: Expression::Function(Box::new(Function::new("POWER".to_string(), vec![Expression::number(10), second_arg]))), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))]))), zone: Expression::Literal(Literal::String("UTC".to_string())) }))),
2162                    _ => { let fmt = self.convert_snowflake_time_format(second_arg); Ok(Expression::Function(Box::new(Function::new("STRPTIME".to_string(), vec![value, fmt])))) }
2163                }
2164            }
2165            "TO_TIME" if f.args.len() == 1 => { let arg = f.args.into_iter().next().unwrap(); Ok(Expression::Cast(Box::new(Cast { this: arg, to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))) }
2166            "TO_TIME" if f.args.len() == 2 => {
2167                let mut args = f.args; let value = args.remove(0); let fmt = self.convert_snowflake_time_format(args.remove(0));
2168                Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRPTIME".to_string(), vec![value, fmt]))), to: DataType::Time { precision: None, timezone: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2169            }
2170            "TO_DATE" if f.args.len() == 2 => {
2171                let mut args = f.args; let value = args.remove(0); let fmt = self.convert_snowflake_date_format(args.remove(0));
2172                Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRPTIME".to_string(), vec![value, fmt]))), to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
2173            }
2174            // LAST_DAY with 2 args handled by comprehensive handler below
2175
2176            // SAFE_DIVIDE(x, y) -> CASE WHEN y <> 0 THEN x / y ELSE NULL END
2177            "SAFE_DIVIDE" if f.args.len() == 2 => {
2178                let mut args = f.args;
2179                let x = args.remove(0);
2180                let y = args.remove(0);
2181                Ok(Expression::Case(Box::new(Case {
2182                    operand: None,
2183                    whens: vec![(
2184                        Expression::Neq(Box::new(BinaryOp {
2185                            left: y.clone(),
2186                            right: Expression::number(0),
2187                            left_comments: Vec::new(),
2188                            operator_comments: Vec::new(),
2189                            trailing_comments: Vec::new(),
2190                        })),
2191                        Expression::Div(Box::new(BinaryOp {
2192                            left: x,
2193                            right: y,
2194                            left_comments: Vec::new(),
2195                            operator_comments: Vec::new(),
2196                            trailing_comments: Vec::new(),
2197                        })),
2198                    )],
2199                    else_: Some(Expression::Null(crate::expressions::Null)),
2200                })))
2201            }
2202
2203            // TO_HEX(x) -> LOWER(HEX(x)) in DuckDB (BigQuery TO_HEX returns lowercase)
2204            "TO_HEX" if f.args.len() == 1 => {
2205                let arg = f.args.into_iter().next().unwrap();
2206                Ok(Expression::Lower(Box::new(UnaryFunc::new(
2207                    Expression::Function(Box::new(Function::new("HEX".to_string(), vec![arg]))),
2208                ))))
2209            }
2210
2211            // EDIT_DISTANCE -> LEVENSHTEIN in DuckDB
2212            "EDIT_DISTANCE" if f.args.len() >= 2 => {
2213                // Only use the first two args (drop max_distance kwarg)
2214                let mut args = f.args;
2215                let a = args.remove(0);
2216                let b = args.remove(0);
2217                Ok(Expression::Function(Box::new(Function::new(
2218                    "LEVENSHTEIN".to_string(),
2219                    vec![a, b],
2220                ))))
2221            }
2222
2223            // UNIX_DATE(d) -> DATE_DIFF('DAY', CAST('1970-01-01' AS DATE), d) in DuckDB
2224            "UNIX_DATE" if f.args.len() == 1 => {
2225                let arg = f.args.into_iter().next().unwrap();
2226                Ok(Expression::Function(Box::new(Function::new(
2227                    "DATE_DIFF".to_string(),
2228                    vec![
2229                        Expression::Literal(Literal::String("DAY".to_string())),
2230                        Expression::Cast(Box::new(Cast {
2231                            this: Expression::Literal(Literal::String("1970-01-01".to_string())),
2232                            to: DataType::Date,
2233                            trailing_comments: Vec::new(),
2234                            double_colon_syntax: false,
2235                            format: None,
2236                            default: None,
2237                        })),
2238                        arg,
2239                    ],
2240                ))))
2241            }
2242
2243            // TIMESTAMP(x) -> CAST(x AS TIMESTAMPTZ) in DuckDB
2244            "TIMESTAMP" if f.args.len() == 1 => {
2245                let arg = f.args.into_iter().next().unwrap();
2246                Ok(Expression::Cast(Box::new(Cast {
2247                    this: arg,
2248                    to: DataType::Custom { name: "TIMESTAMPTZ".to_string() },
2249                    trailing_comments: Vec::new(),
2250                    double_colon_syntax: false,
2251                    format: None,
2252                    default: None,
2253                })))
2254            }
2255
2256            // TIME(h, m, s) -> MAKE_TIME(h, m, s) in DuckDB
2257            "TIME" if f.args.len() == 3 => {
2258                Ok(Expression::Function(Box::new(Function::new(
2259                    "MAKE_TIME".to_string(),
2260                    f.args,
2261                ))))
2262            }
2263
2264            // DATE(y, m, d) -> MAKE_DATE(y, m, d) in DuckDB
2265            "DATE" if f.args.len() == 3 => {
2266                Ok(Expression::Function(Box::new(Function::new(
2267                    "MAKE_DATE".to_string(),
2268                    f.args,
2269                ))))
2270            }
2271
2272            // DATETIME(y, m, d, h, min, sec) -> MAKE_TIMESTAMP(y, m, d, h, min, sec) in DuckDB
2273            "DATETIME" if f.args.len() == 6 => {
2274                Ok(Expression::Function(Box::new(Function::new(
2275                    "MAKE_TIMESTAMP".to_string(),
2276                    f.args,
2277                ))))
2278            }
2279
2280            // PARSE_TIMESTAMP(fmt, x) -> STRPTIME(x, fmt) in DuckDB (swap args)
2281            "PARSE_TIMESTAMP" if f.args.len() >= 2 => {
2282                let mut args = f.args;
2283                let fmt = args.remove(0);
2284                let value = args.remove(0);
2285                // Convert BigQuery format to DuckDB strptime format
2286                let duckdb_fmt = self.convert_bq_to_strptime_format(fmt);
2287                Ok(Expression::Function(Box::new(Function::new(
2288                    "STRPTIME".to_string(),
2289                    vec![value, duckdb_fmt],
2290                ))))
2291            }
2292
2293            // BOOLAND(a, b) -> ((a) AND (b))
2294            "BOOLAND" if f.args.len() == 2 => {
2295                let mut args = f.args;
2296                let a = args.remove(0);
2297                let b = args.remove(0);
2298                Ok(Expression::Paren(Box::new(Paren {
2299                    this: Expression::And(Box::new(BinaryOp {
2300                        left: Expression::Paren(Box::new(Paren { this: a, trailing_comments: Vec::new() })),
2301                        right: Expression::Paren(Box::new(Paren { this: b, trailing_comments: Vec::new() })),
2302                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2303                    })),
2304                    trailing_comments: Vec::new(),
2305                })))
2306            }
2307
2308            // BOOLOR(a, b) -> ((a) OR (b))
2309            "BOOLOR" if f.args.len() == 2 => {
2310                let mut args = f.args;
2311                let a = args.remove(0);
2312                let b = args.remove(0);
2313                Ok(Expression::Paren(Box::new(Paren {
2314                    this: Expression::Or(Box::new(BinaryOp {
2315                        left: Expression::Paren(Box::new(Paren { this: a, trailing_comments: Vec::new() })),
2316                        right: Expression::Paren(Box::new(Paren { this: b, trailing_comments: Vec::new() })),
2317                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2318                    })),
2319                    trailing_comments: Vec::new(),
2320                })))
2321            }
2322
2323            // DECODE(expr, search1, result1, ..., default) -> CASE WHEN expr = search1 OR (expr IS NULL AND search1 IS NULL) THEN result1 ... ELSE default END
2324            "DECODE" if f.args.len() >= 3 => {
2325                let mut args = f.args;
2326                let expr = args.remove(0);
2327                let mut whens = Vec::new();
2328                let mut else_expr = None;
2329                while args.len() >= 2 {
2330                    let search = args.remove(0);
2331                    let result = args.remove(0);
2332                    // Wrap complex expressions in parens for = comparison
2333                    let needs_paren_expr = matches!(&expr, Expression::Eq(_) | Expression::Neq(_) | Expression::Gt(_) | Expression::Lt(_) | Expression::Gte(_) | Expression::Lte(_) | Expression::And(_) | Expression::Or(_));
2334                    let needs_paren_search = matches!(&search, Expression::Eq(_) | Expression::Neq(_) | Expression::Gt(_) | Expression::Lt(_) | Expression::Gte(_) | Expression::Lte(_) | Expression::And(_) | Expression::Or(_));
2335                    let eq_left = if needs_paren_expr { Expression::Paren(Box::new(Paren { this: expr.clone(), trailing_comments: Vec::new() })) } else { expr.clone() };
2336                    let eq_right = if needs_paren_search { Expression::Paren(Box::new(Paren { this: search.clone(), trailing_comments: Vec::new() })) } else { search.clone() };
2337                    let is_null_expr = if needs_paren_expr { Expression::Paren(Box::new(Paren { this: expr.clone(), trailing_comments: Vec::new() })) } else { expr.clone() };
2338                    let is_null_search = if needs_paren_search { Expression::Paren(Box::new(Paren { this: search.clone(), trailing_comments: Vec::new() })) } else { search.clone() };
2339                    let condition = Expression::Or(Box::new(BinaryOp {
2340                        left: Expression::Eq(Box::new(BinaryOp { left: eq_left, right: eq_right, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
2341                        right: Expression::Paren(Box::new(Paren {
2342                            this: Expression::And(Box::new(BinaryOp {
2343                                left: Expression::IsNull(Box::new(crate::expressions::IsNull { this: is_null_expr, not: false, postfix_form: false })),
2344                                right: Expression::IsNull(Box::new(crate::expressions::IsNull { this: is_null_search, not: false, postfix_form: false })),
2345                                left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2346                            })),
2347                            trailing_comments: Vec::new(),
2348                        })),
2349                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2350                    }));
2351                    whens.push((condition, result));
2352                }
2353                if !args.is_empty() {
2354                    else_expr = Some(args.remove(0));
2355                }
2356                Ok(Expression::Case(Box::new(Case { operand: None, whens, else_: else_expr })))
2357            }
2358
2359            // TRY_TO_BOOLEAN -> CASE WHEN UPPER(CAST(x AS TEXT)) = 'ON' THEN TRUE WHEN ... = 'OFF' THEN FALSE ELSE TRY_CAST(x AS BOOLEAN) END
2360            "TRY_TO_BOOLEAN" if f.args.len() == 1 => {
2361                let arg = f.args.into_iter().next().unwrap();
2362                let cast_text = Expression::Cast(Box::new(Cast { this: arg.clone(), to: DataType::Text, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2363                let upper_text = Expression::Upper(Box::new(UnaryFunc::new(cast_text)));
2364                Ok(Expression::Case(Box::new(Case {
2365                    operand: None,
2366                    whens: vec![
2367                        (Expression::Eq(Box::new(BinaryOp { left: upper_text.clone(), right: Expression::Literal(Literal::String("ON".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Boolean(crate::expressions::BooleanLiteral { value: true })),
2368                        (Expression::Eq(Box::new(BinaryOp { left: upper_text, right: Expression::Literal(Literal::String("OFF".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Boolean(crate::expressions::BooleanLiteral { value: false })),
2369                    ],
2370                    else_: Some(Expression::TryCast(Box::new(Cast { this: arg, to: DataType::Boolean, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
2371                })))
2372            }
2373
2374            // TO_BOOLEAN -> complex CASE expression
2375            "TO_BOOLEAN" if f.args.len() == 1 => {
2376                let arg = f.args.into_iter().next().unwrap();
2377                let cast_text = Expression::Cast(Box::new(Cast { this: arg.clone(), to: DataType::Text, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }));
2378                let upper_text = Expression::Upper(Box::new(UnaryFunc::new(cast_text)));
2379                Ok(Expression::Case(Box::new(Case {
2380                    operand: None,
2381                    whens: vec![
2382                        (Expression::Eq(Box::new(BinaryOp { left: upper_text.clone(), right: Expression::Literal(Literal::String("ON".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Boolean(crate::expressions::BooleanLiteral { value: true })),
2383                        (Expression::Eq(Box::new(BinaryOp { left: upper_text, right: Expression::Literal(Literal::String("OFF".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })), Expression::Boolean(crate::expressions::BooleanLiteral { value: false })),
2384                        (Expression::Or(Box::new(BinaryOp {
2385                            left: Expression::Function(Box::new(Function::new("ISNAN".to_string(), vec![Expression::TryCast(Box::new(Cast { this: arg.clone(), to: DataType::Custom { name: "REAL".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))]))),
2386                            right: Expression::Function(Box::new(Function::new("ISINF".to_string(), vec![Expression::TryCast(Box::new(Cast { this: arg.clone(), to: DataType::Custom { name: "REAL".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))]))),
2387                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2388                        })), Expression::Function(Box::new(Function::new("ERROR".to_string(), vec![Expression::Literal(Literal::String("TO_BOOLEAN: Non-numeric values NaN and INF are not supported".to_string()))])))),
2389                    ],
2390                    else_: Some(Expression::Cast(Box::new(Cast { this: arg, to: DataType::Boolean, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
2391                })))
2392            }
2393
2394            // OBJECT_INSERT(obj, key, value) -> STRUCT_INSERT(obj, key := value)
2395            // Special case: OBJECT_INSERT(OBJECT_CONSTRUCT(), key, value) -> STRUCT_PACK(key := value)
2396            "OBJECT_INSERT" if f.args.len() == 3 => {
2397                let mut args = f.args;
2398                let obj = args.remove(0);
2399                let key = args.remove(0);
2400                let value = args.remove(0);
2401                // Extract key string for named arg
2402                let key_name = match &key {
2403                    Expression::Literal(Literal::String(s)) => s.clone(),
2404                    _ => "key".to_string(),
2405                };
2406                let named_arg = Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
2407                    name: Identifier::new(&key_name),
2408                    value,
2409                    separator: crate::expressions::NamedArgSeparator::ColonEq,
2410                }));
2411                // Check if the inner object is an empty STRUCT_PACK or OBJECT_CONSTRUCT
2412                let is_empty_struct = match &obj {
2413                    Expression::Struct(s) if s.fields.is_empty() => true,
2414                    Expression::Function(f) => {
2415                        let n = f.name.to_uppercase();
2416                        (n == "STRUCT_PACK" || n == "OBJECT_CONSTRUCT") && f.args.is_empty()
2417                    }
2418                    _ => false,
2419                };
2420                if is_empty_struct {
2421                    // Collapse: OBJECT_INSERT(empty, key, value) -> STRUCT_PACK(key := value)
2422                    Ok(Expression::Function(Box::new(Function::new(
2423                        "STRUCT_PACK".to_string(),
2424                        vec![named_arg],
2425                    ))))
2426                } else {
2427                    Ok(Expression::Function(Box::new(Function::new(
2428                        "STRUCT_INSERT".to_string(),
2429                        vec![obj, named_arg],
2430                    ))))
2431                }
2432            }
2433
2434            // GET(array_or_obj, key) -> array[key+1] for arrays, obj -> '$.key' for objects
2435            "GET" if f.args.len() == 2 => {
2436                let mut args = f.args;
2437                let this = args.remove(0);
2438                let key = args.remove(0);
2439                match &key {
2440                    // String key -> JSON extract (object access)
2441                    Expression::Literal(Literal::String(s)) => {
2442                        let json_path = format!("$.{}", s);
2443                        Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
2444                            this,
2445                            path: Expression::Literal(Literal::String(json_path)),
2446                            returning: None,
2447                            arrow_syntax: true,
2448                            hash_arrow_syntax: false,
2449                            wrapper_option: None,
2450                            quotes_option: None,
2451                            on_scalar_string: false,
2452                            on_error: None,
2453                        })))
2454                    }
2455                    // Numeric key -> array subscript
2456                    // For MAP access: key is used as-is (map[key])
2457                    // For ARRAY access: Snowflake is 0-based, DuckDB is 1-based, so add 1
2458                    Expression::Literal(Literal::Number(n)) => {
2459                        let idx: i64 = n.parse().unwrap_or(0);
2460                        let is_map = matches!(&this, Expression::Cast(c) if matches!(c.to, DataType::Map { .. }));
2461                        let index_val = if is_map { idx } else { idx + 1 };
2462                        Ok(Expression::Subscript(Box::new(crate::expressions::Subscript {
2463                            this,
2464                            index: Expression::number(index_val),
2465                        })))
2466                    }
2467                    _ => {
2468                        // Unknown key type - use JSON arrow
2469                        Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
2470                            this,
2471                            path: Expression::JSONPath(Box::new(JSONPath { expressions: vec![Expression::JSONPathRoot(JSONPathRoot), Expression::JSONPathKey(Box::new(JSONPathKey { this: Box::new(key) }))], escape: None })),
2472                            returning: None,
2473                            arrow_syntax: true,
2474                            hash_arrow_syntax: false,
2475                            wrapper_option: None,
2476                            quotes_option: None,
2477                            on_scalar_string: false,
2478                            on_error: None,
2479                        })))
2480                    }
2481                }
2482            }
2483
2484            // GET_PATH(obj, path) -> obj -> json_path in DuckDB
2485            "GET_PATH" if f.args.len() == 2 => {
2486                let mut args = f.args;
2487                let this = args.remove(0);
2488                let path = args.remove(0);
2489                // Convert Snowflake path to JSONPath
2490                let json_path = match &path {
2491                    Expression::Literal(Literal::String(s)) => {
2492                        // Convert bracket notation ["key"] to quoted dot notation ."key"
2493                        let s = Self::convert_bracket_to_quoted_path(s);
2494                        // Convert Snowflake path (e.g., 'attr[0].name' or '[0].attr') to JSON path ($.attr[0].name or $[0].attr)
2495                        let normalized = if s.starts_with('$') {
2496                            s
2497                        } else if s.starts_with('[') {
2498                            format!("${}", s)
2499                        } else {
2500                            format!("$.{}", s)
2501                        };
2502                        Expression::Literal(Literal::String(normalized))
2503                    }
2504                    _ => path,
2505                };
2506                Ok(Expression::JsonExtract(Box::new(JsonExtractFunc {
2507                    this,
2508                    path: json_path,
2509                    returning: None,
2510                    arrow_syntax: true,
2511                    hash_arrow_syntax: false,
2512                    wrapper_option: None,
2513                    quotes_option: None,
2514                    on_scalar_string: false,
2515                    on_error: None,
2516                })))
2517            }
2518
2519            // BASE64_ENCODE(x) -> TO_BASE64(x)
2520            "BASE64_ENCODE" if f.args.len() == 1 => {
2521                Ok(Expression::Function(Box::new(Function::new("TO_BASE64".to_string(), f.args))))
2522            }
2523
2524            // BASE64_ENCODE(x, max_line_length) -> RTRIM(REGEXP_REPLACE(TO_BASE64(x), '(.{N})', '\1' || CHR(10), 'g'), CHR(10))
2525            "BASE64_ENCODE" if f.args.len() >= 2 => {
2526                let mut args = f.args;
2527                let x = args.remove(0);
2528                let line_len = args.remove(0);
2529                let line_len_str = match &line_len {
2530                    Expression::Literal(Literal::Number(n)) => n.clone(),
2531                    _ => "76".to_string(),
2532                };
2533                let to_base64 = Expression::Function(Box::new(Function::new("TO_BASE64".to_string(), vec![x])));
2534                let pattern = format!("(.{{{}}})", line_len_str);
2535                let chr_10 = Expression::Function(Box::new(Function::new("CHR".to_string(), vec![Expression::number(10)])));
2536                let replacement = Expression::Concat(Box::new(BinaryOp {
2537                    left: Expression::Literal(Literal::String("\\1".to_string())),
2538                    right: chr_10.clone(),
2539                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2540                }));
2541                let regexp_replace = Expression::Function(Box::new(Function::new("REGEXP_REPLACE".to_string(), vec![
2542                    to_base64,
2543                    Expression::Literal(Literal::String(pattern)),
2544                    replacement,
2545                    Expression::Literal(Literal::String("g".to_string())),
2546                ])));
2547                Ok(Expression::Function(Box::new(Function::new("RTRIM".to_string(), vec![regexp_replace, chr_10]))))
2548            }
2549
2550            // TRY_TO_DATE with 2 args -> CAST(CAST(TRY_STRPTIME(value, fmt) AS TIMESTAMP) AS DATE)
2551            "TRY_TO_DATE" if f.args.len() == 2 => {
2552                let mut args = f.args;
2553                let value = args.remove(0);
2554                let fmt = self.convert_snowflake_date_format(args.remove(0));
2555                Ok(Expression::Cast(Box::new(Cast {
2556                    this: Expression::Cast(Box::new(Cast {
2557                        this: Expression::Function(Box::new(Function::new("TRY_STRPTIME".to_string(), vec![value, fmt]))),
2558                        to: DataType::Timestamp { precision: None, timezone: false },
2559                        trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2560                    })),
2561                    to: DataType::Date,
2562                    trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2563                })))
2564            }
2565
2566            // REGEXP_REPLACE with 4 args: check if 4th arg is a number (Snowflake position) or flags (DuckDB native)
2567            // REGEXP_REPLACE with 4 args: check if 4th is a string flag (DuckDB native) or a numeric position
2568            "REGEXP_REPLACE" if f.args.len() == 4 => {
2569                let is_snowflake_position = matches!(&f.args[3], Expression::Literal(Literal::Number(_)));
2570                if is_snowflake_position {
2571                    // Snowflake form: REGEXP_REPLACE(subject, pattern, replacement, position) -> add 'g' flag
2572                    let mut args = f.args;
2573                    let subject = args.remove(0);
2574                    let pattern = args.remove(0);
2575                    let replacement = args.remove(0);
2576                    Ok(Expression::Function(Box::new(Function::new(
2577                        "REGEXP_REPLACE".to_string(),
2578                        vec![subject, pattern, replacement, Expression::Literal(Literal::String("g".to_string()))],
2579                    ))))
2580                } else {
2581                    // DuckDB native form (string flags) or pass through
2582                    Ok(Expression::Function(Box::new(f)))
2583                }
2584            }
2585
2586            // REGEXP_REPLACE with 5+ args -> Snowflake form: (subject, pattern, replacement, position, occurrence, params)
2587            "REGEXP_REPLACE" if f.args.len() >= 5 => {
2588                let mut args = f.args;
2589                let subject = args.remove(0);
2590                let pattern = args.remove(0);
2591                let replacement = args.remove(0);
2592                let _position = args.remove(0);
2593                let occurrence = if !args.is_empty() { Some(args.remove(0)) } else { None };
2594                let params = if !args.is_empty() { Some(args.remove(0)) } else { None };
2595
2596                let mut flags = String::new();
2597                if let Some(Expression::Literal(Literal::String(p))) = &params {
2598                    flags = p.clone();
2599                }
2600                let is_global = match &occurrence {
2601                    Some(Expression::Literal(Literal::Number(n))) => n == "0",
2602                    None => true,
2603                    _ => false,
2604                };
2605                if is_global && !flags.contains('g') {
2606                    flags.push('g');
2607                }
2608
2609                Ok(Expression::Function(Box::new(Function::new(
2610                    "REGEXP_REPLACE".to_string(),
2611                    vec![subject, pattern, replacement, Expression::Literal(Literal::String(flags))],
2612                ))))
2613            }
2614
2615            // ROUND with named args (EXPR =>, SCALE =>, ROUNDING_MODE =>)
2616            "ROUND" if f.args.iter().any(|a| matches!(a, Expression::NamedArgument(_))) => {
2617                let mut expr_val = None;
2618                let mut scale_val = None;
2619                let mut rounding_mode = None;
2620                for arg in &f.args {
2621                    if let Expression::NamedArgument(na) = arg {
2622                        match na.name.name.to_uppercase().as_str() {
2623                            "EXPR" => expr_val = Some(na.value.clone()),
2624                            "SCALE" => scale_val = Some(na.value.clone()),
2625                            "ROUNDING_MODE" => rounding_mode = Some(na.value.clone()),
2626                            _ => {}
2627                        }
2628                    }
2629                }
2630                if let Some(expr) = expr_val {
2631                    let scale = scale_val.unwrap_or(Expression::number(0));
2632                    let is_half_to_even = match &rounding_mode {
2633                        Some(Expression::Literal(Literal::String(s))) => s == "HALF_TO_EVEN",
2634                        _ => false,
2635                    };
2636                    if is_half_to_even {
2637                        Ok(Expression::Function(Box::new(Function::new("ROUND_EVEN".to_string(), vec![expr, scale]))))
2638                    } else {
2639                        Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![expr, scale]))))
2640                    }
2641                } else {
2642                    Ok(Expression::Function(Box::new(f)))
2643                }
2644            }
2645
2646            // ROUND(x, scale, 'HALF_TO_EVEN') -> ROUND_EVEN(x, scale)
2647            // ROUND(x, scale, 'HALF_AWAY_FROM_ZERO') -> ROUND(x, scale)
2648            "ROUND" if f.args.len() == 3 => {
2649                let mut args = f.args;
2650                let x = args.remove(0);
2651                let scale = args.remove(0);
2652                let mode = args.remove(0);
2653                let is_half_to_even = match &mode {
2654                    Expression::Literal(Literal::String(s)) => s == "HALF_TO_EVEN",
2655                    _ => false,
2656                };
2657                if is_half_to_even {
2658                    Ok(Expression::Function(Box::new(Function::new("ROUND_EVEN".to_string(), vec![x, scale]))))
2659                } else {
2660                    // HALF_AWAY_FROM_ZERO is default in DuckDB, just drop the mode
2661                    Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![x, scale]))))
2662                }
2663            }
2664
2665            // ROUND(x, scale) where scale is non-integer -> ROUND(x, CAST(scale AS INT))
2666            "ROUND" if f.args.len() == 2 => {
2667                let mut args = f.args;
2668                let x = args.remove(0);
2669                let scale = args.remove(0);
2670                let needs_cast = match &scale {
2671                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
2672                    Expression::Cast(_) => {
2673                        // Already has a CAST - wrap in another CAST to INT
2674                        true
2675                    }
2676                    _ => false,
2677                };
2678                if needs_cast {
2679                    Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![x, Expression::Cast(Box::new(Cast { this: scale, to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))]))))
2680                } else {
2681                    Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![x, scale]))))
2682                }
2683            }
2684
2685            // FLOOR(x, scale) -> ROUND(FLOOR(x * POWER(10, scale)) / POWER(10, scale), scale)
2686            "FLOOR" if f.args.len() == 2 => {
2687                let mut args = f.args;
2688                let x = args.remove(0);
2689                let scale = args.remove(0);
2690                // Check if scale needs CAST to INT
2691                let needs_cast = match &scale {
2692                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
2693                    _ => false,
2694                };
2695                let int_scale = if needs_cast {
2696                    Expression::Cast(Box::new(Cast { this: scale.clone(), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))
2697                } else {
2698                    scale.clone()
2699                };
2700                let power_10 = Expression::Function(Box::new(Function::new("POWER".to_string(), vec![Expression::number(10), int_scale.clone()])));
2701                let x_paren = match &x { Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => Expression::Paren(Box::new(Paren { this: x, trailing_comments: Vec::new() })), _ => x };
2702                let multiplied = Expression::Mul(Box::new(BinaryOp { left: x_paren, right: power_10.clone(), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2703                let floored = Expression::Function(Box::new(Function::new("FLOOR".to_string(), vec![multiplied])));
2704                let divided = Expression::Div(Box::new(BinaryOp { left: floored, right: power_10, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2705                Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![divided, int_scale]))))
2706            }
2707
2708            // CEIL(x, scale) -> ROUND(CEIL(x * POWER(10, scale)) / POWER(10, scale), scale)
2709            "CEIL" | "CEILING" if f.args.len() == 2 => {
2710                let mut args = f.args;
2711                let x = args.remove(0);
2712                let scale = args.remove(0);
2713                let needs_cast = match &scale {
2714                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
2715                    _ => false,
2716                };
2717                let int_scale = if needs_cast {
2718                    Expression::Cast(Box::new(Cast { this: scale.clone(), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))
2719                } else {
2720                    scale.clone()
2721                };
2722                let power_10 = Expression::Function(Box::new(Function::new("POWER".to_string(), vec![Expression::number(10), int_scale.clone()])));
2723                let x_paren = match &x { Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => Expression::Paren(Box::new(Paren { this: x, trailing_comments: Vec::new() })), _ => x };
2724                let multiplied = Expression::Mul(Box::new(BinaryOp { left: x_paren, right: power_10.clone(), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2725                let ceiled = Expression::Function(Box::new(Function::new("CEIL".to_string(), vec![multiplied])));
2726                let divided = Expression::Div(Box::new(BinaryOp { left: ceiled, right: power_10, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2727                Ok(Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![divided, int_scale]))))
2728            }
2729
2730            // ADD_MONTHS(date, n) -> CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + INTERVAL n MONTH) ELSE date + INTERVAL n MONTH END
2731            "ADD_MONTHS" if f.args.len() == 2 => {
2732                let mut args = f.args;
2733                let date_expr_raw = args.remove(0);
2734                let months_expr = args.remove(0);
2735
2736                // Track whether the raw expression was a string literal
2737                let was_string_literal = matches!(&date_expr_raw, Expression::Literal(Literal::String(_)));
2738
2739                // Wrap string literals in CAST(... AS TIMESTAMP) for DuckDB
2740                let date_expr = match &date_expr_raw {
2741                    Expression::Literal(Literal::String(_)) => {
2742                        Expression::Cast(Box::new(Cast {
2743                            this: date_expr_raw,
2744                            to: DataType::Timestamp { precision: None, timezone: false },
2745                            trailing_comments: Vec::new(),
2746                            double_colon_syntax: false,
2747                            format: None,
2748                            default: None,
2749                        }))
2750                    }
2751                    _ => date_expr_raw,
2752                };
2753
2754                // Determine the type of the date expression for outer CAST
2755                // But NOT if the CAST was added by us (for string literal wrapping)
2756                let date_type = if was_string_literal {
2757                    None
2758                } else {
2759                    match &date_expr {
2760                        Expression::Cast(c) => Some(c.to.clone()),
2761                        _ => None,
2762                    }
2763                };
2764
2765                // Determine interval expression - for non-integer months, use TO_MONTHS(CAST(ROUND(n) AS INT))
2766                let is_non_integer_months = match &months_expr {
2767                    Expression::Literal(Literal::Number(n)) => n.contains('.'),
2768                    Expression::Neg(_) => {
2769                        if let Expression::Neg(um) = &months_expr {
2770                            matches!(&um.this, Expression::Literal(Literal::Number(n)) if n.contains('.'))
2771                        } else { false }
2772                    }
2773                    // Cast to DECIMAL type means non-integer months
2774                    Expression::Cast(c) => matches!(&c.to, DataType::Decimal { .. }),
2775                    _ => false,
2776                };
2777
2778                let is_negative = match &months_expr {
2779                    Expression::Neg(_) => true,
2780                    Expression::Literal(Literal::Number(n)) => n.starts_with('-'),
2781                    _ => false,
2782                };
2783                let is_null = matches!(&months_expr, Expression::Null(_));
2784
2785                let interval_expr = if is_non_integer_months {
2786                    // For non-integer: TO_MONTHS(CAST(ROUND(n) AS INT))
2787                    Expression::Function(Box::new(Function::new("TO_MONTHS".to_string(), vec![
2788                        Expression::Cast(Box::new(Cast {
2789                            this: Expression::Function(Box::new(Function::new("ROUND".to_string(), vec![months_expr.clone()]))),
2790                            to: DataType::Int { length: None, integer_spelling: false },
2791                            trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2792                        }))
2793                    ])))
2794                } else if is_negative || is_null {
2795                    // For negative or NULL: INTERVAL (n) MONTH
2796                    Expression::Interval(Box::new(Interval {
2797                        this: Some(Expression::Paren(Box::new(Paren { this: months_expr.clone(), trailing_comments: Vec::new() }))),
2798                        unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Month, use_plural: false }),
2799                    }))
2800                } else {
2801                    // For positive integer: INTERVAL n MONTH
2802                    Expression::Interval(Box::new(Interval {
2803                        this: Some(months_expr.clone()),
2804                        unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Month, use_plural: false }),
2805                    }))
2806                };
2807
2808                let date_plus_interval = Expression::Add(Box::new(BinaryOp {
2809                    left: date_expr.clone(),
2810                    right: interval_expr.clone(),
2811                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2812                }));
2813
2814                let case_expr = Expression::Case(Box::new(Case {
2815                    operand: None,
2816                    whens: vec![(
2817                        Expression::Eq(Box::new(BinaryOp {
2818                            left: Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![date_expr.clone()]))),
2819                            right: date_expr.clone(),
2820                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2821                        })),
2822                        Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![date_plus_interval.clone()]))),
2823                    )],
2824                    else_: Some(date_plus_interval),
2825                }));
2826
2827                // Wrap in CAST if date had explicit type
2828                if let Some(dt) = date_type {
2829                    Ok(Expression::Cast(Box::new(Cast {
2830                        this: case_expr,
2831                        to: dt,
2832                        trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2833                    })))
2834                } else {
2835                    Ok(case_expr)
2836                }
2837            }
2838
2839            // TIME_SLICE(date, n, 'UNIT') -> TIME_BUCKET(INTERVAL n UNIT, date)
2840            // TIME_SLICE(date, n, 'UNIT', 'END') -> TIME_BUCKET(INTERVAL n UNIT, date) + INTERVAL n UNIT
2841            "TIME_SLICE" if f.args.len() >= 3 => {
2842                let mut args = f.args;
2843                let date_expr = args.remove(0);
2844                let n = args.remove(0);
2845                let unit_str = args.remove(0);
2846                let alignment = if !args.is_empty() { Some(args.remove(0)) } else { None };
2847
2848                // Extract unit string
2849                let unit = match &unit_str {
2850                    Expression::Literal(Literal::String(s)) => s.to_uppercase(),
2851                    Expression::Column(c) => c.name.name.to_uppercase(),
2852                    Expression::Identifier(i) => i.name.to_uppercase(),
2853                    _ => "DAY".to_string(),
2854                };
2855
2856                let interval_unit = match unit.as_str() {
2857                    "YEAR" => IntervalUnit::Year,
2858                    "QUARTER" => IntervalUnit::Quarter,
2859                    "MONTH" => IntervalUnit::Month,
2860                    "WEEK" => IntervalUnit::Week,
2861                    "DAY" => IntervalUnit::Day,
2862                    "HOUR" => IntervalUnit::Hour,
2863                    "MINUTE" => IntervalUnit::Minute,
2864                    "SECOND" => IntervalUnit::Second,
2865                    _ => IntervalUnit::Day,
2866                };
2867
2868                let interval = Expression::Interval(Box::new(Interval {
2869                    this: Some(n.clone()),
2870                    unit: Some(IntervalUnitSpec::Simple { unit: interval_unit.clone(), use_plural: false }),
2871                }));
2872
2873                let time_bucket = Expression::Function(Box::new(Function::new("TIME_BUCKET".to_string(), vec![interval.clone(), date_expr.clone()])));
2874
2875                let is_end = match &alignment {
2876                    Some(Expression::Literal(Literal::String(s))) => s.to_uppercase() == "END",
2877                    _ => false,
2878                };
2879
2880                // Determine if date is a DATE type (needs CAST)
2881                let is_date_type = match &date_expr {
2882                    Expression::Cast(c) => matches!(&c.to, DataType::Date),
2883                    _ => false,
2884                };
2885
2886                if is_end {
2887                    let bucket_plus = Expression::Add(Box::new(BinaryOp {
2888                        left: time_bucket,
2889                        right: Expression::Interval(Box::new(Interval {
2890                            this: Some(n),
2891                            unit: Some(IntervalUnitSpec::Simple { unit: interval_unit, use_plural: false }),
2892                        })),
2893                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2894                    }));
2895                    if is_date_type {
2896                        Ok(Expression::Cast(Box::new(Cast {
2897                            this: bucket_plus,
2898                            to: DataType::Date,
2899                            trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2900                        })))
2901                    } else {
2902                        Ok(bucket_plus)
2903                    }
2904                } else {
2905                    Ok(time_bucket)
2906                }
2907            }
2908
2909            // DATE_FROM_PARTS(year, month, day) -> CAST(MAKE_DATE(year, 1, 1) + INTERVAL (month - 1) MONTH + INTERVAL (day - 1) DAY AS DATE)
2910            "DATE_FROM_PARTS" | "DATEFROMPARTS" if f.args.len() == 3 => {
2911                let mut args = f.args;
2912                let year = args.remove(0);
2913                let month = args.remove(0);
2914                let day = args.remove(0);
2915
2916                let make_date = Expression::Function(Box::new(Function::new("MAKE_DATE".to_string(), vec![year, Expression::number(1), Expression::number(1)])));
2917
2918                // Wrap compound expressions in parens to get ((expr) - 1) instead of (expr - 1)
2919                let month_wrapped = match &month {
2920                    Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => {
2921                        Expression::Paren(Box::new(Paren { this: month, trailing_comments: Vec::new() }))
2922                    }
2923                    _ => month,
2924                };
2925                let day_wrapped = match &day {
2926                    Expression::Add(_) | Expression::Sub(_) | Expression::Mul(_) | Expression::Div(_) => {
2927                        Expression::Paren(Box::new(Paren { this: day, trailing_comments: Vec::new() }))
2928                    }
2929                    _ => day,
2930                };
2931                let month_minus_1 = Expression::Sub(Box::new(BinaryOp { left: month_wrapped, right: Expression::number(1), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2932                let month_interval = Expression::Interval(Box::new(Interval {
2933                    this: Some(Expression::Paren(Box::new(Paren { this: month_minus_1, trailing_comments: Vec::new() }))),
2934                    unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Month, use_plural: false }),
2935                }));
2936
2937                let day_minus_1 = Expression::Sub(Box::new(BinaryOp { left: day_wrapped, right: Expression::number(1), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
2938                let day_interval = Expression::Interval(Box::new(Interval {
2939                    this: Some(Expression::Paren(Box::new(Paren { this: day_minus_1, trailing_comments: Vec::new() }))),
2940                    unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Day, use_plural: false }),
2941                }));
2942
2943                let result = Expression::Add(Box::new(BinaryOp {
2944                    left: Expression::Add(Box::new(BinaryOp {
2945                        left: make_date,
2946                        right: month_interval,
2947                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2948                    })),
2949                    right: day_interval,
2950                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
2951                }));
2952
2953                Ok(Expression::Cast(Box::new(Cast {
2954                    this: result,
2955                    to: DataType::Date,
2956                    trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
2957                })))
2958            }
2959
2960            // NEXT_DAY(date, 'day_name') -> complex expression using ISODOW
2961            "NEXT_DAY" if f.args.len() == 2 => {
2962                let mut args = f.args;
2963                let date = args.remove(0);
2964                let day_name = args.remove(0);
2965
2966                // Parse day name to ISO day number (1=Monday..7=Sunday)
2967                let day_num = match &day_name {
2968                    Expression::Literal(Literal::String(s)) => {
2969                        let upper = s.to_uppercase();
2970                        if upper.starts_with("MO") { Some(1) }
2971                        else if upper.starts_with("TU") { Some(2) }
2972                        else if upper.starts_with("WE") { Some(3) }
2973                        else if upper.starts_with("TH") { Some(4) }
2974                        else if upper.starts_with("FR") { Some(5) }
2975                        else if upper.starts_with("SA") { Some(6) }
2976                        else if upper.starts_with("SU") { Some(7) }
2977                        else { None }
2978                    }
2979                    _ => None,
2980                };
2981
2982                let target_day_expr = if let Some(n) = day_num {
2983                    Expression::number(n)
2984                } else {
2985                    // Dynamic day name: CASE WHEN STARTS_WITH(UPPER(day_column), 'MO') THEN 1 ... END
2986                    Expression::Case(Box::new(Case {
2987                        operand: None,
2988                        whens: vec![
2989                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("MO".to_string()))]))), Expression::number(1)),
2990                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("TU".to_string()))]))), Expression::number(2)),
2991                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("WE".to_string()))]))), Expression::number(3)),
2992                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("TH".to_string()))]))), Expression::number(4)),
2993                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("FR".to_string()))]))), Expression::number(5)),
2994                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("SA".to_string()))]))), Expression::number(6)),
2995                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name))), Expression::Literal(Literal::String("SU".to_string()))]))), Expression::number(7)),
2996                        ],
2997                        else_: None,
2998                    }))
2999                };
3000
3001                let isodow = Expression::Function(Box::new(Function::new("ISODOW".to_string(), vec![date.clone()])));
3002                // ((target_day - ISODOW(date) + 6) % 7) + 1
3003                let diff = Expression::Add(Box::new(BinaryOp {
3004                    left: Expression::Paren(Box::new(Paren {
3005                        this: Expression::Mod(Box::new(BinaryOp {
3006                            left: Expression::Paren(Box::new(Paren {
3007                                this: Expression::Add(Box::new(BinaryOp {
3008                                    left: Expression::Paren(Box::new(Paren {
3009                                        this: Expression::Sub(Box::new(BinaryOp { left: target_day_expr, right: isodow, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
3010                                        trailing_comments: Vec::new(),
3011                                    })),
3012                                    right: Expression::number(6),
3013                                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3014                                })),
3015                                trailing_comments: Vec::new(),
3016                            })),
3017                            right: Expression::number(7),
3018                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3019                        })),
3020                        trailing_comments: Vec::new(),
3021                    })),
3022                    right: Expression::number(1),
3023                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3024                }));
3025
3026                let result = Expression::Add(Box::new(BinaryOp {
3027                    left: date,
3028                    right: Expression::Interval(Box::new(Interval {
3029                        this: Some(Expression::Paren(Box::new(Paren { this: diff, trailing_comments: Vec::new() }))),
3030                        unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Day, use_plural: false }),
3031                    })),
3032                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3033                }));
3034
3035                Ok(Expression::Cast(Box::new(Cast {
3036                    this: result,
3037                    to: DataType::Date,
3038                    trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
3039                })))
3040            }
3041
3042            // PREVIOUS_DAY(date, 'day_name') -> complex expression using ISODOW
3043            "PREVIOUS_DAY" if f.args.len() == 2 => {
3044                let mut args = f.args;
3045                let date = args.remove(0);
3046                let day_name = args.remove(0);
3047
3048                let day_num = match &day_name {
3049                    Expression::Literal(Literal::String(s)) => {
3050                        let upper = s.to_uppercase();
3051                        if upper.starts_with("MO") { Some(1) }
3052                        else if upper.starts_with("TU") { Some(2) }
3053                        else if upper.starts_with("WE") { Some(3) }
3054                        else if upper.starts_with("TH") { Some(4) }
3055                        else if upper.starts_with("FR") { Some(5) }
3056                        else if upper.starts_with("SA") { Some(6) }
3057                        else if upper.starts_with("SU") { Some(7) }
3058                        else { None }
3059                    }
3060                    _ => None,
3061                };
3062
3063                let target_day_expr = if let Some(n) = day_num {
3064                    Expression::number(n)
3065                } else {
3066                    Expression::Case(Box::new(Case {
3067                        operand: None,
3068                        whens: vec![
3069                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("MO".to_string()))]))), Expression::number(1)),
3070                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("TU".to_string()))]))), Expression::number(2)),
3071                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("WE".to_string()))]))), Expression::number(3)),
3072                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("TH".to_string()))]))), Expression::number(4)),
3073                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("FR".to_string()))]))), Expression::number(5)),
3074                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name.clone()))), Expression::Literal(Literal::String("SA".to_string()))]))), Expression::number(6)),
3075                            (Expression::Function(Box::new(Function::new("STARTS_WITH".to_string(), vec![Expression::Upper(Box::new(UnaryFunc::new(day_name))), Expression::Literal(Literal::String("SU".to_string()))]))), Expression::number(7)),
3076                        ],
3077                        else_: None,
3078                    }))
3079                };
3080
3081                let isodow = Expression::Function(Box::new(Function::new("ISODOW".to_string(), vec![date.clone()])));
3082                // ((ISODOW(date) - target_day + 6) % 7) + 1
3083                let diff = Expression::Add(Box::new(BinaryOp {
3084                    left: Expression::Paren(Box::new(Paren {
3085                        this: Expression::Mod(Box::new(BinaryOp {
3086                            left: Expression::Paren(Box::new(Paren {
3087                                this: Expression::Add(Box::new(BinaryOp {
3088                                    left: Expression::Paren(Box::new(Paren {
3089                                        this: Expression::Sub(Box::new(BinaryOp { left: isodow, right: target_day_expr, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
3090                                        trailing_comments: Vec::new(),
3091                                    })),
3092                                    right: Expression::number(6),
3093                                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3094                                })),
3095                                trailing_comments: Vec::new(),
3096                            })),
3097                            right: Expression::number(7),
3098                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3099                        })),
3100                        trailing_comments: Vec::new(),
3101                    })),
3102                    right: Expression::number(1),
3103                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3104                }));
3105
3106                let result = Expression::Sub(Box::new(BinaryOp {
3107                    left: date,
3108                    right: Expression::Interval(Box::new(Interval {
3109                        this: Some(Expression::Paren(Box::new(Paren { this: diff, trailing_comments: Vec::new() }))),
3110                        unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Day, use_plural: false }),
3111                    })),
3112                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3113                }));
3114
3115                Ok(Expression::Cast(Box::new(Cast {
3116                    this: result,
3117                    to: DataType::Date,
3118                    trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
3119                })))
3120            }
3121
3122            // LAST_DAY(date, YEAR) -> MAKE_DATE(EXTRACT(YEAR FROM date), 12, 31)
3123            // LAST_DAY(date, QUARTER) -> LAST_DAY(MAKE_DATE(EXTRACT(YEAR FROM date), EXTRACT(QUARTER FROM date) * 3, 1))
3124            // LAST_DAY(date, WEEK) -> CAST(date + INTERVAL ((7 - EXTRACT(DAYOFWEEK FROM date)) % 7) DAY AS DATE)
3125            "LAST_DAY" if f.args.len() == 2 => {
3126                let mut args = f.args;
3127                let date = args.remove(0);
3128                let unit = args.remove(0);
3129                let unit_str = match &unit {
3130                    Expression::Column(c) => c.name.name.to_uppercase(),
3131                    Expression::Identifier(i) => i.name.to_uppercase(),
3132                    _ => String::new(),
3133                };
3134
3135                match unit_str.as_str() {
3136                    "MONTH" => Ok(Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![date])))),
3137                    "YEAR" => {
3138                        Ok(Expression::Function(Box::new(Function::new("MAKE_DATE".to_string(), vec![
3139                            Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date, field: crate::expressions::DateTimeField::Year })),
3140                            Expression::number(12),
3141                            Expression::number(31),
3142                        ]))))
3143                    }
3144                    "QUARTER" => {
3145                        let year = Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date.clone(), field: crate::expressions::DateTimeField::Year }));
3146                        let quarter_month = Expression::Mul(Box::new(BinaryOp {
3147                            left: Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date, field: crate::expressions::DateTimeField::Custom("QUARTER".to_string()) })),
3148                            right: Expression::number(3),
3149                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3150                        }));
3151                        let make_date = Expression::Function(Box::new(Function::new("MAKE_DATE".to_string(), vec![year, quarter_month, Expression::number(1)])));
3152                        Ok(Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![make_date]))))
3153                    }
3154                    "WEEK" => {
3155                        let dow = Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date.clone(), field: crate::expressions::DateTimeField::Custom("DAYOFWEEK".to_string()) }));
3156                        let diff = Expression::Mod(Box::new(BinaryOp {
3157                            left: Expression::Paren(Box::new(Paren {
3158                                this: Expression::Sub(Box::new(BinaryOp { left: Expression::number(7), right: dow, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
3159                                trailing_comments: Vec::new(),
3160                            })),
3161                            right: Expression::number(7),
3162                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3163                        }));
3164                        let result = Expression::Add(Box::new(BinaryOp {
3165                            left: date,
3166                            right: Expression::Interval(Box::new(Interval {
3167                                this: Some(Expression::Paren(Box::new(Paren { this: diff, trailing_comments: Vec::new() }))),
3168                                unit: Some(IntervalUnitSpec::Simple { unit: IntervalUnit::Day, use_plural: false }),
3169                            })),
3170                            left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3171                        }));
3172                        Ok(Expression::Cast(Box::new(Cast { this: result, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })))
3173                    }
3174                    _ => Ok(Expression::Function(Box::new(Function::new("LAST_DAY".to_string(), vec![date, unit])))),
3175                }
3176            }
3177
3178            // SEQ1/SEQ2/SEQ4/SEQ8 -> (ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1) % range
3179            "SEQ1" | "SEQ2" | "SEQ4" | "SEQ8" => {
3180                let (range, half): (u128, u128) = match name_upper.as_str() {
3181                    "SEQ1" => (256, 128),
3182                    "SEQ2" => (65536, 32768),
3183                    "SEQ4" => (4294967296, 2147483648),
3184                    "SEQ8" => (18446744073709551616, 9223372036854775808),
3185                    _ => unreachable!("sequence type already matched in caller"),
3186                };
3187
3188                let is_signed = match f.args.first() {
3189                    Some(Expression::Literal(Literal::Number(n))) => n == "1",
3190                    _ => false,
3191                };
3192
3193                let row_num = Expression::Sub(Box::new(BinaryOp {
3194                    left: Expression::WindowFunction(Box::new(crate::expressions::WindowFunction {
3195                        this: Expression::Function(Box::new(Function::new("ROW_NUMBER".to_string(), vec![]))),
3196                        over: crate::expressions::Over {
3197                            window_name: None,
3198                            partition_by: vec![],
3199                            order_by: vec![crate::expressions::Ordered {
3200                                this: Expression::number(1),
3201                                desc: false,
3202                                nulls_first: Some(true),
3203                                explicit_asc: false,
3204                                with_fill: None,
3205                            }],
3206                            frame: None,
3207                            alias: None,
3208                        },
3209                        keep: None,
3210                    })),
3211                    right: Expression::number(1),
3212                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3213                }));
3214
3215                let modded = Expression::Mod(Box::new(BinaryOp {
3216                    left: Expression::Paren(Box::new(Paren { this: row_num, trailing_comments: Vec::new() })),
3217                    right: Expression::Literal(Literal::Number(range.to_string())),
3218                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3219                }));
3220
3221                if is_signed {
3222                    // CASE WHEN val >= half THEN val - range ELSE val END
3223                    let cond = Expression::Gte(Box::new(BinaryOp {
3224                        left: modded.clone(),
3225                        right: Expression::Literal(Literal::Number(half.to_string())),
3226                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3227                    }));
3228                    let signed_val = Expression::Sub(Box::new(BinaryOp {
3229                        left: modded.clone(),
3230                        right: Expression::Literal(Literal::Number(range.to_string())),
3231                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3232                    }));
3233                    Ok(Expression::Paren(Box::new(Paren {
3234                        this: Expression::Case(Box::new(Case {
3235                            operand: None,
3236                            whens: vec![(cond, signed_val)],
3237                            else_: Some(modded),
3238                        })),
3239                        trailing_comments: Vec::new(),
3240                    })))
3241                } else {
3242                    Ok(modded)
3243                }
3244            }
3245
3246            // TABLE(fn) -> fn (unwrap TABLE wrapper for DuckDB)
3247            // Also handles TABLE(GENERATOR(ROWCOUNT => n)) -> RANGE(n) directly
3248            "TABLE" if f.args.len() == 1 => {
3249                let inner = f.args.into_iter().next().unwrap();
3250                // If inner is GENERATOR, transform it to RANGE
3251                if let Expression::Function(ref gen_f) = inner {
3252                    if gen_f.name.to_uppercase() == "GENERATOR" {
3253                        let mut rowcount = None;
3254                        for arg in &gen_f.args {
3255                            if let Expression::NamedArgument(na) = arg {
3256                                if na.name.name.to_uppercase() == "ROWCOUNT" {
3257                                    rowcount = Some(na.value.clone());
3258                                }
3259                            }
3260                        }
3261                        if let Some(n) = rowcount {
3262                            return Ok(Expression::Function(Box::new(Function::new("RANGE".to_string(), vec![n]))));
3263                        }
3264                    }
3265                }
3266                Ok(inner)
3267            }
3268
3269            // GENERATOR(ROWCOUNT => n) -> RANGE(n) in DuckDB
3270            "GENERATOR" => {
3271                let mut rowcount = None;
3272                for arg in &f.args {
3273                    if let Expression::NamedArgument(na) = arg {
3274                        if na.name.name.to_uppercase() == "ROWCOUNT" {
3275                            rowcount = Some(na.value.clone());
3276                        }
3277                    }
3278                }
3279                if let Some(n) = rowcount {
3280                    Ok(Expression::Function(Box::new(Function::new("RANGE".to_string(), vec![n]))))
3281                } else {
3282                    Ok(Expression::Function(Box::new(f)))
3283                }
3284            }
3285
3286            // UNIFORM(low, high, gen) -> CAST(FLOOR(low + RANDOM() * (high - low + 1)) AS BIGINT)
3287            // or with seed: CAST(FLOOR(low + (ABS(HASH(seed)) % 1000000) / 1000000.0 * (high - low + 1)) AS BIGINT)
3288            "UNIFORM" if f.args.len() == 3 => {
3289                let mut args = f.args;
3290                let low = args.remove(0);
3291                let high = args.remove(0);
3292                let gen = args.remove(0);
3293
3294                let range = Expression::Add(Box::new(BinaryOp {
3295                    left: Expression::Sub(Box::new(BinaryOp { left: high, right: low.clone(), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
3296                    right: Expression::number(1),
3297                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3298                }));
3299
3300                // Check if gen is RANDOM() (function) or a literal seed
3301                let random_val = match &gen {
3302                    Expression::Rand(_) | Expression::Random(_) => {
3303                        // RANDOM() - use directly
3304                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])))
3305                    }
3306                    Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
3307                        // RANDOM(seed) or RANDOM() - just use RANDOM()
3308                        Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])))
3309                    }
3310                    _ => {
3311                        // Seed-based: (ABS(HASH(seed)) % 1000000) / 1000000.0
3312                        let hash = Expression::Function(Box::new(Function::new("HASH".to_string(), vec![gen])));
3313                        let abs_hash = Expression::Abs(Box::new(UnaryFunc::new(hash)));
3314                        let modded = Expression::Mod(Box::new(BinaryOp { left: abs_hash, right: Expression::number(1000000), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
3315                        let paren_modded = Expression::Paren(Box::new(Paren { this: modded, trailing_comments: Vec::new() }));
3316                        Expression::Div(Box::new(BinaryOp { left: paren_modded, right: Expression::Literal(Literal::Number("1000000.0".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))
3317                    }
3318                };
3319
3320                let inner = Expression::Function(Box::new(Function::new("FLOOR".to_string(), vec![
3321                    Expression::Add(Box::new(BinaryOp {
3322                        left: low,
3323                        right: Expression::Mul(Box::new(BinaryOp { left: random_val, right: Expression::Paren(Box::new(Paren { this: range, trailing_comments: Vec::new() })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })),
3324                        left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3325                    }))
3326                ])));
3327
3328                Ok(Expression::Cast(Box::new(Cast {
3329                    this: inner,
3330                    to: DataType::BigInt { length: None },
3331                    trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None,
3332                })))
3333            }
3334
3335            // NORMAL(mean, stddev, gen) -> Box-Muller transform
3336            // mean + (stddev * SQRT(-2 * LN(GREATEST(u1, 1e-10))) * COS(2 * PI() * u2))
3337            // where u1 and u2 are uniform random values derived from gen
3338            "NORMAL" if f.args.len() == 3 => {
3339                let mut args = f.args;
3340                let mean = args.remove(0);
3341                let stddev = args.remove(0);
3342                let gen = args.remove(0);
3343
3344                // Helper to create seed-based random: (ABS(HASH(seed)) % 1000000) / 1000000.0
3345                let make_seed_random = |seed: Expression| -> Expression {
3346                    let hash = Expression::Function(Box::new(Function::new("HASH".to_string(), vec![seed])));
3347                    let abs_hash = Expression::Abs(Box::new(UnaryFunc::new(hash)));
3348                    let modded = Expression::Mod(Box::new(BinaryOp { left: abs_hash, right: Expression::number(1000000), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
3349                    let paren_modded = Expression::Paren(Box::new(Paren { this: modded, trailing_comments: Vec::new() }));
3350                    Expression::Div(Box::new(BinaryOp { left: paren_modded, right: Expression::Literal(Literal::Number("1000000.0".to_string())), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))
3351                };
3352
3353                // Determine u1 and u2 based on gen type
3354                let is_random_no_seed = match &gen {
3355                    Expression::Random(_) => true,
3356                    Expression::Rand(r) => r.seed.is_none(),
3357                    _ => false,
3358                };
3359                let (u1, u2) = if is_random_no_seed {
3360                    // RANDOM() -> u1 = RANDOM(), u2 = RANDOM()
3361                    let u1 = Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])));
3362                    let u2 = Expression::Function(Box::new(Function::new("RANDOM".to_string(), vec![])));
3363                    (u1, u2)
3364                } else {
3365                    // Seed-based: extract the seed value
3366                    let seed = match gen {
3367                        Expression::Rand(r) => r.seed.map(|s| *s).unwrap_or(Expression::number(0)),
3368                        Expression::Function(func) if func.name.to_uppercase() == "RANDOM" => {
3369                            if func.args.len() == 1 {
3370                                func.args.into_iter().next().unwrap()
3371                            } else {
3372                                Expression::number(0)
3373                            }
3374                        }
3375                        other => other,
3376                    };
3377                    let u1 = make_seed_random(seed.clone());
3378                    let seed_plus_1 = Expression::Add(Box::new(BinaryOp { left: seed, right: Expression::number(1), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }));
3379                    let u2 = make_seed_random(seed_plus_1);
3380                    (u1, u2)
3381                };
3382
3383                // GREATEST(u1, 1e-10)
3384                let greatest = Expression::Greatest(Box::new(VarArgFunc {
3385                    expressions: vec![u1, Expression::Literal(Literal::Number("1e-10".to_string()))],
3386                    original_name: None,
3387                }));
3388
3389                // SQRT(-2 * LN(GREATEST(u1, 1e-10)))
3390                let neg2 = Expression::Neg(Box::new(crate::expressions::UnaryOp { this: Expression::number(2) }));
3391                let ln_greatest = Expression::Function(Box::new(Function::new("LN".to_string(), vec![greatest])));
3392                let neg2_times_ln = Expression::Mul(Box::new(BinaryOp {
3393                    left: neg2,
3394                    right: ln_greatest,
3395                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3396                }));
3397                let sqrt_part = Expression::Function(Box::new(Function::new("SQRT".to_string(), vec![neg2_times_ln])));
3398
3399                // COS(2 * PI() * u2)
3400                let pi = Expression::Function(Box::new(Function::new("PI".to_string(), vec![])));
3401                let two_pi = Expression::Mul(Box::new(BinaryOp {
3402                    left: Expression::number(2),
3403                    right: pi,
3404                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3405                }));
3406                let two_pi_u2 = Expression::Mul(Box::new(BinaryOp {
3407                    left: two_pi,
3408                    right: u2,
3409                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3410                }));
3411                let cos_part = Expression::Function(Box::new(Function::new("COS".to_string(), vec![two_pi_u2])));
3412
3413                // stddev * sqrt_part * cos_part
3414                let stddev_times_sqrt = Expression::Mul(Box::new(BinaryOp {
3415                    left: stddev,
3416                    right: sqrt_part,
3417                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3418                }));
3419                let inner = Expression::Mul(Box::new(BinaryOp {
3420                    left: stddev_times_sqrt,
3421                    right: cos_part,
3422                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3423                }));
3424                let paren_inner = Expression::Paren(Box::new(Paren { this: inner, trailing_comments: Vec::new() }));
3425
3426                // mean + (inner)
3427                Ok(Expression::Add(Box::new(BinaryOp {
3428                    left: mean,
3429                    right: paren_inner,
3430                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3431                })))
3432            }
3433
3434            // DATE_TRUNC: DuckDB supports natively, just pass through
3435            // (DuckDB returns the correct type automatically)
3436
3437            // BITOR/BITAND with BITSHIFT need parenthesization
3438            // This is handled via the BITOR/BITAND transforms which create BitwiseOr/BitwiseAnd
3439            // The issue is operator precedence: BITOR(BITSHIFTLEFT(a, b), BITSHIFTLEFT(c, d))
3440            // should generate (a << b) | (c << d), not a << b | c << d
3441
3442            // ZIPF(s, n, gen) -> CTE-based emulation for DuckDB
3443            "ZIPF" if f.args.len() == 3 => {
3444                let mut args = f.args;
3445                let s_expr = args.remove(0);
3446                let n_expr = args.remove(0);
3447                let gen_expr = args.remove(0);
3448
3449                let s_sql = Self::expr_to_sql(&s_expr);
3450                let n_sql = Self::expr_to_sql(&n_expr);
3451                let (seed_sql, is_random) = Self::extract_seed_info(&gen_expr);
3452
3453                let rand_sql = if is_random {
3454                    format!("SELECT {} AS r", seed_sql)
3455                } else {
3456                    format!("SELECT (ABS(HASH({})) % 1000000) / 1000000.0 AS r", seed_sql)
3457                };
3458
3459                let template = format!(
3460                    "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)",
3461                    rand_sql, s_sql, n_sql
3462                );
3463
3464                Self::parse_as_subquery(&template)
3465            }
3466
3467            // RANDSTR(len, gen) -> subquery-based emulation for DuckDB
3468            "RANDSTR" if f.args.len() == 2 => {
3469                let mut args = f.args;
3470                let len_expr = args.remove(0);
3471                let gen_expr = args.remove(0);
3472
3473                let len_sql = Self::expr_to_sql(&len_expr);
3474                let (seed_sql, is_random) = Self::extract_seed_info(&gen_expr);
3475
3476                let random_value_sql = if is_random {
3477                    format!("(ABS(HASH(i + {})) % 1000) / 1000.0", seed_sql)
3478                } else {
3479                    format!("(ABS(HASH(i + {})) % 1000) / 1000.0", seed_sql)
3480                };
3481
3482                let template = format!(
3483                    "SELECT LISTAGG(SUBSTRING('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 1 + CAST(FLOOR(random_value * 62) AS INT), 1), '') FROM (SELECT {} AS random_value FROM RANGE({}) AS t(i))",
3484                    random_value_sql, len_sql
3485                );
3486
3487                Self::parse_as_subquery(&template)
3488            }
3489
3490            // Pass through everything else
3491            _ => Ok(Expression::Function(Box::new(f))),
3492        }
3493    }
3494
3495    /// Convert Snowflake date format to DuckDB strptime format
3496    fn convert_snowflake_date_format(&self, fmt: Expression) -> Expression {
3497        match fmt {
3498            Expression::Literal(Literal::String(s)) => {
3499                let converted = Self::snowflake_to_strptime(&s);
3500                Expression::Literal(Literal::String(converted))
3501            }
3502            _ => fmt,
3503        }
3504    }
3505
3506    /// Convert Snowflake time format to DuckDB strptime format
3507    fn convert_snowflake_time_format(&self, fmt: Expression) -> Expression {
3508        match fmt {
3509            Expression::Literal(Literal::String(s)) => {
3510                let converted = Self::snowflake_to_strptime(&s);
3511                Expression::Literal(Literal::String(converted))
3512            }
3513            _ => fmt,
3514        }
3515    }
3516
3517    /// Token-based conversion from Snowflake format strings (both original and normalized) to DuckDB strptime format.
3518    /// Handles both uppercase Snowflake originals (YYYY, MM, DD) and normalized lowercase forms (yyyy, mm, DD).
3519    fn snowflake_to_strptime(s: &str) -> String {
3520        let mut result = String::new();
3521        let chars: Vec<char> = s.chars().collect();
3522        let len = chars.len();
3523        let mut i = 0;
3524        while i < len {
3525            let remaining = &s[i..];
3526            let remaining_upper: String = remaining.chars().take(8).collect::<String>().to_uppercase();
3527
3528            // Compound patterns first
3529            if remaining_upper.starts_with("HH24MISS") {
3530                result.push_str("%H%M%S");
3531                i += 8;
3532            } else if remaining_upper.starts_with("MMMM") {
3533                result.push_str("%B");
3534                i += 4;
3535            } else if remaining_upper.starts_with("YYYY") {
3536                result.push_str("%Y");
3537                i += 4;
3538            } else if remaining_upper.starts_with("YY") {
3539                result.push_str("%y");
3540                i += 2;
3541            } else if remaining_upper.starts_with("MON") {
3542                result.push_str("%b");
3543                i += 3;
3544            } else if remaining_upper.starts_with("HH24") {
3545                result.push_str("%H");
3546                i += 4;
3547            } else if remaining_upper.starts_with("HH12") {
3548                result.push_str("%I");
3549                i += 4;
3550            } else if remaining_upper.starts_with("HH") {
3551                result.push_str("%I");
3552                i += 2;
3553            } else if remaining_upper.starts_with("MISS") {
3554                result.push_str("%M%S");
3555                i += 4;
3556            } else if remaining_upper.starts_with("MI") {
3557                result.push_str("%M");
3558                i += 2;
3559            } else if remaining_upper.starts_with("MM") {
3560                result.push_str("%m");
3561                i += 2;
3562            } else if remaining_upper.starts_with("DD") {
3563                result.push_str("%d");
3564                i += 2;
3565            } else if remaining_upper.starts_with("DY") {
3566                result.push_str("%a");
3567                i += 2;
3568            } else if remaining_upper.starts_with("SS") {
3569                result.push_str("%S");
3570                i += 2;
3571            } else if remaining_upper.starts_with("FF") {
3572                // FF with optional digit (FF, FF1-FF9)
3573                // %f = microseconds (6 digits, FF1-FF6), %n = nanoseconds (9 digits, FF7-FF9)
3574                let ff_pos = i + 2;
3575                if ff_pos < len && chars[ff_pos].is_ascii_digit() {
3576                    let digit = chars[ff_pos].to_digit(10).unwrap_or(6);
3577                    if digit >= 7 {
3578                        result.push_str("%n");
3579                    } else {
3580                        result.push_str("%f");
3581                    }
3582                    i += 3; // skip FF + digit
3583                } else {
3584                    result.push_str("%f");
3585                    i += 2;
3586                }
3587            } else if remaining_upper.starts_with("PM") || remaining_upper.starts_with("AM") {
3588                result.push_str("%p");
3589                i += 2;
3590            } else if remaining_upper.starts_with("TZH") {
3591                result.push_str("%z");
3592                i += 3;
3593            } else if remaining_upper.starts_with("TZM") {
3594                // TZM is part of timezone, skip
3595                i += 3;
3596            } else {
3597                result.push(chars[i]);
3598                i += 1;
3599            }
3600        }
3601        result
3602    }
3603
3604    /// Convert BigQuery format string to DuckDB strptime format
3605    /// BigQuery: %E6S -> DuckDB: %S.%f (seconds with microseconds)
3606    fn convert_bq_to_strptime_format(&self, fmt: Expression) -> Expression {
3607        match fmt {
3608            Expression::Literal(Literal::String(s)) => {
3609                let converted = s
3610                    .replace("%E6S", "%S.%f")
3611                    .replace("%E*S", "%S.%f");
3612                Expression::Literal(Literal::String(converted))
3613            }
3614            _ => fmt,
3615        }
3616    }
3617
3618    /// Transform DATE_PART(unit, expr) for DuckDB
3619    fn transform_date_part(&self, args: Vec<Expression>) -> Result<Expression> {
3620        let mut args = args;
3621        let unit_expr = args.remove(0);
3622        let date_expr = args.remove(0);
3623        let unit_name = match &unit_expr {
3624            Expression::Column(c) => c.name.name.to_uppercase(),
3625            Expression::Identifier(i) => i.name.to_uppercase(),
3626            Expression::Literal(Literal::String(s)) => s.to_uppercase(),
3627            _ => return Ok(Expression::Function(Box::new(Function::new("DATE_PART".to_string(), vec![unit_expr, date_expr])))),
3628        };
3629        match unit_name.as_str() {
3630            "EPOCH_SECOND" | "EPOCH" => Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("EPOCH".to_string(), vec![date_expr]))), to: DataType::BigInt { length: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
3631            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => Ok(Expression::Function(Box::new(Function::new("EPOCH_MS".to_string(), vec![date_expr])))),
3632            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => Ok(Expression::Function(Box::new(Function::new("EPOCH_US".to_string(), vec![date_expr])))),
3633            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => Ok(Expression::Function(Box::new(Function::new("EPOCH_NS".to_string(), vec![date_expr])))),
3634            "DAYOFWEEKISO" | "DAYOFWEEK_ISO" => Ok(Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date_expr, field: crate::expressions::DateTimeField::Custom("ISODOW".to_string()) }))),
3635            "YEAROFWEEK" | "YEAROFWEEKISO" => Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRFTIME".to_string(), vec![date_expr, Expression::Literal(Literal::String("%G".to_string()))]))), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
3636            "WEEKISO" => Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRFTIME".to_string(), vec![date_expr, Expression::Literal(Literal::String("%V".to_string()))]))), to: DataType::Int { length: None, integer_spelling: false }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
3637            "NANOSECOND" | "NANOSECONDS" | "NS" => Ok(Expression::Cast(Box::new(Cast { this: Expression::Function(Box::new(Function::new("STRFTIME".to_string(), vec![Expression::Cast(Box::new(Cast { this: date_expr, to: DataType::Custom { name: "TIMESTAMP_NS".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })), Expression::Literal(Literal::String("%n".to_string()))]))), to: DataType::BigInt { length: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))),
3638            "DAYOFMONTH" => Ok(Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date_expr, field: crate::expressions::DateTimeField::Day }))),
3639            _ => {
3640                let field = match unit_name.as_str() {
3641                    "YEAR" | "YY" | "YYYY" => crate::expressions::DateTimeField::Year,
3642                    "MONTH" | "MON" | "MM" => crate::expressions::DateTimeField::Month,
3643                    "DAY" | "DD" | "D" => crate::expressions::DateTimeField::Day,
3644                    "HOUR" | "HH" => crate::expressions::DateTimeField::Hour,
3645                    "MINUTE" | "MI" | "MIN" => crate::expressions::DateTimeField::Minute,
3646                    "SECOND" | "SEC" | "SS" => crate::expressions::DateTimeField::Second,
3647                    "MILLISECOND" | "MS" => crate::expressions::DateTimeField::Millisecond,
3648                    "MICROSECOND" | "US" => crate::expressions::DateTimeField::Microsecond,
3649                    "QUARTER" | "QTR" => crate::expressions::DateTimeField::Quarter,
3650                    "WEEK" | "WK" => crate::expressions::DateTimeField::Week,
3651                    "DAYOFWEEK" | "DOW" => crate::expressions::DateTimeField::DayOfWeek,
3652                    "DAYOFYEAR" | "DOY" => crate::expressions::DateTimeField::DayOfYear,
3653                    "TIMEZONE_HOUR" => crate::expressions::DateTimeField::TimezoneHour,
3654                    "TIMEZONE_MINUTE" => crate::expressions::DateTimeField::TimezoneMinute,
3655                    _ => crate::expressions::DateTimeField::Custom(unit_name),
3656                };
3657                Ok(Expression::Extract(Box::new(crate::expressions::ExtractFunc { this: date_expr, field })))
3658            }
3659        }
3660    }
3661
3662    /// Transform DATEADD(unit, amount, date) for DuckDB
3663    fn transform_dateadd(&self, args: Vec<Expression>) -> Result<Expression> {
3664        let mut args = args;
3665        let unit_expr = args.remove(0);
3666        let amount = args.remove(0);
3667        let date = args.remove(0);
3668        let unit_name = match &unit_expr {
3669            Expression::Column(c) => c.name.name.to_uppercase(),
3670            Expression::Identifier(i) => i.name.to_uppercase(),
3671            Expression::Literal(Literal::String(s)) => s.to_uppercase(),
3672            _ => String::new(),
3673        };
3674        if unit_name == "NANOSECOND" || unit_name == "NS" {
3675            let epoch_ns = Expression::Function(Box::new(Function::new("EPOCH_NS".to_string(), vec![Expression::Cast(Box::new(Cast { this: date, to: DataType::Custom { name: "TIMESTAMP_NS".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))])));
3676            return Ok(Expression::Function(Box::new(Function::new("MAKE_TIMESTAMP_NS".to_string(), vec![Expression::Add(Box::new(BinaryOp { left: epoch_ns, right: amount, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))]))));
3677        }
3678        let (interval_unit, multiplied_amount) = match unit_name.as_str() {
3679            "YEAR" | "YY" | "YYYY" => (IntervalUnit::Year, amount),
3680            "MONTH" | "MON" | "MM" => (IntervalUnit::Month, amount),
3681            "DAY" | "DD" | "D" => (IntervalUnit::Day, amount),
3682            "HOUR" | "HH" => (IntervalUnit::Hour, amount),
3683            "MINUTE" | "MI" | "MIN" => (IntervalUnit::Minute, amount),
3684            "SECOND" | "SEC" | "SS" => (IntervalUnit::Second, amount),
3685            "MILLISECOND" | "MS" => (IntervalUnit::Millisecond, amount),
3686            "MICROSECOND" | "US" => (IntervalUnit::Microsecond, amount),
3687            "WEEK" | "WK" => (IntervalUnit::Day, Expression::Mul(Box::new(BinaryOp { left: amount, right: Expression::number(7), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))),
3688            "QUARTER" | "QTR" => (IntervalUnit::Month, Expression::Mul(Box::new(BinaryOp { left: amount, right: Expression::number(3), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() }))),
3689            _ => (IntervalUnit::Day, amount),
3690        };
3691        Ok(Expression::Add(Box::new(BinaryOp { left: date, right: Expression::Interval(Box::new(Interval { this: Some(multiplied_amount), unit: Some(IntervalUnitSpec::Simple { unit: interval_unit, use_plural: false }) })), left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })))
3692    }
3693
3694    /// Transform DATEDIFF(unit, start, end) for DuckDB
3695    fn transform_datediff(&self, args: Vec<Expression>) -> Result<Expression> {
3696        let mut args = args;
3697        let unit_expr = args.remove(0);
3698        let start = args.remove(0);
3699        let end = args.remove(0);
3700        let unit_name = match &unit_expr {
3701            Expression::Column(c) => c.name.name.to_uppercase(),
3702            Expression::Identifier(i) => i.name.to_uppercase(),
3703            Expression::Literal(Literal::String(s)) => s.to_uppercase(),
3704            _ => String::new(),
3705        };
3706        if unit_name == "NANOSECOND" || unit_name == "NS" {
3707            let epoch_end = Expression::Function(Box::new(Function::new("EPOCH_NS".to_string(), vec![Expression::Cast(Box::new(Cast { this: end, to: DataType::Custom { name: "TIMESTAMP_NS".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))])));
3708            let epoch_start = Expression::Function(Box::new(Function::new("EPOCH_NS".to_string(), vec![Expression::Cast(Box::new(Cast { this: start, to: DataType::Custom { name: "TIMESTAMP_NS".to_string() }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))])));
3709            return Ok(Expression::Sub(Box::new(BinaryOp { left: epoch_end, right: epoch_start, left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new() })));
3710        }
3711        if unit_name == "WEEK" || unit_name == "WK" {
3712            let trunc_start = Expression::Function(Box::new(Function::new("DATE_TRUNC".to_string(), vec![Expression::Literal(Literal::String("WEEK".to_string())), Expression::Cast(Box::new(Cast { this: start, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))])));
3713            let trunc_end = Expression::Function(Box::new(Function::new("DATE_TRUNC".to_string(), vec![Expression::Literal(Literal::String("WEEK".to_string())), Expression::Cast(Box::new(Cast { this: end, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None }))])));
3714            return Ok(Expression::Function(Box::new(Function::new("DATE_DIFF".to_string(), vec![Expression::Literal(Literal::String("WEEK".to_string())), trunc_start, trunc_end]))));
3715        }
3716        let cast_if_string = |e: Expression| -> Expression { match &e { Expression::Literal(Literal::String(_)) => Expression::Cast(Box::new(Cast { this: e, to: DataType::Date, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })), _ => e } };
3717        let start = cast_if_string(start);
3718        let end = cast_if_string(end);
3719        Ok(Expression::Function(Box::new(Function::new("DATE_DIFF".to_string(), vec![Expression::Literal(Literal::String(unit_name)), start, end]))))
3720    }
3721
3722    fn transform_aggregate_function(
3723        &self,
3724        f: Box<crate::expressions::AggregateFunction>,
3725    ) -> Result<Expression> {
3726        let name_upper = f.name.to_uppercase();
3727        match name_upper.as_str() {
3728            // GROUP_CONCAT -> LISTAGG
3729            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3730                Function::new("LISTAGG".to_string(), f.args),
3731            ))),
3732
3733            // LISTAGG is native to DuckDB
3734            "LISTAGG" => Ok(Expression::AggregateFunction(f)),
3735
3736            // STRING_AGG -> LISTAGG
3737            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3738                "LISTAGG".to_string(),
3739                f.args,
3740            )))),
3741
3742            // ARRAY_AGG -> list (or array_agg, both work)
3743            "ARRAY_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3744                "list".to_string(),
3745                f.args,
3746            )))),
3747
3748            // LOGICAL_OR -> BOOL_OR with CAST to BOOLEAN
3749            "LOGICAL_OR" if !f.args.is_empty() => {
3750                let arg = f.args.into_iter().next().unwrap();
3751                Ok(Expression::Function(Box::new(Function::new(
3752                    "BOOL_OR".to_string(),
3753                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
3754                        this: arg,
3755                        to: crate::expressions::DataType::Boolean,
3756                        trailing_comments: Vec::new(),
3757                        double_colon_syntax: false,
3758                        format: None,
3759                        default: None,
3760                    }))],
3761                ))))
3762            }
3763
3764            // LOGICAL_AND -> BOOL_AND with CAST to BOOLEAN
3765            "LOGICAL_AND" if !f.args.is_empty() => {
3766                let arg = f.args.into_iter().next().unwrap();
3767                Ok(Expression::Function(Box::new(Function::new(
3768                    "BOOL_AND".to_string(),
3769                    vec![Expression::Cast(Box::new(crate::expressions::Cast {
3770                        this: arg,
3771                        to: crate::expressions::DataType::Boolean,
3772                        trailing_comments: Vec::new(),
3773                        double_colon_syntax: false,
3774                        format: None,
3775                        default: None,
3776                    }))],
3777                ))))
3778            }
3779
3780            // SKEW -> SKEWNESS
3781            "SKEW" => Ok(Expression::Function(Box::new(Function::new(
3782                "SKEWNESS".to_string(),
3783                f.args,
3784            )))),
3785
3786            // REGR_VALX(y, x) -> CASE WHEN y IS NULL THEN CAST(NULL AS DOUBLE) ELSE x END
3787            "REGR_VALX" if f.args.len() == 2 => {
3788                let mut args = f.args;
3789                let y = args.remove(0);
3790                let x = args.remove(0);
3791                Ok(Expression::Case(Box::new(Case {
3792                    operand: None,
3793                    whens: vec![(
3794                        Expression::IsNull(Box::new(crate::expressions::IsNull { this: y, not: false, postfix_form: false })),
3795                        Expression::Cast(Box::new(Cast { this: Expression::Null(crate::expressions::Null), to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })),
3796                    )],
3797                    else_: Some(x),
3798                })))
3799            }
3800
3801            // REGR_VALY(y, x) -> CASE WHEN x IS NULL THEN CAST(NULL AS DOUBLE) ELSE y END
3802            "REGR_VALY" if f.args.len() == 2 => {
3803                let mut args = f.args;
3804                let y = args.remove(0);
3805                let x = args.remove(0);
3806                Ok(Expression::Case(Box::new(Case {
3807                    operand: None,
3808                    whens: vec![(
3809                        Expression::IsNull(Box::new(crate::expressions::IsNull { this: x, not: false, postfix_form: false })),
3810                        Expression::Cast(Box::new(Cast { this: Expression::Null(crate::expressions::Null), to: DataType::Double { precision: None, scale: None }, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })),
3811                    )],
3812                    else_: Some(y),
3813                })))
3814            }
3815
3816            // BOOLAND_AGG -> BOOL_AND(CAST(arg AS BOOLEAN))
3817            "BOOLAND_AGG" if !f.args.is_empty() => {
3818                let arg = f.args.into_iter().next().unwrap();
3819                Ok(Expression::Function(Box::new(Function::new(
3820                    "BOOL_AND".to_string(),
3821                    vec![Expression::Cast(Box::new(Cast {
3822                        this: arg,
3823                        to: DataType::Boolean,
3824                        trailing_comments: Vec::new(),
3825                        double_colon_syntax: false,
3826                        format: None,
3827                        default: None,
3828                    }))],
3829                ))))
3830            }
3831
3832            // BOOLOR_AGG -> BOOL_OR(CAST(arg AS BOOLEAN))
3833            "BOOLOR_AGG" if !f.args.is_empty() => {
3834                let arg = f.args.into_iter().next().unwrap();
3835                Ok(Expression::Function(Box::new(Function::new(
3836                    "BOOL_OR".to_string(),
3837                    vec![Expression::Cast(Box::new(Cast {
3838                        this: arg,
3839                        to: DataType::Boolean,
3840                        trailing_comments: Vec::new(),
3841                        double_colon_syntax: false,
3842                        format: None,
3843                        default: None,
3844                    }))],
3845                ))))
3846            }
3847
3848            // BOOLXOR_AGG(c) -> COUNT_IF(CAST(c AS BOOLEAN)) = 1
3849            "BOOLXOR_AGG" if !f.args.is_empty() => {
3850                let arg = f.args.into_iter().next().unwrap();
3851                Ok(Expression::Eq(Box::new(BinaryOp {
3852                    left: Expression::Function(Box::new(Function::new("COUNT_IF".to_string(), vec![
3853                        Expression::Cast(Box::new(Cast { this: arg, to: DataType::Boolean, trailing_comments: Vec::new(), double_colon_syntax: false, format: None, default: None })),
3854                    ]))),
3855                    right: Expression::number(1),
3856                    left_comments: Vec::new(), operator_comments: Vec::new(), trailing_comments: Vec::new(),
3857                })))
3858            }
3859
3860            // MAX_BY -> ARG_MAX
3861            "MAX_BY" if f.args.len() == 2 => {
3862                Ok(Expression::AggregateFunction(Box::new(crate::expressions::AggregateFunction {
3863                    name: "ARG_MAX".to_string(),
3864                    ..(*f)
3865                })))
3866            }
3867
3868            // MIN_BY -> ARG_MIN
3869            "MIN_BY" if f.args.len() == 2 => {
3870                Ok(Expression::AggregateFunction(Box::new(crate::expressions::AggregateFunction {
3871                    name: "ARG_MIN".to_string(),
3872                    ..(*f)
3873                })))
3874            }
3875
3876            // CORR - pass through (DuckDB handles NaN natively)
3877            "CORR" if f.args.len() == 2 => {
3878                Ok(Expression::AggregateFunction(f))
3879            }
3880
3881            // BITMAP_CONSTRUCT_AGG(v) -> complex DuckDB subquery emulation
3882            "BITMAP_CONSTRUCT_AGG" if f.args.len() == 1 => {
3883                let v_sql = Self::expr_to_sql(&f.args[0]);
3884
3885                let template = format!(
3886                    "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))",
3887                    v = v_sql
3888                );
3889
3890                Self::parse_as_subquery(&template)
3891            }
3892
3893            // Pass through everything else
3894            _ => Ok(Expression::AggregateFunction(f)),
3895        }
3896    }
3897
3898    /// Convert Presto/MySQL format string to DuckDB format string
3899    /// DuckDB uses strftime/strptime C-style format specifiers
3900    /// Key difference: %i (Presto minutes) -> %M (DuckDB minutes)
3901    fn convert_format_to_duckdb(expr: &Expression) -> Expression {
3902        if let Expression::Literal(Literal::String(s)) = expr {
3903            let duckdb_fmt = Self::presto_to_duckdb_format(s);
3904            Expression::Literal(Literal::String(duckdb_fmt))
3905        } else {
3906            expr.clone()
3907        }
3908    }
3909
3910    /// Convert Presto format specifiers to DuckDB strftime format
3911    fn presto_to_duckdb_format(fmt: &str) -> String {
3912        let mut result = String::new();
3913        let chars: Vec<char> = fmt.chars().collect();
3914        let mut i = 0;
3915        while i < chars.len() {
3916            if chars[i] == '%' && i + 1 < chars.len() {
3917                match chars[i + 1] {
3918                    'i' => {
3919                        // Presto %i (minutes) -> DuckDB %M (minutes)
3920                        result.push_str("%M");
3921                        i += 2;
3922                    }
3923                    'T' => {
3924                        // Presto %T (time shorthand %H:%M:%S)
3925                        result.push_str("%H:%M:%S");
3926                        i += 2;
3927                    }
3928                    'F' => {
3929                        // Presto %F (date shorthand %Y-%m-%d)
3930                        result.push_str("%Y-%m-%d");
3931                        i += 2;
3932                    }
3933                    _ => {
3934                        result.push('%');
3935                        result.push(chars[i + 1]);
3936                        i += 2;
3937                    }
3938                }
3939            } else {
3940                result.push(chars[i]);
3941                i += 1;
3942            }
3943        }
3944        result
3945    }
3946}
3947
3948#[cfg(test)]
3949mod tests {
3950    use super::*;
3951    use crate::dialects::Dialect;
3952
3953    fn transpile_to_duckdb(sql: &str) -> String {
3954        let dialect = Dialect::get(DialectType::Generic);
3955        let result = dialect
3956            .transpile_to(sql, DialectType::DuckDB)
3957            .expect("Transpile failed");
3958        result[0].clone()
3959    }
3960
3961    #[test]
3962    fn test_ifnull_to_coalesce() {
3963        let result = transpile_to_duckdb("SELECT IFNULL(a, b)");
3964        assert!(
3965            result.contains("COALESCE"),
3966            "Expected COALESCE, got: {}",
3967            result
3968        );
3969    }
3970
3971    #[test]
3972    fn test_nvl_to_coalesce() {
3973        let result = transpile_to_duckdb("SELECT NVL(a, b)");
3974        assert!(
3975            result.contains("COALESCE"),
3976            "Expected COALESCE, got: {}",
3977            result
3978        );
3979    }
3980
3981    #[test]
3982    fn test_basic_select() {
3983        let result = transpile_to_duckdb("SELECT a, b FROM users WHERE id = 1");
3984        assert!(result.contains("SELECT"));
3985        assert!(result.contains("FROM users"));
3986    }
3987
3988    #[test]
3989    fn test_group_concat_to_listagg() {
3990        let result = transpile_to_duckdb("SELECT GROUP_CONCAT(name)");
3991        assert!(
3992            result.contains("LISTAGG"),
3993            "Expected LISTAGG, got: {}",
3994            result
3995        );
3996    }
3997
3998    #[test]
3999    fn test_listagg_preserved() {
4000        let result = transpile_to_duckdb("SELECT LISTAGG(name)");
4001        assert!(
4002            result.contains("LISTAGG"),
4003            "Expected LISTAGG, got: {}",
4004            result
4005        );
4006    }
4007
4008    #[test]
4009    fn test_date_format_to_strftime() {
4010        let result = transpile_to_duckdb("SELECT DATE_FORMAT(d, '%Y-%m-%d')");
4011        // Generator uppercases function names
4012        assert!(
4013            result.to_uppercase().contains("STRFTIME"),
4014            "Expected STRFTIME, got: {}",
4015            result
4016        );
4017    }
4018
4019    #[test]
4020    fn test_regexp_like_to_regexp_matches() {
4021        let result = transpile_to_duckdb("SELECT REGEXP_LIKE(name, 'pattern')");
4022        // Generator uppercases function names
4023        assert!(
4024            result.to_uppercase().contains("REGEXP_MATCHES"),
4025            "Expected REGEXP_MATCHES, got: {}",
4026            result
4027        );
4028    }
4029
4030    #[test]
4031    fn test_double_quote_identifiers() {
4032        // DuckDB uses double quotes for identifiers
4033        let dialect = Dialect::get(DialectType::DuckDB);
4034        let config = dialect.generator_config();
4035        assert_eq!(config.identifier_quote, '"');
4036    }
4037
4038    /// Helper for DuckDB identity tests (parse with DuckDB, generate with DuckDB)
4039    fn duckdb_identity(sql: &str) -> String {
4040        let dialect = Dialect::get(DialectType::DuckDB);
4041        let ast = dialect.parse(sql).expect("Parse failed");
4042        let transformed = dialect.transform(ast[0].clone()).expect("Transform failed");
4043        dialect.generate(&transformed).expect("Generate failed")
4044    }
4045
4046    #[test]
4047    fn test_interval_quoting() {
4048        // Test 137: INTERVAL value should be quoted for DuckDB
4049        let result = duckdb_identity(
4050            "SELECT DATE_ADD(CAST('2020-01-01' AS DATE), INTERVAL 1 DAY)",
4051        );
4052        assert_eq!(
4053            result,
4054            "SELECT CAST('2020-01-01' AS DATE) + INTERVAL '1' DAY",
4055            "Interval value should be quoted as string"
4056        );
4057    }
4058
4059    #[test]
4060    fn test_struct_pack_to_curly_brace() {
4061        // Test 221: STRUCT_PACK should become curly brace notation
4062        let result = duckdb_identity(
4063            "CAST([STRUCT_PACK(a := 1)] AS STRUCT(a BIGINT)[])",
4064        );
4065        assert_eq!(
4066            result,
4067            "CAST([{'a': 1}] AS STRUCT(a BIGINT)[])",
4068            "STRUCT_PACK should be transformed to curly brace notation"
4069        );
4070    }
4071
4072    #[test]
4073    fn test_struct_pack_nested() {
4074        // Test 220: Nested STRUCT_PACK
4075        let result = duckdb_identity(
4076            "CAST([[STRUCT_PACK(a := 1)]] AS STRUCT(a BIGINT)[][])",
4077        );
4078        assert_eq!(
4079            result,
4080            "CAST([[{'a': 1}]] AS STRUCT(a BIGINT)[][])",
4081            "Nested STRUCT_PACK should be transformed"
4082        );
4083    }
4084
4085    #[test]
4086    fn test_struct_pack_cast() {
4087        // Test 222: STRUCT_PACK with :: cast
4088        let result = duckdb_identity(
4089            "STRUCT_PACK(a := 'b')::json",
4090        );
4091        assert_eq!(
4092            result,
4093            "CAST({'a': 'b'} AS JSON)",
4094            "STRUCT_PACK with cast should be transformed"
4095        );
4096    }
4097
4098    #[test]
4099    fn test_list_value_to_bracket() {
4100        // Test 309: LIST_VALUE should become bracket notation
4101        let result = duckdb_identity(
4102            "SELECT LIST_VALUE(1)[i]",
4103        );
4104        assert_eq!(
4105            result,
4106            "SELECT [1][i]",
4107            "LIST_VALUE should be transformed to bracket notation"
4108        );
4109    }
4110
4111    #[test]
4112    fn test_list_value_in_struct_literal() {
4113        // Test 310: LIST_VALUE inside struct literal
4114        let result = duckdb_identity(
4115            "{'x': LIST_VALUE(1)[i]}",
4116        );
4117        assert_eq!(
4118            result,
4119            "{'x': [1][i]}",
4120            "LIST_VALUE inside struct literal should be transformed"
4121        );
4122    }
4123
4124    #[test]
4125    fn test_struct_pack_simple() {
4126        // Simple STRUCT_PACK without nesting
4127        let result = duckdb_identity("SELECT STRUCT_PACK(a := 1)");
4128        eprintln!("STRUCT_PACK result: {}", result);
4129        assert!(result.contains("{"), "Expected curly brace, got: {}", result);
4130    }
4131
4132    #[test]
4133    fn test_not_in_position() {
4134        // Test 78: NOT IN should become NOT (...) IN (...)
4135        // DuckDB prefers `NOT (expr) IN (list)` over `expr NOT IN (list)`
4136        let result = duckdb_identity(
4137            "SELECT col FROM t WHERE JSON_EXTRACT_STRING(col, '$.id') NOT IN ('b')",
4138        );
4139        assert_eq!(
4140            result,
4141            "SELECT col FROM t WHERE NOT (col ->> '$.id') IN ('b')",
4142            "NOT IN should have NOT moved outside and JSON expression wrapped"
4143        );
4144    }
4145
4146    #[test]
4147    fn test_unnest_comma_join_to_join_on_true() {
4148        // Test 310: Comma-join with UNNEST should become JOIN ... ON TRUE
4149        let result = duckdb_identity(
4150            "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",
4151        );
4152        assert_eq!(
4153            result,
4154            "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",
4155            "Comma-join with UNNEST should become JOIN ON TRUE"
4156        );
4157    }
4158}