Skip to main content

polyglot_sql/dialects/
snowflake.rs

1//! Snowflake Dialect
2//!
3//! Snowflake-specific transformations based on sqlglot patterns.
4//! Key differences:
5//! - TRY_ prefix for safe operations (TRY_CAST, TRY_TO_NUMBER)
6//! - FLATTEN for unnesting arrays
7//! - QUALIFY clause support
8//! - ARRAY_CONSTRUCT, OBJECT_CONSTRUCT for arrays/objects
9//! - Variant type handling
10//! - Default case-insensitive identifiers (unquoted)
11
12use super::{DialectImpl, DialectType};
13use crate::error::Result;
14use crate::expressions::{
15    AggFunc, BinaryOp, Cast, CeilFunc, DataType, Expression, Function, IntervalUnit, ListAggFunc,
16    Literal, UnaryFunc, VarArgFunc,
17};
18use crate::generator::GeneratorConfig;
19use crate::tokens::TokenizerConfig;
20
21/// Convert IntervalUnit to string for Snowflake syntax
22fn interval_unit_to_str(unit: &IntervalUnit) -> String {
23    match unit {
24        IntervalUnit::Year => "YEAR".to_string(),
25        IntervalUnit::Quarter => "QUARTER".to_string(),
26        IntervalUnit::Month => "MONTH".to_string(),
27        IntervalUnit::Week => "WEEK".to_string(),
28        IntervalUnit::Day => "DAY".to_string(),
29        IntervalUnit::Hour => "HOUR".to_string(),
30        IntervalUnit::Minute => "MINUTE".to_string(),
31        IntervalUnit::Second => "SECOND".to_string(),
32        IntervalUnit::Millisecond => "MILLISECOND".to_string(),
33        IntervalUnit::Microsecond => "MICROSECOND".to_string(),
34        IntervalUnit::Nanosecond => "NANOSECOND".to_string(),
35    }
36}
37
38/// Snowflake dialect
39pub struct SnowflakeDialect;
40
41impl DialectImpl for SnowflakeDialect {
42    fn dialect_type(&self) -> DialectType {
43        DialectType::Snowflake
44    }
45
46    fn tokenizer_config(&self) -> TokenizerConfig {
47        let mut config = TokenizerConfig::default();
48        // Snowflake uses double quotes for identifiers
49        config.identifiers.insert('"', '"');
50        // Snowflake supports $$ string literals
51        config.quotes.insert("$$".to_string(), "$$".to_string());
52        // Snowflake does NOT support nested comments (per Python sqlglot)
53        config.nested_comments = false;
54        // Snowflake supports // as single-line comments (in addition to --)
55        config.comments.insert("//".to_string(), None);
56        config
57    }
58
59    fn generator_config(&self) -> GeneratorConfig {
60        use crate::generator::IdentifierQuoteStyle;
61        GeneratorConfig {
62            identifier_quote: '"',
63            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
64            dialect: Some(DialectType::Snowflake),
65            // Snowflake-specific settings from Python sqlglot
66            parameter_token: "$",
67            matched_by_source: false,
68            single_string_interval: true,
69            join_hints: false,
70            table_hints: false,
71            query_hints: false,
72            aggregate_filter_supported: false,
73            supports_table_copy: false,
74            collate_is_func: true,
75            limit_only_literals: true,
76            json_key_value_pair_sep: ",",
77            insert_overwrite: " OVERWRITE INTO",
78            struct_delimiter: ("(", ")"),
79            copy_params_are_wrapped: false,
80            copy_params_eq_required: true,
81            star_except: "EXCLUDE",
82            supports_exploding_projections: false,
83            array_concat_is_var_len: false,
84            supports_convert_timezone: true,
85            except_intersect_support_all_clause: false,
86            supports_median: true,
87            array_size_name: "ARRAY_SIZE",
88            supports_decode_case: true,
89            is_bool_allowed: false,
90            // Snowflake supports TRY_ prefix operations
91            try_supported: true,
92            // Snowflake supports NVL2
93            nvl2_supported: true,
94            // Snowflake uses FLATTEN for unnest
95            unnest_with_ordinality: false,
96            // Snowflake uses space before paren: ALL (subquery)
97            quantified_no_paren_space: false,
98            // Snowflake uses bracket-only array syntax: [1, 2, 3]
99            array_bracket_only: true,
100            ..Default::default()
101        }
102    }
103
104    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
105        match expr {
106            // ===== Data Type Mappings =====
107            Expression::DataType(dt) => self.transform_data_type(dt),
108
109            // ===== NOT IN transformation =====
110            // Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`
111            // See: https://docs.snowflake.com/en/sql-reference/functions/in
112            Expression::In(in_expr) if in_expr.not && in_expr.query.is_some() => {
113                // Transform NOT IN (subquery) -> <> ALL (subquery)
114                let inner = in_expr.query.unwrap();
115                // Wrap in Subquery so generator outputs ALL (subquery) with space
116                let subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
117                    this: inner,
118                    alias: None,
119                    column_aliases: Vec::new(),
120                    order_by: None,
121                    limit: None,
122                    offset: None,
123                    distribute_by: None,
124                    sort_by: None,
125                    cluster_by: None,
126                    lateral: false,
127                    modifiers_inside: false,
128                    trailing_comments: Vec::new(),
129                    inferred_type: None,
130                }));
131                Ok(Expression::All(Box::new(
132                    crate::expressions::QuantifiedExpr {
133                        this: in_expr.this,
134                        subquery,
135                        op: Some(crate::expressions::QuantifiedOp::Neq),
136                    },
137                )))
138            }
139
140            // NOT IN (values) -> NOT x IN (values)
141            Expression::In(in_expr) if in_expr.not => {
142                // Transform NOT x IN (values) by wrapping the In expression with not=false inside a Not
143                let in_without_not = crate::expressions::In {
144                    this: in_expr.this,
145                    expressions: in_expr.expressions,
146                    query: in_expr.query,
147                    not: false,
148                    global: in_expr.global,
149                    unnest: in_expr.unnest,
150                    is_field: in_expr.is_field,
151                };
152                Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
153                    this: Expression::In(Box::new(in_without_not)),
154                    inferred_type: None,
155                })))
156            }
157
158            // ===== Interval unit expansion =====
159            // Expand abbreviated units in interval string values (e.g., '1 w' -> '1 WEEK')
160            Expression::Interval(interval) => self.transform_interval(*interval),
161
162            // ===== Null handling =====
163            // IFNULL -> COALESCE (both work in Snowflake, but COALESCE is standard)
164            Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
165                original_name: None,
166                expressions: vec![f.this, f.expression],
167                inferred_type: None,
168            }))),
169
170            // NVL -> COALESCE (both work in Snowflake, but COALESCE is standard)
171            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
172                original_name: None,
173                expressions: vec![f.this, f.expression],
174                inferred_type: None,
175            }))),
176
177            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
178            Expression::Coalesce(mut f) => {
179                f.original_name = None;
180                Ok(Expression::Coalesce(f))
181            }
182
183            // GROUP_CONCAT -> LISTAGG in Snowflake
184            Expression::GroupConcat(f) => Ok(Expression::ListAgg(Box::new(ListAggFunc {
185                this: f.this,
186                separator: f.separator,
187                on_overflow: None,
188                order_by: f.order_by,
189                distinct: f.distinct,
190                filter: f.filter,
191                inferred_type: None,
192            }))),
193
194            // ===== Cast operations =====
195            // CAST(x AS GEOGRAPHY) -> TO_GEOGRAPHY(x)
196            // CAST(x AS GEOMETRY) -> TO_GEOMETRY(x)
197            Expression::Cast(c) => {
198                use crate::expressions::DataType;
199                // First, recursively transform the inner expression
200                let transformed_this = self.transform_expr(c.this)?;
201                match &c.to {
202                    DataType::Geography { .. } => Ok(Expression::Function(Box::new(
203                        Function::new("TO_GEOGRAPHY".to_string(), vec![transformed_this]),
204                    ))),
205                    DataType::Geometry { .. } => Ok(Expression::Function(Box::new(Function::new(
206                        "TO_GEOMETRY".to_string(),
207                        vec![transformed_this],
208                    )))),
209                    _ => {
210                        // Transform the data type
211                        let transformed_dt = match self.transform_data_type(c.to.clone())? {
212                            Expression::DataType(dt) => dt,
213                            _ => c.to.clone(),
214                        };
215                        Ok(Expression::Cast(Box::new(Cast {
216                            this: transformed_this,
217                            to: transformed_dt,
218                            double_colon_syntax: false, // Normalize :: to CAST()
219                            trailing_comments: c.trailing_comments,
220                            format: c.format,
221                            default: c.default,
222                            inferred_type: None,
223                        })))
224                    }
225                }
226            }
227
228            // TryCast stays as TryCast (Snowflake supports TRY_CAST)
229            // Recursively transform the inner expression
230            Expression::TryCast(c) => {
231                let transformed_this = self.transform_expr(c.this)?;
232                Ok(Expression::TryCast(Box::new(Cast {
233                    this: transformed_this,
234                    to: c.to,
235                    double_colon_syntax: false, // Normalize :: to CAST()
236                    trailing_comments: c.trailing_comments,
237                    format: c.format,
238                    default: c.default,
239                    inferred_type: None,
240                })))
241            }
242
243            // SafeCast -> Cast in Snowflake (Snowflake CAST is safe by default)
244            // Also convert TIMESTAMP to TIMESTAMPTZ (BigQuery TIMESTAMP = tz-aware)
245            Expression::SafeCast(c) => {
246                let to = match c.to {
247                    DataType::Timestamp { .. } => DataType::Custom {
248                        name: "TIMESTAMPTZ".to_string(),
249                    },
250                    DataType::Custom { name } if name.eq_ignore_ascii_case("TIMESTAMP") => {
251                        DataType::Custom {
252                            name: "TIMESTAMPTZ".to_string(),
253                        }
254                    }
255                    other => other,
256                };
257                let transformed_this = self.transform_expr(c.this)?;
258                Ok(Expression::Cast(Box::new(Cast {
259                    this: transformed_this,
260                    to,
261                    double_colon_syntax: c.double_colon_syntax,
262                    trailing_comments: c.trailing_comments,
263                    format: c.format,
264                    default: c.default,
265                    inferred_type: None,
266                })))
267            }
268
269            // ===== Typed Literals -> CAST =====
270            // TIMESTAMP '...' -> CAST('...' AS TIMESTAMP)
271            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
272                let Literal::Timestamp(s) = lit.as_ref() else {
273                    unreachable!()
274                };
275                Ok(Expression::Cast(Box::new(Cast {
276                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
277                    to: DataType::Timestamp {
278                        precision: None,
279                        timezone: false,
280                    },
281                    double_colon_syntax: false,
282                    trailing_comments: Vec::new(),
283                    format: None,
284                    default: None,
285                    inferred_type: None,
286                })))
287            }
288
289            // DATE '...' -> CAST('...' AS DATE)
290            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
291                let Literal::Date(s) = lit.as_ref() else {
292                    unreachable!()
293                };
294                Ok(Expression::Cast(Box::new(Cast {
295                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
296                    to: DataType::Date,
297                    double_colon_syntax: false,
298                    trailing_comments: Vec::new(),
299                    format: None,
300                    default: None,
301                    inferred_type: None,
302                })))
303            }
304
305            // TIME '...' -> CAST('...' AS TIME)
306            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)) => {
307                let Literal::Time(s) = lit.as_ref() else {
308                    unreachable!()
309                };
310                Ok(Expression::Cast(Box::new(Cast {
311                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
312                    to: DataType::Time {
313                        precision: None,
314                        timezone: false,
315                    },
316                    double_colon_syntax: false,
317                    trailing_comments: Vec::new(),
318                    format: None,
319                    default: None,
320                    inferred_type: None,
321                })))
322            }
323
324            // DATETIME '...' -> CAST('...' AS DATETIME)
325            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
326                let Literal::Datetime(s) = lit.as_ref() else {
327                    unreachable!()
328                };
329                Ok(Expression::Cast(Box::new(Cast {
330                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
331                    to: DataType::Custom {
332                        name: "DATETIME".to_string(),
333                    },
334                    double_colon_syntax: false,
335                    trailing_comments: Vec::new(),
336                    format: None,
337                    default: None,
338                    inferred_type: None,
339                })))
340            }
341
342            // ===== Pattern matching =====
343            // ILIKE is native to Snowflake (no transformation needed)
344            Expression::ILike(op) => Ok(Expression::ILike(op)),
345
346            // ===== Array operations =====
347            // EXPLODE -> FLATTEN in Snowflake
348            Expression::Explode(f) => Ok(Expression::Function(Box::new(Function::new(
349                "FLATTEN".to_string(),
350                vec![f.this],
351            )))),
352
353            // ExplodeOuter -> FLATTEN with OUTER => TRUE
354            Expression::ExplodeOuter(f) => Ok(Expression::Function(Box::new(Function::new(
355                "FLATTEN".to_string(),
356                vec![f.this],
357            )))),
358
359            // UNNEST -> TABLE(FLATTEN(INPUT => x)) AS _t0(seq, key, path, index, value, this)
360            Expression::Unnest(f) => {
361                // Create INPUT => x named argument
362                let input_arg =
363                    Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
364                        name: crate::expressions::Identifier::new("INPUT"),
365                        value: f.this,
366                        separator: crate::expressions::NamedArgSeparator::DArrow,
367                    }));
368
369                // Create FLATTEN(INPUT => x)
370                let flatten = Expression::Function(Box::new(Function::new(
371                    "FLATTEN".to_string(),
372                    vec![input_arg],
373                )));
374
375                // Wrap in TABLE(...)
376                let table_func =
377                    Expression::TableFromRows(Box::new(crate::expressions::TableFromRows {
378                        this: Box::new(flatten),
379                        alias: None,
380                        joins: vec![],
381                        pivots: None,
382                        sample: None,
383                    }));
384
385                // Add alias _t0(seq, key, path, index, value, this)
386                Ok(Expression::Alias(Box::new(crate::expressions::Alias {
387                    this: table_func,
388                    alias: crate::expressions::Identifier::new("_t0"),
389                    column_aliases: vec![
390                        crate::expressions::Identifier::new("seq"),
391                        crate::expressions::Identifier::new("key"),
392                        crate::expressions::Identifier::new("path"),
393                        crate::expressions::Identifier::new("index"),
394                        crate::expressions::Identifier::new("value"),
395                        crate::expressions::Identifier::new("this"),
396                    ],
397                    pre_alias_comments: vec![],
398                    trailing_comments: vec![],
399                    inferred_type: None,
400                })))
401            }
402
403            // Array constructor:
404            // - If bracket notation ([1, 2, 3]), preserve it in Snowflake
405            // - If ARRAY[...] syntax, convert to ARRAY_CONSTRUCT
406            Expression::ArrayFunc(arr) => {
407                if arr.bracket_notation {
408                    // Keep bracket notation in Snowflake
409                    Ok(Expression::ArrayFunc(arr))
410                } else {
411                    // Convert ARRAY[...] to ARRAY_CONSTRUCT
412                    Ok(Expression::Function(Box::new(Function::new(
413                        "ARRAY_CONSTRUCT".to_string(),
414                        arr.expressions,
415                    ))))
416                }
417            }
418
419            // ArrayConcat -> ARRAY_CAT
420            Expression::ArrayConcat(f) => Ok(Expression::Function(Box::new(Function::new(
421                "ARRAY_CAT".to_string(),
422                f.expressions,
423            )))),
424
425            // ArrayConcatAgg -> ARRAY_FLATTEN
426            Expression::ArrayConcatAgg(f) => Ok(Expression::Function(Box::new(Function::new(
427                "ARRAY_FLATTEN".to_string(),
428                vec![f.this],
429            )))),
430
431            // ArrayContains -> ARRAY_CONTAINS
432            Expression::ArrayContains(f) => Ok(Expression::Function(Box::new(Function::new(
433                "ARRAY_CONTAINS".to_string(),
434                vec![f.this, f.expression],
435            )))),
436
437            // ArrayIntersect -> ARRAY_INTERSECTION
438            Expression::ArrayIntersect(f) => Ok(Expression::Function(Box::new(Function::new(
439                "ARRAY_INTERSECTION".to_string(),
440                f.expressions,
441            )))),
442
443            // SortArray -> ARRAY_SORT
444            Expression::ArraySort(f) => Ok(Expression::Function(Box::new(Function::new(
445                "ARRAY_SORT".to_string(),
446                vec![f.this],
447            )))),
448
449            // StringToArray -> STRTOK_TO_ARRAY
450            Expression::StringToArray(f) => {
451                let mut args = vec![*f.this];
452                if let Some(expr) = f.expression {
453                    args.push(*expr);
454                }
455                Ok(Expression::Function(Box::new(Function::new(
456                    "STRTOK_TO_ARRAY".to_string(),
457                    args,
458                ))))
459            }
460
461            // ===== Bitwise operations =====
462            // BitwiseOr -> BITOR
463            Expression::BitwiseOr(f) => Ok(Expression::Function(Box::new(Function::new(
464                "BITOR".to_string(),
465                vec![f.left, f.right],
466            )))),
467
468            // BitwiseXor -> BITXOR
469            Expression::BitwiseXor(f) => Ok(Expression::Function(Box::new(Function::new(
470                "BITXOR".to_string(),
471                vec![f.left, f.right],
472            )))),
473
474            // BitwiseAnd -> BITAND
475            Expression::BitwiseAnd(f) => Ok(Expression::Function(Box::new(Function::new(
476                "BITAND".to_string(),
477                vec![f.left, f.right],
478            )))),
479
480            // BitwiseNot -> BITNOT
481            Expression::BitwiseNot(f) => Ok(Expression::Function(Box::new(Function::new(
482                "BITNOT".to_string(),
483                vec![f.this],
484            )))),
485
486            // BitwiseLeftShift -> BITSHIFTLEFT
487            Expression::BitwiseLeftShift(f) => Ok(Expression::Function(Box::new(Function::new(
488                "BITSHIFTLEFT".to_string(),
489                vec![f.left, f.right],
490            )))),
491
492            // BitwiseRightShift -> BITSHIFTRIGHT
493            Expression::BitwiseRightShift(f) => Ok(Expression::Function(Box::new(Function::new(
494                "BITSHIFTRIGHT".to_string(),
495                vec![f.left, f.right],
496            )))),
497
498            // BitwiseAndAgg -> BITAND_AGG
499            Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
500                "BITAND_AGG".to_string(),
501                vec![f.this],
502            )))),
503
504            // BitwiseOrAgg -> BITOR_AGG
505            Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
506                "BITOR_AGG".to_string(),
507                vec![f.this],
508            )))),
509
510            // BitwiseXorAgg -> BITXOR_AGG
511            Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
512                "BITXOR_AGG".to_string(),
513                vec![f.this],
514            )))),
515
516            // ===== Boolean aggregates =====
517            // LogicalAnd -> BOOLAND_AGG
518            Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
519                "BOOLAND_AGG".to_string(),
520                vec![f.this],
521            )))),
522
523            // LogicalOr -> BOOLOR_AGG
524            Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
525                "BOOLOR_AGG".to_string(),
526                vec![f.this],
527            )))),
528
529            // Booland -> BOOLAND
530            Expression::Booland(f) => Ok(Expression::Function(Box::new(Function::new(
531                "BOOLAND".to_string(),
532                vec![*f.this, *f.expression],
533            )))),
534
535            // Boolor -> BOOLOR
536            Expression::Boolor(f) => Ok(Expression::Function(Box::new(Function::new(
537                "BOOLOR".to_string(),
538                vec![*f.this, *f.expression],
539            )))),
540
541            // Xor -> BOOLXOR
542            Expression::Xor(f) => {
543                let mut args = Vec::new();
544                if let Some(this) = f.this {
545                    args.push(*this);
546                }
547                if let Some(expr) = f.expression {
548                    args.push(*expr);
549                }
550                Ok(Expression::Function(Box::new(Function::new(
551                    "BOOLXOR".to_string(),
552                    args,
553                ))))
554            }
555
556            // ===== Date/time functions =====
557            // DayOfMonth -> DAYOFMONTH
558            Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
559                "DAYOFMONTH".to_string(),
560                vec![f.this],
561            )))),
562
563            // DayOfWeek -> DAYOFWEEK
564            Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
565                "DAYOFWEEK".to_string(),
566                vec![f.this],
567            )))),
568
569            // DayOfWeekIso -> DAYOFWEEKISO
570            Expression::DayOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
571                "DAYOFWEEKISO".to_string(),
572                vec![f.this],
573            )))),
574
575            // DayOfYear -> DAYOFYEAR
576            Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
577                "DAYOFYEAR".to_string(),
578                vec![f.this],
579            )))),
580
581            // WeekOfYear -> WEEK (Snowflake native function)
582            Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
583                "WEEK".to_string(),
584                vec![f.this],
585            )))),
586
587            // YearOfWeek -> YEAROFWEEK
588            Expression::YearOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
589                "YEAROFWEEK".to_string(),
590                vec![f.this],
591            )))),
592
593            // YearOfWeekIso -> YEAROFWEEKISO
594            Expression::YearOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
595                "YEAROFWEEKISO".to_string(),
596                vec![f.this],
597            )))),
598
599            // ByteLength -> OCTET_LENGTH
600            Expression::ByteLength(f) => Ok(Expression::Function(Box::new(Function::new(
601                "OCTET_LENGTH".to_string(),
602                vec![f.this],
603            )))),
604
605            // TimestampDiff -> TIMESTAMPDIFF
606            Expression::TimestampDiff(f) => {
607                let mut args = vec![];
608                // If unit is set (from cross-dialect normalize), use unit as first arg, this as second, expression as third
609                if let Some(ref unit_str) = f.unit {
610                    args.push(Expression::Identifier(crate::expressions::Identifier::new(
611                        unit_str.clone(),
612                    )));
613                    args.push(*f.this);
614                    args.push(*f.expression);
615                } else {
616                    args.push(*f.this);
617                    args.push(*f.expression);
618                }
619                Ok(Expression::Function(Box::new(Function::new(
620                    "TIMESTAMPDIFF".to_string(),
621                    args,
622                ))))
623            }
624
625            // TimestampAdd -> TIMESTAMPADD
626            Expression::TimestampAdd(f) => {
627                let mut args = vec![];
628                if let Some(ref unit_str) = f.unit {
629                    args.push(Expression::Identifier(crate::expressions::Identifier::new(
630                        unit_str.clone(),
631                    )));
632                    args.push(*f.this);
633                    args.push(*f.expression);
634                } else {
635                    args.push(*f.this);
636                    args.push(*f.expression);
637                }
638                Ok(Expression::Function(Box::new(Function::new(
639                    "TIMESTAMPADD".to_string(),
640                    args,
641                ))))
642            }
643
644            // ToArray -> TO_ARRAY
645            Expression::ToArray(f) => Ok(Expression::Function(Box::new(Function::new(
646                "TO_ARRAY".to_string(),
647                vec![f.this],
648            )))),
649
650            // DateAdd -> DATEADD (with unit, amount, date order)
651            Expression::DateAdd(f) => {
652                let unit_str = interval_unit_to_str(&f.unit);
653                let unit = Expression::Identifier(crate::expressions::Identifier {
654                    name: unit_str,
655                    quoted: false,
656                    trailing_comments: Vec::new(),
657                    span: None,
658                });
659                Ok(Expression::Function(Box::new(Function::new(
660                    "DATEADD".to_string(),
661                    vec![unit, f.interval, f.this],
662                ))))
663            }
664
665            // DateSub -> DATEADD with negated amount: val * -1
666            Expression::DateSub(f) => {
667                let unit_str = interval_unit_to_str(&f.unit);
668                let unit = Expression::Identifier(crate::expressions::Identifier {
669                    name: unit_str,
670                    quoted: false,
671                    trailing_comments: Vec::new(),
672                    span: None,
673                });
674                // Negate using val * -1 format (matching Python sqlglot output)
675                let neg_expr = Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
676                    f.interval,
677                    Expression::Neg(Box::new(crate::expressions::UnaryOp {
678                        this: Expression::number(1),
679                        inferred_type: None,
680                    })),
681                )));
682                Ok(Expression::Function(Box::new(Function::new(
683                    "DATEADD".to_string(),
684                    vec![unit, neg_expr, f.this],
685                ))))
686            }
687
688            // DateDiff -> DATEDIFF
689            Expression::DateDiff(f) => {
690                let unit_str =
691                    interval_unit_to_str(&f.unit.unwrap_or(crate::expressions::IntervalUnit::Day));
692                let unit = Expression::Identifier(crate::expressions::Identifier {
693                    name: unit_str,
694                    quoted: false,
695                    trailing_comments: Vec::new(),
696                    span: None,
697                });
698                Ok(Expression::Function(Box::new(Function::new(
699                    "DATEDIFF".to_string(),
700                    vec![unit, f.expression, f.this],
701                ))))
702            }
703
704            // ===== String functions =====
705            // StringAgg -> LISTAGG in Snowflake
706            Expression::StringAgg(f) => {
707                let mut args = vec![f.this.clone()];
708                if let Some(separator) = &f.separator {
709                    args.push(separator.clone());
710                }
711                Ok(Expression::Function(Box::new(Function::new(
712                    "LISTAGG".to_string(),
713                    args,
714                ))))
715            }
716
717            // StartsWith -> STARTSWITH
718            Expression::StartsWith(f) => Ok(Expression::Function(Box::new(Function::new(
719                "STARTSWITH".to_string(),
720                vec![f.this, f.expression],
721            )))),
722
723            // EndsWith -> keep as EndsWith AST node; generator outputs per-dialect
724            Expression::EndsWith(f) => Ok(Expression::EndsWith(f)),
725
726            // Stuff -> INSERT
727            Expression::Stuff(f) => {
728                let mut args = vec![*f.this];
729                if let Some(start) = f.start {
730                    args.push(*start);
731                }
732                if let Some(length) = f.length {
733                    args.push(Expression::number(length));
734                }
735                args.push(*f.expression);
736                Ok(Expression::Function(Box::new(Function::new(
737                    "INSERT".to_string(),
738                    args,
739                ))))
740            }
741
742            // ===== Hash functions =====
743            // SHA -> SHA1
744            Expression::SHA(f) => Ok(Expression::Function(Box::new(Function::new(
745                "SHA1".to_string(),
746                vec![f.this],
747            )))),
748
749            // SHA1Digest -> SHA1_BINARY
750            Expression::SHA1Digest(f) => Ok(Expression::Function(Box::new(Function::new(
751                "SHA1_BINARY".to_string(),
752                vec![f.this],
753            )))),
754
755            // SHA2Digest -> SHA2_BINARY
756            Expression::SHA2Digest(f) => Ok(Expression::Function(Box::new(Function::new(
757                "SHA2_BINARY".to_string(),
758                vec![*f.this],
759            )))),
760
761            // MD5Digest -> MD5_BINARY
762            Expression::MD5Digest(f) => Ok(Expression::Function(Box::new(Function::new(
763                "MD5_BINARY".to_string(),
764                vec![*f.this],
765            )))),
766
767            // MD5NumberLower64 -> MD5_NUMBER_LOWER64
768            Expression::MD5NumberLower64(f) => Ok(Expression::Function(Box::new(Function::new(
769                "MD5_NUMBER_LOWER64".to_string(),
770                vec![f.this],
771            )))),
772
773            // MD5NumberUpper64 -> MD5_NUMBER_UPPER64
774            Expression::MD5NumberUpper64(f) => Ok(Expression::Function(Box::new(Function::new(
775                "MD5_NUMBER_UPPER64".to_string(),
776                vec![f.this],
777            )))),
778
779            // ===== Vector functions =====
780            // CosineDistance -> VECTOR_COSINE_SIMILARITY
781            Expression::CosineDistance(f) => Ok(Expression::Function(Box::new(Function::new(
782                "VECTOR_COSINE_SIMILARITY".to_string(),
783                vec![*f.this, *f.expression],
784            )))),
785
786            // DotProduct -> VECTOR_INNER_PRODUCT
787            Expression::DotProduct(f) => Ok(Expression::Function(Box::new(Function::new(
788                "VECTOR_INNER_PRODUCT".to_string(),
789                vec![*f.this, *f.expression],
790            )))),
791
792            // EuclideanDistance -> VECTOR_L2_DISTANCE
793            Expression::EuclideanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
794                "VECTOR_L2_DISTANCE".to_string(),
795                vec![*f.this, *f.expression],
796            )))),
797
798            // ManhattanDistance -> VECTOR_L1_DISTANCE
799            Expression::ManhattanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
800                "VECTOR_L1_DISTANCE".to_string(),
801                vec![*f.this, *f.expression],
802            )))),
803
804            // ===== JSON/Struct functions =====
805            // JSONFormat -> TO_JSON
806            Expression::JSONFormat(f) => {
807                let mut args = Vec::new();
808                if let Some(this) = f.this {
809                    args.push(*this);
810                }
811                Ok(Expression::Function(Box::new(Function::new(
812                    "TO_JSON".to_string(),
813                    args,
814                ))))
815            }
816
817            // JSONKeys -> OBJECT_KEYS
818            Expression::JSONKeys(f) => Ok(Expression::Function(Box::new(Function::new(
819                "OBJECT_KEYS".to_string(),
820                vec![*f.this],
821            )))),
822
823            // GetExtract -> GET
824            Expression::GetExtract(f) => Ok(Expression::Function(Box::new(Function::new(
825                "GET".to_string(),
826                vec![*f.this, *f.expression],
827            )))),
828
829            // StarMap -> OBJECT_CONSTRUCT
830            Expression::StarMap(f) => Ok(Expression::Function(Box::new(Function::new(
831                "OBJECT_CONSTRUCT".to_string(),
832                vec![f.this, f.expression],
833            )))),
834
835            // LowerHex -> TO_CHAR
836            Expression::LowerHex(f) => Ok(Expression::Function(Box::new(Function::new(
837                "TO_CHAR".to_string(),
838                vec![f.this],
839            )))),
840
841            // Skewness -> SKEW
842            Expression::Skewness(f) => Ok(Expression::Function(Box::new(Function::new(
843                "SKEW".to_string(),
844                vec![f.this],
845            )))),
846
847            // StPoint -> ST_MAKEPOINT
848            Expression::StPoint(f) => Ok(Expression::Function(Box::new(Function::new(
849                "ST_MAKEPOINT".to_string(),
850                vec![*f.this, *f.expression],
851            )))),
852
853            // FromTimeZone -> CONVERT_TIMEZONE
854            Expression::FromTimeZone(f) => Ok(Expression::Function(Box::new(Function::new(
855                "CONVERT_TIMEZONE".to_string(),
856                vec![*f.this],
857            )))),
858
859            // ===== Conversion functions =====
860            // Unhex -> HEX_DECODE_BINARY
861            Expression::Unhex(f) => Ok(Expression::Function(Box::new(Function::new(
862                "HEX_DECODE_BINARY".to_string(),
863                vec![*f.this],
864            )))),
865
866            // UnixToTime -> TO_TIMESTAMP
867            Expression::UnixToTime(f) => {
868                let mut args = vec![*f.this];
869                if let Some(scale) = f.scale {
870                    args.push(Expression::number(scale));
871                }
872                Ok(Expression::Function(Box::new(Function::new(
873                    "TO_TIMESTAMP".to_string(),
874                    args,
875                ))))
876            }
877
878            // ===== Conditional =====
879            // IfFunc -> keep as IfFunc with IFF name for Snowflake
880            Expression::IfFunc(f) => Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
881                condition: f.condition,
882                true_value: f.true_value,
883                false_value: Some(
884                    f.false_value
885                        .unwrap_or(Expression::Null(crate::expressions::Null)),
886                ),
887                original_name: Some("IFF".to_string()),
888                inferred_type: None,
889            }))),
890
891            // ===== Aggregate functions =====
892            // ApproxDistinct -> APPROX_COUNT_DISTINCT
893            Expression::ApproxDistinct(f) => Ok(Expression::Function(Box::new(Function::new(
894                "APPROX_COUNT_DISTINCT".to_string(),
895                vec![f.this],
896            )))),
897
898            // ArgMax -> MAX_BY
899            Expression::ArgMax(f) => Ok(Expression::Function(Box::new(Function::new(
900                "MAX_BY".to_string(),
901                vec![*f.this, *f.expression],
902            )))),
903
904            // ArgMin -> MIN_BY
905            Expression::ArgMin(f) => Ok(Expression::Function(Box::new(Function::new(
906                "MIN_BY".to_string(),
907                vec![*f.this, *f.expression],
908            )))),
909
910            // ===== Random =====
911            // RANDOM is native to Snowflake - keep as-is
912            Expression::Random(_) => Ok(Expression::Random(crate::expressions::Random)),
913
914            // Rand - keep as-is (generator outputs RANDOM for Snowflake)
915            Expression::Rand(r) => Ok(Expression::Rand(r)),
916
917            // ===== UUID =====
918            // Uuid -> keep as Uuid node; generator will output UUID_STRING for Snowflake
919            Expression::Uuid(u) => Ok(Expression::Uuid(u)),
920
921            // ===== Map/Object =====
922            // Map -> OBJECT_CONSTRUCT
923            Expression::Map(f) => Ok(Expression::Function(Box::new(Function::new(
924                "OBJECT_CONSTRUCT".to_string(),
925                f.keys
926                    .into_iter()
927                    .zip(f.values.into_iter())
928                    .flat_map(|(k, v)| vec![k, v])
929                    .collect(),
930            )))),
931
932            // MapFunc (curly brace syntax) -> OBJECT_CONSTRUCT
933            Expression::MapFunc(f) => Ok(Expression::Function(Box::new(Function::new(
934                "OBJECT_CONSTRUCT".to_string(),
935                f.keys
936                    .into_iter()
937                    .zip(f.values.into_iter())
938                    .flat_map(|(k, v)| vec![k, v])
939                    .collect(),
940            )))),
941
942            // VarMap -> OBJECT_CONSTRUCT
943            Expression::VarMap(f) => Ok(Expression::Function(Box::new(Function::new(
944                "OBJECT_CONSTRUCT".to_string(),
945                f.keys
946                    .into_iter()
947                    .zip(f.values.into_iter())
948                    .flat_map(|(k, v)| vec![k, v])
949                    .collect(),
950            )))),
951
952            // ===== JSON =====
953            // JSONObject -> OBJECT_CONSTRUCT_KEEP_NULL
954            Expression::JsonObject(f) => Ok(Expression::Function(Box::new(Function::new(
955                "OBJECT_CONSTRUCT_KEEP_NULL".to_string(),
956                f.pairs.into_iter().flat_map(|(k, v)| vec![k, v]).collect(),
957            )))),
958
959            // JSONExtractScalar -> JSON_EXTRACT_PATH_TEXT
960            Expression::JsonExtractScalar(f) => Ok(Expression::Function(Box::new(Function::new(
961                "JSON_EXTRACT_PATH_TEXT".to_string(),
962                vec![f.this, f.path],
963            )))),
964
965            // ===== Struct =====
966            // Struct -> OBJECT_CONSTRUCT
967            Expression::Struct(f) => Ok(Expression::Function(Box::new(Function::new(
968                "OBJECT_CONSTRUCT".to_string(),
969                f.fields
970                    .into_iter()
971                    .flat_map(|(name, expr)| {
972                        let key = match name {
973                            Some(n) => Expression::string(n),
974                            None => Expression::Null(crate::expressions::Null),
975                        };
976                        vec![key, expr]
977                    })
978                    .collect(),
979            )))),
980
981            // ===== JSON Path =====
982            // JSONPathRoot -> empty string ($ is implicit in Snowflake)
983            Expression::JSONPathRoot(_) => Ok(Expression::Literal(Box::new(
984                crate::expressions::Literal::String(String::new()),
985            ))),
986
987            // ===== VarSamp -> VARIANCE (Snowflake) =====
988            // Snowflake uses VARIANCE instead of VAR_SAMP
989            Expression::VarSamp(agg) => Ok(Expression::Variance(agg)),
990
991            // ===== VarPop -> keep as VarPop =====
992            // The generator handles dialect-specific naming (VARIANCE_POP for Snowflake)
993            Expression::VarPop(agg) => Ok(Expression::VarPop(agg)),
994
995            // ===== EXTRACT -> DATE_PART =====
996            // Snowflake uses DATE_PART instead of EXTRACT
997            Expression::Extract(f) => {
998                use crate::expressions::DateTimeField;
999                // Recursively transform the inner expression (e.g., CAST(... AS TIMESTAMP_NTZ) -> CAST(... AS TIMESTAMPNTZ))
1000                let transformed_this = self.transform_expr(f.this)?;
1001                let field_name = match &f.field {
1002                    DateTimeField::Year => "YEAR",
1003                    DateTimeField::Month => "MONTH",
1004                    DateTimeField::Day => "DAY",
1005                    DateTimeField::Hour => "HOUR",
1006                    DateTimeField::Minute => "MINUTE",
1007                    DateTimeField::Second => "SECOND",
1008                    DateTimeField::Millisecond => "MILLISECOND",
1009                    DateTimeField::Microsecond => "MICROSECOND",
1010                    DateTimeField::Week => "WEEK",
1011                    DateTimeField::WeekWithModifier(m) => {
1012                        return Ok(Expression::Function(Box::new(Function::new(
1013                            "DATE_PART".to_string(),
1014                            vec![
1015                                Expression::Identifier(crate::expressions::Identifier {
1016                                    name: format!("WEEK({})", m),
1017                                    quoted: false,
1018                                    trailing_comments: Vec::new(),
1019                                    span: None,
1020                                }),
1021                                transformed_this,
1022                            ],
1023                        ))))
1024                    }
1025                    DateTimeField::DayOfWeek => "DAYOFWEEK",
1026                    DateTimeField::DayOfYear => "DAYOFYEAR",
1027                    DateTimeField::Quarter => "QUARTER",
1028                    DateTimeField::Epoch => "EPOCH",
1029                    DateTimeField::Timezone => "TIMEZONE",
1030                    DateTimeField::TimezoneHour => "TIMEZONE_HOUR",
1031                    DateTimeField::TimezoneMinute => "TIMEZONE_MINUTE",
1032                    DateTimeField::Date => "DATE",
1033                    DateTimeField::Time => "TIME",
1034                    DateTimeField::Custom(s) => {
1035                        // Map common EXTRACT field names to Snowflake DATE_PART names
1036                        match s.to_uppercase().as_str() {
1037                            "DAYOFMONTH" => "DAY",
1038                            "DOW" => "DAYOFWEEK",
1039                            "DOY" => "DAYOFYEAR",
1040                            "ISODOW" => "DAYOFWEEKISO",
1041                            "EPOCH_SECOND" | "EPOCH_SECONDS" => "EPOCH_SECOND",
1042                            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => "EPOCH_MILLISECOND",
1043                            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => "EPOCH_MICROSECOND",
1044                            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => "EPOCH_NANOSECOND",
1045                            _ => {
1046                                return {
1047                                    let field_ident =
1048                                        Expression::Identifier(crate::expressions::Identifier {
1049                                            name: s.to_string(),
1050                                            quoted: false,
1051                                            trailing_comments: Vec::new(),
1052                                            span: None,
1053                                        });
1054                                    Ok(Expression::Function(Box::new(Function::new(
1055                                        "DATE_PART".to_string(),
1056                                        vec![field_ident, transformed_this],
1057                                    ))))
1058                                }
1059                            }
1060                        }
1061                    }
1062                };
1063                let field_ident = Expression::Identifier(crate::expressions::Identifier {
1064                    name: field_name.to_string(),
1065                    quoted: false,
1066                    trailing_comments: Vec::new(),
1067                    span: None,
1068                });
1069                Ok(Expression::Function(Box::new(Function::new(
1070                    "DATE_PART".to_string(),
1071                    vec![field_ident, transformed_this],
1072                ))))
1073            }
1074
1075            // Generic function transformations
1076            Expression::Function(f) => self.transform_function(*f),
1077
1078            // SUM - recursively transform inner expression
1079            Expression::Sum(mut agg) => {
1080                agg.this = self.transform_expr(agg.this)?;
1081                Ok(Expression::Sum(agg))
1082            }
1083
1084            // Generic aggregate function transformations
1085            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
1086
1087            // Handle NamedArgument - recursively transform the value
1088            Expression::NamedArgument(na) => {
1089                let transformed_value = self.transform_expr(na.value)?;
1090                Ok(Expression::NamedArgument(Box::new(
1091                    crate::expressions::NamedArgument {
1092                        name: na.name,
1093                        value: transformed_value,
1094                        separator: na.separator,
1095                    },
1096                )))
1097            }
1098
1099            // Handle CreateTable - transform column data types and default/computed expressions
1100            Expression::CreateTable(mut ct) => {
1101                for col in &mut ct.columns {
1102                    if let Expression::DataType(new_dt) =
1103                        self.transform_data_type(col.data_type.clone())?
1104                    {
1105                        col.data_type = new_dt;
1106                    }
1107                    // Also transform computed/default expressions (e.g., AS (parse_json(x):COL3::number))
1108                    if let Some(default_expr) = col.default.take() {
1109                        col.default = Some(self.transform_expr(default_expr)?);
1110                    }
1111                    // Transform expressions in column constraints (computed columns)
1112                    for constraint in &mut col.constraints {
1113                        if let crate::expressions::ColumnConstraint::ComputedColumn(cc) = constraint
1114                        {
1115                            let transformed = self.transform_expr(*cc.expression.clone())?;
1116                            cc.expression = Box::new(transformed);
1117                        }
1118                    }
1119                }
1120
1121                // For EXTERNAL tables, convert with_properties to Raw properties
1122                // with proper Snowflake formatting (no WITH wrapper, specific key casing)
1123                if ct.table_modifier.as_deref() == Some("EXTERNAL")
1124                    && !ct.with_properties.is_empty()
1125                {
1126                    for (key, value) in ct.with_properties.drain(..) {
1127                        let formatted = Self::format_external_table_property(&key, &value);
1128                        ct.properties
1129                            .push(Expression::Raw(crate::expressions::Raw { sql: formatted }));
1130                    }
1131                }
1132
1133                Ok(Expression::CreateTable(ct))
1134            }
1135
1136            // Handle AlterTable - transform column data types in ADD operations
1137            Expression::AlterTable(mut at) => {
1138                for action in &mut at.actions {
1139                    if let crate::expressions::AlterTableAction::AddColumn { column, .. } = action {
1140                        if let Expression::DataType(new_dt) =
1141                            self.transform_data_type(column.data_type.clone())?
1142                        {
1143                            column.data_type = new_dt;
1144                        }
1145                    }
1146                }
1147                Ok(Expression::AlterTable(at))
1148            }
1149
1150            // Handle Table reference - transform HistoricalData (AT/BEFORE time travel clauses)
1151            Expression::Table(mut t) => {
1152                if let Some(when) = t.when.take() {
1153                    // Recursively transform the expression inside HistoricalData
1154                    let transformed_expr = self.transform_expr(*when.expression)?;
1155                    t.when = Some(Box::new(crate::expressions::HistoricalData {
1156                        this: when.this,
1157                        kind: when.kind,
1158                        expression: Box::new(transformed_expr),
1159                    }));
1160                }
1161                Ok(Expression::Table(t))
1162            }
1163
1164            // Handle Subscript - recursively transform inner expression
1165            Expression::Subscript(s) => {
1166                let transformed_this = self.transform_expr(s.this)?;
1167                let transformed_index = self.transform_expr(s.index)?;
1168                Ok(Expression::Subscript(Box::new(
1169                    crate::expressions::Subscript {
1170                        this: transformed_this,
1171                        index: transformed_index,
1172                    },
1173                )))
1174            }
1175
1176            // Recursively transform parenthesized expressions
1177            Expression::Paren(p) => {
1178                let transformed = self.transform_expr(p.this)?;
1179                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
1180                    this: transformed,
1181                    trailing_comments: p.trailing_comments,
1182                })))
1183            }
1184
1185            // ===== ORDER BY null ordering normalization =====
1186            // Snowflake is nulls_are_large: ASC defaults to NULLS LAST, DESC defaults to NULLS FIRST
1187            // Fill in implicit nulls_first so target dialects can properly strip/add as needed
1188            Expression::Select(mut select) => {
1189                if let Some(ref mut order) = select.order_by {
1190                    for ord in &mut order.expressions {
1191                        if ord.nulls_first.is_none() {
1192                            ord.nulls_first = Some(ord.desc);
1193                        }
1194                    }
1195                }
1196                Ok(Expression::Select(select))
1197            }
1198
1199            // Fill in NULLS ordering for window function ORDER BY clauses
1200            Expression::WindowFunction(mut wf) => {
1201                for ord in &mut wf.over.order_by {
1202                    if ord.nulls_first.is_none() {
1203                        ord.nulls_first = Some(ord.desc);
1204                    }
1205                }
1206                Ok(Expression::WindowFunction(wf))
1207            }
1208
1209            // Also handle Expression::Window (WindowSpec)
1210            Expression::Window(mut w) => {
1211                for ord in &mut w.order_by {
1212                    if ord.nulls_first.is_none() {
1213                        ord.nulls_first = Some(ord.desc);
1214                    }
1215                }
1216                Ok(Expression::Window(w))
1217            }
1218
1219            // LATERAL FLATTEN: add default column aliases (SEQ, KEY, PATH, INDEX, VALUE, THIS)
1220            Expression::Lateral(mut lat) => {
1221                // Check if the inner expression is a FLATTEN function
1222                let is_flatten = match lat.this.as_ref() {
1223                    Expression::Function(f) => f.name.to_uppercase() == "FLATTEN",
1224                    _ => false,
1225                };
1226                if is_flatten && lat.column_aliases.is_empty() {
1227                    // Add default column aliases
1228                    lat.column_aliases = vec![
1229                        "SEQ".to_string(),
1230                        "KEY".to_string(),
1231                        "PATH".to_string(),
1232                        "INDEX".to_string(),
1233                        "VALUE".to_string(),
1234                        "THIS".to_string(),
1235                    ];
1236                    // If no alias, add _flattened
1237                    if lat.alias.is_none() {
1238                        lat.alias = Some("_flattened".to_string());
1239                    }
1240                }
1241                Ok(Expression::Lateral(lat))
1242            }
1243
1244            // Pass through everything else
1245            _ => Ok(expr),
1246        }
1247    }
1248}
1249
1250impl SnowflakeDialect {
1251    /// Format a Snowflake external table property for output.
1252    /// Some properties like LOCATION and FILE_FORMAT are uppercased keywords.
1253    fn format_external_table_property(key: &str, value: &str) -> String {
1254        let lower_key = key.to_lowercase();
1255        match lower_key.as_str() {
1256            "location" => format!("LOCATION={}", value),
1257            "file_format" => {
1258                // Format file_format value: remove spaces around =, uppercase booleans
1259                let formatted_value = Self::format_file_format_value(value);
1260                format!("FILE_FORMAT={}", formatted_value)
1261            }
1262            _ => format!("{}={}", key, value),
1263        }
1264    }
1265
1266    /// Format file_format property value:
1267    /// - Remove spaces around = signs
1268    /// - Uppercase boolean values (false -> FALSE, true -> TRUE)
1269    fn format_file_format_value(value: &str) -> String {
1270        if !value.starts_with('(') {
1271            return value.to_string();
1272        }
1273        // Strip outer parens, process inner key=value pairs
1274        let inner = value[1..value.len() - 1].trim();
1275        // Parse space-separated key=value pairs (may have spaces around =)
1276        let mut result = String::from("(");
1277        let mut parts: Vec<String> = Vec::new();
1278        // Split by whitespace and reconstruct key=value pairs
1279        let tokens: Vec<&str> = inner.split_whitespace().collect();
1280        let mut i = 0;
1281        while i < tokens.len() {
1282            let token = tokens[i];
1283            if i + 2 < tokens.len() && tokens[i + 1] == "=" {
1284                // key = value pattern
1285                let val = Self::format_property_value(tokens[i + 2]);
1286                parts.push(format!("{}={}", token, val));
1287                i += 3;
1288            } else if token.contains('=') {
1289                // key=value already joined
1290                let eq_pos = token.find('=').unwrap();
1291                let k = &token[..eq_pos];
1292                let v = Self::format_property_value(&token[eq_pos + 1..]);
1293                parts.push(format!("{}={}", k, v));
1294                i += 1;
1295            } else {
1296                parts.push(token.to_string());
1297                i += 1;
1298            }
1299        }
1300        result.push_str(&parts.join(" "));
1301        result.push(')');
1302        result
1303    }
1304
1305    /// Format a property value - uppercase boolean literals
1306    fn format_property_value(value: &str) -> String {
1307        match value.to_lowercase().as_str() {
1308            "true" => "TRUE".to_string(),
1309            "false" => "FALSE".to_string(),
1310            _ => value.to_string(),
1311        }
1312    }
1313
1314    /// Transform data types according to Snowflake TYPE_MAPPING
1315    fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
1316        use crate::expressions::DataType;
1317        let transformed = match dt {
1318            // TEXT -> VARCHAR
1319            DataType::Text => DataType::VarChar {
1320                length: None,
1321                parenthesized_length: false,
1322            },
1323            // STRUCT -> OBJECT
1324            DataType::Struct { fields, .. } => {
1325                // Snowflake uses OBJECT for struct types
1326                let _ = fields; // Snowflake OBJECT doesn't preserve field names in the same way
1327                DataType::Custom {
1328                    name: "OBJECT".to_string(),
1329                }
1330            }
1331            // Custom type transformations
1332            DataType::Custom { name } => {
1333                let upper_name = name.to_uppercase();
1334                match upper_name.as_str() {
1335                    // NVARCHAR -> VARCHAR (SQL Server type)
1336                    "NVARCHAR" | "NCHAR" | "NATIONAL CHARACTER VARYING" | "NATIONAL CHAR" => {
1337                        DataType::VarChar {
1338                            length: None,
1339                            parenthesized_length: false,
1340                        }
1341                    }
1342                    // STRING -> VARCHAR (Snowflake accepts both, but normalizes to VARCHAR)
1343                    "STRING" => DataType::VarChar {
1344                        length: None,
1345                        parenthesized_length: false,
1346                    },
1347                    // BIGDECIMAL -> DOUBLE
1348                    "BIGDECIMAL" => DataType::Double {
1349                        precision: None,
1350                        scale: None,
1351                    },
1352                    // NESTED -> OBJECT
1353                    "NESTED" => DataType::Custom {
1354                        name: "OBJECT".to_string(),
1355                    },
1356                    // BYTEINT -> INT
1357                    "BYTEINT" => DataType::Int {
1358                        length: None,
1359                        integer_spelling: false,
1360                    },
1361                    // CHAR VARYING -> VARCHAR
1362                    "CHAR VARYING" | "CHARACTER VARYING" => DataType::VarChar {
1363                        length: None,
1364                        parenthesized_length: false,
1365                    },
1366                    // SQL_DOUBLE -> DOUBLE
1367                    "SQL_DOUBLE" => DataType::Double {
1368                        precision: None,
1369                        scale: None,
1370                    },
1371                    // SQL_VARCHAR -> VARCHAR
1372                    "SQL_VARCHAR" => DataType::VarChar {
1373                        length: None,
1374                        parenthesized_length: false,
1375                    },
1376                    // TIMESTAMP_NTZ -> TIMESTAMPNTZ (normalize underscore form)
1377                    "TIMESTAMP_NTZ" => DataType::Custom {
1378                        name: "TIMESTAMPNTZ".to_string(),
1379                    },
1380                    // TIMESTAMP_LTZ -> TIMESTAMPLTZ (normalize underscore form)
1381                    "TIMESTAMP_LTZ" => DataType::Custom {
1382                        name: "TIMESTAMPLTZ".to_string(),
1383                    },
1384                    // TIMESTAMP_TZ -> TIMESTAMPTZ (normalize underscore form)
1385                    "TIMESTAMP_TZ" => DataType::Custom {
1386                        name: "TIMESTAMPTZ".to_string(),
1387                    },
1388                    // NCHAR VARYING -> VARCHAR
1389                    "NCHAR VARYING" => DataType::VarChar {
1390                        length: None,
1391                        parenthesized_length: false,
1392                    },
1393                    // NUMBER -> DECIMAL(38, 0) (Snowflake's default NUMBER is DECIMAL(38, 0))
1394                    "NUMBER" => DataType::Decimal {
1395                        precision: Some(38),
1396                        scale: Some(0),
1397                    },
1398                    _ if name.starts_with("NUMBER(") => {
1399                        // NUMBER(precision, scale) -> DECIMAL(precision, scale)
1400                        // Parse: "NUMBER(38, 0)" -> precision=38, scale=0
1401                        let inner = &name[7..name.len() - 1]; // strip "NUMBER(" and ")"
1402                        let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
1403                        let precision = parts.first().and_then(|p| p.parse::<u32>().ok());
1404                        let scale = parts.get(1).and_then(|s| s.parse::<u32>().ok());
1405                        DataType::Decimal { precision, scale }
1406                    }
1407                    _ => DataType::Custom { name },
1408                }
1409            }
1410            // DECIMAL without precision -> DECIMAL(38, 0) (Snowflake default)
1411            DataType::Decimal {
1412                precision: None,
1413                scale: None,
1414            } => DataType::Decimal {
1415                precision: Some(38),
1416                scale: Some(0),
1417            },
1418            // FLOAT -> DOUBLE (Snowflake FLOAT is actually 64-bit DOUBLE)
1419            DataType::Float { .. } => DataType::Double {
1420                precision: None,
1421                scale: None,
1422            },
1423            // Keep all other types as-is (Snowflake is quite flexible)
1424            other => other,
1425        };
1426        Ok(Expression::DataType(transformed))
1427    }
1428
1429    /// Map date part abbreviation to canonical form (from Python SQLGlot DATE_PART_MAPPING)
1430    fn map_date_part(abbr: &str) -> Option<&'static str> {
1431        match abbr.to_uppercase().as_str() {
1432            // Year
1433            "Y" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => Some("YEAR"),
1434            // Month
1435            "MM" | "MON" | "MONS" | "MONTHS" => Some("MONTH"),
1436            // Day
1437            "D" | "DD" | "DAYS" | "DAYOFMONTH" => Some("DAY"),
1438            // Day of week
1439            "DAY OF WEEK" | "WEEKDAY" | "DOW" | "DW" => Some("DAYOFWEEK"),
1440            "WEEKDAY_ISO" | "DOW_ISO" | "DW_ISO" | "DAYOFWEEK_ISO" => Some("DAYOFWEEKISO"),
1441            // Day of year
1442            "DAY OF YEAR" | "DOY" | "DY" => Some("DAYOFYEAR"),
1443            // Week
1444            "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY" => Some("WEEK"),
1445            "WEEK_ISO" | "WEEKOFYEARISO" | "WEEKOFYEAR_ISO" => Some("WEEKISO"),
1446            // Quarter
1447            "Q" | "QTR" | "QTRS" | "QUARTERS" => Some("QUARTER"),
1448            // Hour
1449            "H" | "HH" | "HR" | "HOURS" | "HRS" => Some("HOUR"),
1450            // Minute (note: 'M' could be minute in some contexts, but we keep it simple)
1451            "MI" | "MIN" | "MINUTES" | "MINS" => Some("MINUTE"),
1452            // Second
1453            "S" | "SEC" | "SECONDS" | "SECS" => Some("SECOND"),
1454            // Millisecond
1455            "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC" | "MILLISECS"
1456            | "MILLISECON" | "MILLISECONDS" => Some("MILLISECOND"),
1457            // Microsecond
1458            "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND" | "USECONDS"
1459            | "MICROSECONDS" => Some("MICROSECOND"),
1460            // Nanosecond
1461            "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => Some("NANOSECOND"),
1462            // Epoch variants
1463            "EPOCH_SECOND" | "EPOCH_SECONDS" => Some("EPOCH_SECOND"),
1464            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => Some("EPOCH_MILLISECOND"),
1465            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => Some("EPOCH_MICROSECOND"),
1466            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => Some("EPOCH_NANOSECOND"),
1467            // Timezone
1468            "TZH" => Some("TIMEZONE_HOUR"),
1469            "TZM" => Some("TIMEZONE_MINUTE"),
1470            // Decade
1471            "DEC" | "DECS" | "DECADES" => Some("DECADE"),
1472            // Millennium
1473            "MIL" | "MILS" | "MILLENIA" => Some("MILLENNIUM"),
1474            // Century
1475            "C" | "CENT" | "CENTS" | "CENTURIES" => Some("CENTURY"),
1476            // No mapping needed (already canonical or unknown)
1477            _ => None,
1478        }
1479    }
1480
1481    /// Transform a date part identifier/expression using the mapping
1482    fn transform_date_part_arg(&self, expr: Expression) -> Expression {
1483        match &expr {
1484            // Handle string literal: 'minute' -> minute (unquoted identifier, preserving case)
1485            Expression::Literal(lit)
1486                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
1487            {
1488                let crate::expressions::Literal::String(s) = lit.as_ref() else {
1489                    unreachable!()
1490                };
1491                Expression::Identifier(crate::expressions::Identifier {
1492                    name: s.clone(),
1493                    quoted: false,
1494                    trailing_comments: Vec::new(),
1495                    span: None,
1496                })
1497            }
1498            // Handle Identifier (rare case)
1499            Expression::Identifier(id) => {
1500                if let Some(canonical) = Self::map_date_part(&id.name) {
1501                    Expression::Identifier(crate::expressions::Identifier {
1502                        name: canonical.to_string(),
1503                        quoted: false,
1504                        trailing_comments: Vec::new(),
1505                        span: None,
1506                    })
1507                } else {
1508                    // No mapping needed, keep original (Python sqlglot preserves case)
1509                    expr
1510                }
1511            }
1512            Expression::Var(v) => {
1513                if let Some(canonical) = Self::map_date_part(&v.this) {
1514                    Expression::Identifier(crate::expressions::Identifier {
1515                        name: canonical.to_string(),
1516                        quoted: false,
1517                        trailing_comments: Vec::new(),
1518                        span: None,
1519                    })
1520                } else {
1521                    expr
1522                }
1523            }
1524            // Handle Column (more common - parser treats unqualified names as columns)
1525            Expression::Column(col) if col.table.is_none() => {
1526                if let Some(canonical) = Self::map_date_part(&col.name.name) {
1527                    Expression::Identifier(crate::expressions::Identifier {
1528                        name: canonical.to_string(),
1529                        quoted: false,
1530                        trailing_comments: Vec::new(),
1531                        span: None,
1532                    })
1533                } else {
1534                    // No mapping needed, keep original (Python sqlglot preserves case)
1535                    expr
1536                }
1537            }
1538            _ => expr,
1539        }
1540    }
1541
1542    /// Like transform_date_part_arg but only handles Identifier/Column, never String literals.
1543    /// Used for native Snowflake DATE_PART where string args should stay as strings.
1544    fn transform_date_part_arg_identifiers_only(&self, expr: Expression) -> Expression {
1545        match &expr {
1546            Expression::Identifier(id) => {
1547                if let Some(canonical) = Self::map_date_part(&id.name) {
1548                    Expression::Identifier(crate::expressions::Identifier {
1549                        name: canonical.to_string(),
1550                        quoted: false,
1551                        trailing_comments: Vec::new(),
1552                        span: None,
1553                    })
1554                } else {
1555                    expr
1556                }
1557            }
1558            Expression::Var(v) => {
1559                if let Some(canonical) = Self::map_date_part(&v.this) {
1560                    Expression::Identifier(crate::expressions::Identifier {
1561                        name: canonical.to_string(),
1562                        quoted: false,
1563                        trailing_comments: Vec::new(),
1564                        span: None,
1565                    })
1566                } else {
1567                    expr
1568                }
1569            }
1570            Expression::Column(col) if col.table.is_none() => {
1571                if let Some(canonical) = Self::map_date_part(&col.name.name) {
1572                    Expression::Identifier(crate::expressions::Identifier {
1573                        name: canonical.to_string(),
1574                        quoted: false,
1575                        trailing_comments: Vec::new(),
1576                        span: None,
1577                    })
1578                } else {
1579                    expr
1580                }
1581            }
1582            _ => expr,
1583        }
1584    }
1585
1586    /// Transform JSON path for Snowflake GET_PATH function
1587    /// - Convert colon notation to dot notation (y[0]:z -> y[0].z)
1588    /// - Wrap unsafe keys in brackets ($id -> ["$id"])
1589    fn transform_json_path(path: &str) -> String {
1590        // Check if path is just a single key that needs bracket wrapping
1591        // A safe identifier is alphanumeric + underscore, starting with letter/underscore
1592        fn is_safe_identifier(s: &str) -> bool {
1593            if s.is_empty() {
1594                return false;
1595            }
1596            let mut chars = s.chars();
1597            match chars.next() {
1598                Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
1599                _ => return false,
1600            }
1601            chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
1602        }
1603
1604        // Simple path: just a key like "$id" or "field"
1605        // If no dots, brackets, or colons, it's a simple key
1606        if !path.contains('.') && !path.contains('[') && !path.contains(':') {
1607            if is_safe_identifier(path) {
1608                return path.to_string();
1609            } else {
1610                // Wrap unsafe key in bracket notation
1611                return format!("[\"{}\"]", path);
1612            }
1613        }
1614
1615        // Complex path: replace colons with dots
1616        // e.g., y[0]:z -> y[0].z
1617        let result = path.replace(':', ".");
1618        result
1619    }
1620
1621    /// Transform interval to expand abbreviated units (e.g., 'w' -> 'WEEK')
1622    fn transform_interval(&self, interval: crate::expressions::Interval) -> Result<Expression> {
1623        use crate::expressions::{Interval, Literal};
1624
1625        // Unit abbreviation mapping (from Python SQLGlot UNABBREVIATED_UNIT_NAME)
1626        fn expand_unit(abbr: &str) -> &'static str {
1627            match abbr.to_uppercase().as_str() {
1628                "D" => "DAY",
1629                "H" => "HOUR",
1630                "M" => "MINUTE",
1631                "MS" => "MILLISECOND",
1632                "NS" => "NANOSECOND",
1633                "Q" => "QUARTER",
1634                "S" => "SECOND",
1635                "US" => "MICROSECOND",
1636                "W" => "WEEK",
1637                "Y" => "YEAR",
1638                // Full forms (normalize to singular, uppercase)
1639                "WEEK" | "WEEKS" => "WEEK",
1640                "DAY" | "DAYS" => "DAY",
1641                "HOUR" | "HOURS" => "HOUR",
1642                "MINUTE" | "MINUTES" => "MINUTE",
1643                "SECOND" | "SECONDS" => "SECOND",
1644                "MONTH" | "MONTHS" => "MONTH",
1645                "YEAR" | "YEARS" => "YEAR",
1646                "QUARTER" | "QUARTERS" => "QUARTER",
1647                "MILLISECOND" | "MILLISECONDS" => "MILLISECOND",
1648                "MICROSECOND" | "MICROSECONDS" => "MICROSECOND",
1649                "NANOSECOND" | "NANOSECONDS" => "NANOSECOND",
1650                _ => "", // Unknown unit, return empty to indicate no match
1651            }
1652        }
1653
1654        /// Parse an interval string like "1 w" into (value, unit)
1655        fn parse_interval_string(s: &str) -> Option<(&str, &str)> {
1656            let s = s.trim();
1657
1658            // Find where the number ends and the unit begins
1659            // Number can be: optional -, digits, optional decimal point, more digits
1660            let mut num_end = 0;
1661            let mut chars = s.chars().peekable();
1662
1663            // Skip leading minus
1664            if chars.peek() == Some(&'-') {
1665                chars.next();
1666                num_end += 1;
1667            }
1668
1669            // Skip digits
1670            while let Some(&c) = chars.peek() {
1671                if c.is_ascii_digit() {
1672                    chars.next();
1673                    num_end += 1;
1674                } else {
1675                    break;
1676                }
1677            }
1678
1679            // Skip optional decimal point and more digits
1680            if chars.peek() == Some(&'.') {
1681                chars.next();
1682                num_end += 1;
1683                while let Some(&c) = chars.peek() {
1684                    if c.is_ascii_digit() {
1685                        chars.next();
1686                        num_end += 1;
1687                    } else {
1688                        break;
1689                    }
1690                }
1691            }
1692
1693            if num_end == 0 || (num_end == 1 && s.starts_with('-')) {
1694                return None; // No number found
1695            }
1696
1697            let value = &s[..num_end];
1698            let rest = s[num_end..].trim();
1699
1700            // Rest should be alphabetic (the unit)
1701            if rest.is_empty() || !rest.chars().all(|c| c.is_ascii_alphabetic()) {
1702                return None;
1703            }
1704
1705            Some((value, rest))
1706        }
1707
1708        // Check if the interval value is a string literal with embedded value+unit
1709        if let Some(Expression::Literal(ref lit)) = interval.this {
1710            if let Literal::String(ref s) = lit.as_ref() {
1711                if let Some((value, unit)) = parse_interval_string(s) {
1712                    let expanded = expand_unit(unit);
1713                    if !expanded.is_empty() {
1714                        // Construct new string with expanded unit
1715                        let new_value = format!("{} {}", value, expanded);
1716
1717                        return Ok(Expression::Interval(Box::new(Interval {
1718                            this: Some(Expression::Literal(Box::new(Literal::String(new_value)))),
1719                            unit: None, // Unit is now part of the string (SINGLE_STRING_INTERVAL style)
1720                        })));
1721                    }
1722                }
1723            }
1724        }
1725
1726        // No transformation needed
1727        Ok(Expression::Interval(Box::new(interval)))
1728    }
1729
1730    fn transform_function(&self, f: Function) -> Result<Expression> {
1731        // First, recursively transform all function arguments
1732        let transformed_args: Vec<Expression> = f
1733            .args
1734            .into_iter()
1735            .map(|arg| self.transform_expr(arg))
1736            .collect::<Result<Vec<_>>>()?;
1737
1738        let f = Function {
1739            name: f.name,
1740            args: transformed_args,
1741            distinct: f.distinct,
1742            trailing_comments: f.trailing_comments,
1743            use_bracket_syntax: f.use_bracket_syntax,
1744            no_parens: f.no_parens,
1745            quoted: f.quoted,
1746            span: None,
1747            inferred_type: None,
1748        };
1749
1750        let name_upper = f.name.to_uppercase();
1751        match name_upper.as_str() {
1752            // IFNULL -> COALESCE (standardize to COALESCE)
1753            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1754                original_name: None,
1755                expressions: f.args,
1756                inferred_type: None,
1757            }))),
1758
1759            // NVL -> COALESCE (both work in Snowflake, but COALESCE is standard per SQLGlot)
1760            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1761                original_name: None,
1762                expressions: f.args,
1763                inferred_type: None,
1764            }))),
1765
1766            // NVL2 is native to Snowflake
1767            "NVL2" => Ok(Expression::Function(Box::new(f))),
1768
1769            // GROUP_CONCAT -> LISTAGG in Snowflake
1770            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1771                Function::new("LISTAGG".to_string(), f.args),
1772            ))),
1773
1774            // STRING_AGG -> LISTAGG in Snowflake
1775            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1776                Function::new("LISTAGG".to_string(), f.args),
1777            ))),
1778
1779            // SUBSTR -> SUBSTRING (both work in Snowflake)
1780            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
1781                "SUBSTRING".to_string(),
1782                f.args,
1783            )))),
1784
1785            // UNNEST -> FLATTEN
1786            "UNNEST" => Ok(Expression::Function(Box::new(Function::new(
1787                "FLATTEN".to_string(),
1788                f.args,
1789            )))),
1790
1791            // EXPLODE -> FLATTEN
1792            "EXPLODE" => Ok(Expression::Function(Box::new(Function::new(
1793                "FLATTEN".to_string(),
1794                f.args,
1795            )))),
1796
1797            // CURRENT_DATE is native
1798            "CURRENT_DATE" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
1799
1800            // NOW -> CURRENT_TIMESTAMP (preserving parens style)
1801            "NOW" => Ok(Expression::Function(Box::new(Function {
1802                name: "CURRENT_TIMESTAMP".to_string(),
1803                args: f.args,
1804                distinct: false,
1805                trailing_comments: Vec::new(),
1806                use_bracket_syntax: false,
1807                no_parens: f.no_parens,
1808                quoted: false,
1809                span: None,
1810                inferred_type: None,
1811            }))),
1812
1813            // GETDATE -> CURRENT_TIMESTAMP (preserving parens style)
1814            "GETDATE" => Ok(Expression::Function(Box::new(Function {
1815                name: "CURRENT_TIMESTAMP".to_string(),
1816                args: f.args,
1817                distinct: false,
1818                trailing_comments: Vec::new(),
1819                use_bracket_syntax: false,
1820                no_parens: f.no_parens,
1821                quoted: false,
1822                span: None,
1823                inferred_type: None,
1824            }))),
1825
1826            // CURRENT_TIMESTAMP - always output with parens in Snowflake
1827            // Note: LOCALTIMESTAMP converts to CURRENT_TIMESTAMP without parens,
1828            // but explicit CURRENT_TIMESTAMP calls should have parens
1829            "CURRENT_TIMESTAMP" if f.args.is_empty() => {
1830                Ok(Expression::Function(Box::new(Function {
1831                    name: "CURRENT_TIMESTAMP".to_string(),
1832                    args: Vec::new(),
1833                    distinct: false,
1834                    trailing_comments: Vec::new(),
1835                    use_bracket_syntax: false,
1836                    no_parens: false, // Always output with parens
1837                    quoted: false,
1838                    span: None,
1839                    inferred_type: None,
1840                })))
1841            }
1842
1843            // TO_DATE with single string arg that looks like a date -> CAST(arg AS DATE)
1844            // Per Python SQLGlot: TO_DATE('2013-04-05') -> CAST('2013-04-05' AS DATE)
1845            // But TO_DATE('12345') stays as is (doesn't look like a date)
1846            "TO_DATE" => {
1847                if f.args.len() == 1 {
1848                    if let Expression::Literal(lit) = &f.args[0] {
1849                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
1850                            // Check if the string looks like a date (contains dashes like 2013-04-05)
1851                            if s.contains('-') && s.len() >= 8 && s.len() <= 12 {
1852                                return Ok(Expression::Cast(Box::new(Cast {
1853                                    this: f.args.into_iter().next().unwrap(),
1854                                    to: crate::expressions::DataType::Date,
1855                                    double_colon_syntax: false,
1856                                    trailing_comments: Vec::new(),
1857                                    format: None,
1858                                    default: None,
1859                                    inferred_type: None,
1860                                })));
1861                            }
1862                        }
1863                    }
1864                }
1865                // Normalize format string (2nd arg) if present
1866                let mut args = f.args;
1867                if args.len() >= 2 {
1868                    args[1] = Self::normalize_format_arg(args[1].clone());
1869                }
1870                Ok(Expression::Function(Box::new(Function::new(
1871                    "TO_DATE".to_string(),
1872                    args,
1873                ))))
1874            }
1875
1876            // TO_TIME with single string arg -> CAST(arg AS TIME)
1877            "TO_TIME" => {
1878                if f.args.len() == 1 {
1879                    if let Expression::Literal(lit) = &f.args[0] {
1880                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
1881                            return Ok(Expression::Cast(Box::new(Cast {
1882                                this: f.args.into_iter().next().unwrap(),
1883                                to: crate::expressions::DataType::Time {
1884                                    precision: None,
1885                                    timezone: false,
1886                                },
1887                                double_colon_syntax: false,
1888                                trailing_comments: Vec::new(),
1889                                format: None,
1890                                default: None,
1891                                inferred_type: None,
1892                            })));
1893                        }
1894                    }
1895                }
1896                // Normalize format string (2nd arg) if present
1897                let mut args = f.args;
1898                if args.len() >= 2 {
1899                    args[1] = Self::normalize_format_arg(args[1].clone());
1900                }
1901                Ok(Expression::Function(Box::new(Function::new(
1902                    "TO_TIME".to_string(),
1903                    args,
1904                ))))
1905            }
1906
1907            // TO_TIMESTAMP: Snowflake has multiple forms:
1908            // 1. TO_TIMESTAMP('datetime_string') -> CAST('...' AS TIMESTAMP)
1909            // 2. TO_TIMESTAMP('epoch_string') -> UnixToTime(epoch_string)
1910            // 3. TO_TIMESTAMP(number) -> UnixToTime(number)
1911            // 4. TO_TIMESTAMP(number, scale) where scale is int -> UnixToTime(number, scale)
1912            // 5. TO_TIMESTAMP(string, format) where format is string -> StrToTime(string, format)
1913            "TO_TIMESTAMP" => {
1914                let args = f.args;
1915                if args.len() == 1 {
1916                    let arg = &args[0];
1917                    match arg {
1918                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if Self::looks_like_datetime(s)) =>
1919                        {
1920                            let Literal::String(_) = lit.as_ref() else {
1921                                unreachable!()
1922                            };
1923                            // Case 1: datetime string -> CAST AS TIMESTAMP
1924                            return Ok(Expression::Cast(Box::new(Cast {
1925                                this: args.into_iter().next().unwrap(),
1926                                to: DataType::Timestamp {
1927                                    precision: None,
1928                                    timezone: false,
1929                                },
1930                                double_colon_syntax: false,
1931                                trailing_comments: vec![],
1932                                format: None,
1933                                default: None,
1934                                inferred_type: None,
1935                            })));
1936                        }
1937                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if Self::looks_like_epoch(s)) =>
1938                        {
1939                            let Literal::String(_) = lit.as_ref() else {
1940                                unreachable!()
1941                            };
1942                            // Case 2: epoch number as string -> UnixToTime
1943                            return Ok(Expression::UnixToTime(Box::new(
1944                                crate::expressions::UnixToTime {
1945                                    this: Box::new(args.into_iter().next().unwrap()),
1946                                    scale: None,
1947                                    zone: None,
1948                                    hours: None,
1949                                    minutes: None,
1950                                    format: None,
1951                                    target_type: None,
1952                                },
1953                            )));
1954                        }
1955                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1956                            // Case 3: number -> UnixToTime
1957                            return Ok(Expression::UnixToTime(Box::new(
1958                                crate::expressions::UnixToTime {
1959                                    this: Box::new(args.into_iter().next().unwrap()),
1960                                    scale: None,
1961                                    zone: None,
1962                                    hours: None,
1963                                    minutes: None,
1964                                    format: None,
1965                                    target_type: None,
1966                                },
1967                            )));
1968                        }
1969                        Expression::Neg(_) => {
1970                            // Case 3: number -> UnixToTime
1971                            return Ok(Expression::UnixToTime(Box::new(
1972                                crate::expressions::UnixToTime {
1973                                    this: Box::new(args.into_iter().next().unwrap()),
1974                                    scale: None,
1975                                    zone: None,
1976                                    hours: None,
1977                                    minutes: None,
1978                                    format: None,
1979                                    target_type: None,
1980                                },
1981                            )));
1982                        }
1983                        _ => {
1984                            // Unknown single arg, keep as function
1985                            return Ok(Expression::Function(Box::new(Function::new(
1986                                "TO_TIMESTAMP".to_string(),
1987                                args,
1988                            ))));
1989                        }
1990                    }
1991                } else if args.len() == 2 {
1992                    let second_arg = &args[1];
1993                    // Check if second arg is an integer (scale) or a format string
1994                    let is_int_scale = match second_arg {
1995                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1996                            let Literal::Number(n) = lit.as_ref() else {
1997                                unreachable!()
1998                            };
1999                            n.parse::<i64>().is_ok()
2000                        }
2001                        _ => false,
2002                    };
2003
2004                    if is_int_scale {
2005                        // Case 4: TO_TIMESTAMP(number, scale) -> UnixToTime
2006                        let mut args_iter = args.into_iter();
2007                        let value = args_iter.next().unwrap();
2008                        let scale_expr = args_iter.next().unwrap();
2009                        let scale = if let Expression::Literal(lit) = &scale_expr {
2010                            if let Literal::Number(n) = lit.as_ref() {
2011                                n.parse::<i64>().ok()
2012                            } else {
2013                                None
2014                            }
2015                        } else {
2016                            None
2017                        };
2018                        return Ok(Expression::UnixToTime(Box::new(
2019                            crate::expressions::UnixToTime {
2020                                this: Box::new(value),
2021                                scale,
2022                                zone: None,
2023                                hours: None,
2024                                minutes: None,
2025                                format: None,
2026                                target_type: None,
2027                            },
2028                        )));
2029                    } else {
2030                        // Case 5: TO_TIMESTAMP(string, format) -> StrToTime
2031                        let mut args_iter = args.into_iter();
2032                        let value = args_iter.next().unwrap();
2033                        let format_expr = args_iter.next().unwrap();
2034                        let format_str = match &format_expr {
2035                            Expression::Literal(lit)
2036                                if matches!(lit.as_ref(), Literal::String(_)) =>
2037                            {
2038                                let Literal::String(s) = lit.as_ref() else {
2039                                    unreachable!()
2040                                };
2041                                s.clone()
2042                            }
2043                            _ => {
2044                                // Non-string format, keep as function
2045                                return Ok(Expression::Function(Box::new(Function::new(
2046                                    "TO_TIMESTAMP".to_string(),
2047                                    vec![value, format_expr],
2048                                ))));
2049                            }
2050                        };
2051                        // Normalize Snowflake format to target-neutral
2052                        let normalized_format = Self::normalize_snowflake_format(&format_str);
2053                        return Ok(Expression::StrToTime(Box::new(
2054                            crate::expressions::StrToTime {
2055                                this: Box::new(value),
2056                                format: normalized_format,
2057                                zone: None,
2058                                safe: None,
2059                                target_type: None,
2060                            },
2061                        )));
2062                    }
2063                }
2064                // More than 2 args or other cases, keep as function
2065                Ok(Expression::Function(Box::new(Function::new(
2066                    "TO_TIMESTAMP".to_string(),
2067                    args,
2068                ))))
2069            }
2070
2071            // TO_CHAR is native to Snowflake
2072            "TO_CHAR" => Ok(Expression::Function(Box::new(f))),
2073
2074            // ROUND with named args: ROUND(EXPR => x, SCALE => y, ROUNDING_MODE => z)
2075            // -> ROUND(x, y) or ROUND(x, y, z)
2076            "ROUND"
2077                if f.args
2078                    .iter()
2079                    .any(|a| matches!(a, Expression::NamedArgument(_))) =>
2080            {
2081                let mut expr_val = None;
2082                let mut scale_val = None;
2083                let mut rounding_mode_val = None;
2084                for arg in &f.args {
2085                    if let Expression::NamedArgument(na) = arg {
2086                        match na.name.name.to_uppercase().as_str() {
2087                            "EXPR" => expr_val = Some(na.value.clone()),
2088                            "SCALE" => scale_val = Some(na.value.clone()),
2089                            "ROUNDING_MODE" => rounding_mode_val = Some(na.value.clone()),
2090                            _ => {}
2091                        }
2092                    }
2093                }
2094                if let Some(expr) = expr_val {
2095                    let mut args = vec![expr];
2096                    if let Some(scale) = scale_val {
2097                        args.push(scale);
2098                    }
2099                    if let Some(mode) = rounding_mode_val {
2100                        args.push(mode);
2101                    }
2102                    Ok(Expression::Function(Box::new(Function::new(
2103                        "ROUND".to_string(),
2104                        args,
2105                    ))))
2106                } else {
2107                    Ok(Expression::Function(Box::new(f)))
2108                }
2109            }
2110
2111            // DATE_FORMAT -> TO_CHAR in Snowflake
2112            // Also converts strftime format to Snowflake format and wraps first arg in CAST AS TIMESTAMP
2113            "DATE_FORMAT" => {
2114                let mut args = f.args;
2115                // Wrap first arg in CAST AS TIMESTAMP if it's a string literal
2116                if !args.is_empty() {
2117                    if matches!(&args[0], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
2118                    {
2119                        args[0] = Expression::Cast(Box::new(crate::expressions::Cast {
2120                            this: args[0].clone(),
2121                            to: DataType::Timestamp {
2122                                precision: None,
2123                                timezone: false,
2124                            },
2125                            trailing_comments: Vec::new(),
2126                            double_colon_syntax: false,
2127                            format: None,
2128                            default: None,
2129                            inferred_type: None,
2130                        }));
2131                    }
2132                }
2133                // Convert strftime format to Snowflake format
2134                if args.len() >= 2 {
2135                    if let Expression::Literal(ref lit) = args[1] {
2136                        if let Literal::String(ref fmt) = lit.as_ref() {
2137                            let sf_fmt = strftime_to_snowflake_format(fmt);
2138                            args[1] = Expression::Literal(Box::new(Literal::String(sf_fmt)));
2139                        }
2140                    }
2141                }
2142                Ok(Expression::Function(Box::new(Function::new(
2143                    "TO_CHAR".to_string(),
2144                    args,
2145                ))))
2146            }
2147
2148            // ARRAY -> ARRAY_CONSTRUCT
2149            "ARRAY" => Ok(Expression::Function(Box::new(Function::new(
2150                "ARRAY_CONSTRUCT".to_string(),
2151                f.args,
2152            )))),
2153
2154            // STRUCT -> OBJECT_CONSTRUCT
2155            // Convert STRUCT(value AS name, ...) to OBJECT_CONSTRUCT('name', value, ...)
2156            "STRUCT" => {
2157                let mut oc_args = Vec::new();
2158                for arg in f.args {
2159                    match arg {
2160                        Expression::Alias(a) => {
2161                            // Named field: value AS name -> 'name', value
2162                            oc_args.push(Expression::Literal(Box::new(
2163                                crate::expressions::Literal::String(a.alias.name.clone()),
2164                            )));
2165                            oc_args.push(a.this);
2166                        }
2167                        other => {
2168                            // Unnamed field: just pass through
2169                            oc_args.push(other);
2170                        }
2171                    }
2172                }
2173                Ok(Expression::Function(Box::new(Function::new(
2174                    "OBJECT_CONSTRUCT".to_string(),
2175                    oc_args,
2176                ))))
2177            }
2178
2179            // JSON_EXTRACT -> GET_PATH or GET in Snowflake
2180            "JSON_EXTRACT" => Ok(Expression::Function(Box::new(Function::new(
2181                "GET_PATH".to_string(),
2182                f.args,
2183            )))),
2184
2185            // JSON_EXTRACT_SCALAR -> JSON_EXTRACT_PATH_TEXT
2186            "JSON_EXTRACT_SCALAR" => Ok(Expression::Function(Box::new(Function::new(
2187                "JSON_EXTRACT_PATH_TEXT".to_string(),
2188                f.args,
2189            )))),
2190
2191            // LEN -> LENGTH
2192            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
2193                f.args.into_iter().next().unwrap(),
2194            )))),
2195
2196            // CEILING -> CEIL (both work)
2197            "CEILING" if f.args.len() == 1 => Ok(Expression::Ceil(Box::new(CeilFunc {
2198                this: f.args.into_iter().next().unwrap(),
2199                decimals: None,
2200                to: None,
2201            }))),
2202
2203            // CHARINDEX -> POSITION or CHARINDEX (native)
2204            "CHARINDEX" => Ok(Expression::Function(Box::new(f))),
2205
2206            // SPLIT is native to Snowflake - keep as-is
2207            "SPLIT" => Ok(Expression::Function(Box::new(f))),
2208
2209            // ARRAY_AGG is native to Snowflake
2210            "ARRAY_AGG" => Ok(Expression::Function(Box::new(f))),
2211
2212            // PARSE_JSON for JSON parsing
2213            "JSON_PARSE" | "PARSE_JSON" => Ok(Expression::Function(Box::new(Function::new(
2214                "PARSE_JSON".to_string(),
2215                f.args,
2216            )))),
2217
2218            // RAND -> Rand (to use RANDOM in Snowflake)
2219            "RAND" => {
2220                let seed = f.args.first().cloned().map(Box::new);
2221                Ok(Expression::Rand(Box::new(crate::expressions::Rand {
2222                    seed,
2223                    lower: None,
2224                    upper: None,
2225                })))
2226            }
2227
2228            // SHA -> SHA1
2229            "SHA" => Ok(Expression::Function(Box::new(Function::new(
2230                "SHA1".to_string(),
2231                f.args,
2232            )))),
2233
2234            // APPROX_COUNT_DISTINCT is native
2235            "APPROX_DISTINCT" => Ok(Expression::Function(Box::new(Function::new(
2236                "APPROX_COUNT_DISTINCT".to_string(),
2237                f.args,
2238            )))),
2239
2240            // GEN_RANDOM_UUID/UUID -> Uuid AST node
2241            "GEN_RANDOM_UUID" | "UUID" => {
2242                Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2243                    this: None,
2244                    name: None,
2245                    is_string: None,
2246                })))
2247            }
2248
2249            // NEWID -> Uuid AST node
2250            "NEWID" => Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2251                this: None,
2252                name: None,
2253                is_string: None,
2254            }))),
2255
2256            // UUID_STRING -> Uuid AST node (without args only; with args keep as Function for identity)
2257            "UUID_STRING" => {
2258                if f.args.is_empty() {
2259                    Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2260                        this: None,
2261                        name: None,
2262                        is_string: None,
2263                    })))
2264                } else {
2265                    Ok(Expression::Function(Box::new(Function::new(
2266                        "UUID_STRING".to_string(),
2267                        f.args,
2268                    ))))
2269                }
2270            }
2271
2272            // IF -> IFF (convert to IfFunc AST node)
2273            "IF" if f.args.len() >= 2 => {
2274                let mut args = f.args;
2275                let condition = args.remove(0);
2276                let true_val = args.remove(0);
2277                let false_val = if !args.is_empty() {
2278                    Some(args.remove(0))
2279                } else {
2280                    None
2281                };
2282                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
2283                    condition,
2284                    true_value: true_val,
2285                    false_value: Some(
2286                        false_val.unwrap_or(Expression::Null(crate::expressions::Null)),
2287                    ),
2288                    original_name: Some("IFF".to_string()),
2289                    inferred_type: None,
2290                })))
2291            }
2292
2293            // SQUARE(x) -> POWER(x, 2)
2294            "SQUARE" if f.args.len() == 1 => {
2295                let x = f.args.into_iter().next().unwrap();
2296                Ok(Expression::Power(Box::new(
2297                    crate::expressions::BinaryFunc {
2298                        original_name: None,
2299                        this: x,
2300                        expression: Expression::number(2),
2301                        inferred_type: None,
2302                    },
2303                )))
2304            }
2305
2306            // POW(x, y) -> POWER(x, y)
2307            "POW" if f.args.len() == 2 => {
2308                let mut args = f.args.into_iter();
2309                let x = args.next().unwrap();
2310                let y = args.next().unwrap();
2311                Ok(Expression::Power(Box::new(
2312                    crate::expressions::BinaryFunc {
2313                        original_name: None,
2314                        this: x,
2315                        expression: y,
2316                        inferred_type: None,
2317                    },
2318                )))
2319            }
2320
2321            // MOD(x, y) -> x % y (modulo operator)
2322            "MOD" if f.args.len() == 2 => {
2323                let mut args = f.args.into_iter();
2324                let x = args.next().unwrap();
2325                let y = args.next().unwrap();
2326                Ok(Expression::Mod(Box::new(crate::expressions::BinaryOp {
2327                    left: x,
2328                    right: y,
2329                    left_comments: Vec::new(),
2330                    operator_comments: Vec::new(),
2331                    trailing_comments: Vec::new(),
2332                    inferred_type: None,
2333                })))
2334            }
2335
2336            // APPROXIMATE_JACCARD_INDEX -> APPROXIMATE_SIMILARITY
2337            "APPROXIMATE_JACCARD_INDEX" => Ok(Expression::Function(Box::new(Function::new(
2338                "APPROXIMATE_SIMILARITY".to_string(),
2339                f.args,
2340            )))),
2341
2342            // ARRAY_CONSTRUCT -> Array with bracket notation in Snowflake
2343            "ARRAY_CONSTRUCT" => Ok(Expression::ArrayFunc(Box::new(
2344                crate::expressions::ArrayConstructor {
2345                    expressions: f.args,
2346                    bracket_notation: true,
2347                    use_list_keyword: false,
2348                },
2349            ))),
2350
2351            // APPROX_TOP_K - add default k=1 if not provided
2352            "APPROX_TOP_K" if f.args.len() == 1 => {
2353                let mut args = f.args;
2354                args.push(Expression::number(1));
2355                Ok(Expression::Function(Box::new(Function::new(
2356                    "APPROX_TOP_K".to_string(),
2357                    args,
2358                ))))
2359            }
2360
2361            // TO_DECIMAL, TO_NUMERIC -> TO_NUMBER
2362            "TO_DECIMAL" | "TO_NUMERIC" => Ok(Expression::Function(Box::new(Function::new(
2363                "TO_NUMBER".to_string(),
2364                f.args,
2365            )))),
2366
2367            // TRY_TO_DECIMAL, TRY_TO_NUMERIC -> TRY_TO_NUMBER
2368            "TRY_TO_DECIMAL" | "TRY_TO_NUMERIC" => Ok(Expression::Function(Box::new(
2369                Function::new("TRY_TO_NUMBER".to_string(), f.args),
2370            ))),
2371
2372            // STDDEV_SAMP -> STDDEV
2373            "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
2374                "STDDEV".to_string(),
2375                f.args,
2376            )))),
2377
2378            // STRTOK -> SPLIT_PART (with default delimiter and position)
2379            "STRTOK" if f.args.len() >= 1 => {
2380                let mut args = f.args;
2381                // Add default delimiter (space) if missing
2382                if args.len() == 1 {
2383                    args.push(Expression::string(" ".to_string()));
2384                }
2385                // Add default position (1) if missing
2386                if args.len() == 2 {
2387                    args.push(Expression::number(1));
2388                }
2389                Ok(Expression::Function(Box::new(Function::new(
2390                    "STRTOK".to_string(),
2391                    args,
2392                ))))
2393            }
2394
2395            // WEEKOFYEAR -> WEEK
2396            "WEEKOFYEAR" => Ok(Expression::Function(Box::new(Function::new(
2397                "WEEK".to_string(),
2398                f.args,
2399            )))),
2400
2401            // LIKE(col, pattern, escape) -> col LIKE pattern ESCAPE escape
2402            "LIKE" if f.args.len() >= 2 => {
2403                let mut args = f.args.into_iter();
2404                let left = args.next().unwrap();
2405                let right = args.next().unwrap();
2406                let escape = args.next();
2407                Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
2408                    left,
2409                    right,
2410                    escape,
2411                    quantifier: None,
2412                    inferred_type: None,
2413                })))
2414            }
2415
2416            // ILIKE(col, pattern, escape) -> col ILIKE pattern ESCAPE escape
2417            "ILIKE" if f.args.len() >= 2 => {
2418                let mut args = f.args.into_iter();
2419                let left = args.next().unwrap();
2420                let right = args.next().unwrap();
2421                let escape = args.next();
2422                Ok(Expression::ILike(Box::new(crate::expressions::LikeOp {
2423                    left,
2424                    right,
2425                    escape,
2426                    quantifier: None,
2427                    inferred_type: None,
2428                })))
2429            }
2430
2431            // RLIKE -> REGEXP_LIKE
2432            "RLIKE" if f.args.len() >= 2 => {
2433                let mut args = f.args.into_iter();
2434                let left = args.next().unwrap();
2435                let pattern = args.next().unwrap();
2436                let flags = args.next();
2437                Ok(Expression::RegexpLike(Box::new(
2438                    crate::expressions::RegexpFunc {
2439                        this: left,
2440                        pattern,
2441                        flags,
2442                    },
2443                )))
2444            }
2445
2446            // IFF -> convert to IfFunc AST node for proper cross-dialect handling
2447            "IFF" if f.args.len() >= 2 => {
2448                let mut args = f.args;
2449                let condition = args.remove(0);
2450                let true_value = args.remove(0);
2451                let false_value = if !args.is_empty() {
2452                    Some(args.remove(0))
2453                } else {
2454                    None
2455                };
2456                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
2457                    condition,
2458                    true_value,
2459                    false_value,
2460                    original_name: Some("IFF".to_string()),
2461                    inferred_type: None,
2462                })))
2463            }
2464
2465            // TIMESTAMP_NTZ_FROM_PARTS, TIMESTAMPFROMPARTS, TIMESTAMPNTZFROMPARTS -> TIMESTAMP_FROM_PARTS
2466            "TIMESTAMP_NTZ_FROM_PARTS" | "TIMESTAMPFROMPARTS" | "TIMESTAMPNTZFROMPARTS" => {
2467                Ok(Expression::Function(Box::new(Function::new(
2468                    "TIMESTAMP_FROM_PARTS".to_string(),
2469                    f.args,
2470                ))))
2471            }
2472
2473            // TIMESTAMPLTZFROMPARTS -> TIMESTAMP_LTZ_FROM_PARTS
2474            "TIMESTAMPLTZFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2475                "TIMESTAMP_LTZ_FROM_PARTS".to_string(),
2476                f.args,
2477            )))),
2478
2479            // TIMESTAMPTZFROMPARTS -> TIMESTAMP_TZ_FROM_PARTS
2480            "TIMESTAMPTZFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2481                "TIMESTAMP_TZ_FROM_PARTS".to_string(),
2482                f.args,
2483            )))),
2484
2485            // DATEADD with 3 args - transform the unit (first arg) using date part mapping
2486            "DATEADD" if f.args.len() >= 1 => {
2487                let mut args = f.args;
2488                args[0] = self.transform_date_part_arg(args[0].clone());
2489                Ok(Expression::Function(Box::new(Function::new(
2490                    "DATEADD".to_string(),
2491                    args,
2492                ))))
2493            }
2494
2495            // DATEDIFF with 3 args - transform the unit (first arg) using date part mapping
2496            // Also convert _POLYGLOT_TO_DATE back to TO_DATE (from cross-dialect normalize)
2497            "DATEDIFF" if f.args.len() >= 1 => {
2498                let mut args = f.args;
2499                args[0] = self.transform_date_part_arg(args[0].clone());
2500                // Convert _POLYGLOT_TO_DATE back to TO_DATE for date args
2501                // (_POLYGLOT_TO_DATE is an internal marker from cross-dialect normalize)
2502                for i in 1..args.len() {
2503                    if let Expression::Function(ref func) = args[i] {
2504                        if func.name == "_POLYGLOT_TO_DATE" {
2505                            let inner_args = func.args.clone();
2506                            args[i] = Expression::Function(Box::new(Function::new(
2507                                "TO_DATE".to_string(),
2508                                inner_args,
2509                            )));
2510                        }
2511                    }
2512                }
2513                Ok(Expression::Function(Box::new(Function::new(
2514                    "DATEDIFF".to_string(),
2515                    args,
2516                ))))
2517            }
2518
2519            // TIMEDIFF -> DATEDIFF
2520            "TIMEDIFF" => Ok(Expression::Function(Box::new(Function::new(
2521                "DATEDIFF".to_string(),
2522                f.args,
2523            )))),
2524
2525            // TIMESTAMPDIFF -> DATEDIFF
2526            "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(Function::new(
2527                "DATEDIFF".to_string(),
2528                f.args,
2529            )))),
2530
2531            // TIMESTAMPADD -> DATEADD
2532            "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
2533                "DATEADD".to_string(),
2534                f.args,
2535            )))),
2536
2537            // TIMEADD -> preserve it
2538            "TIMEADD" => Ok(Expression::Function(Box::new(f))),
2539
2540            // DATE_FROM_PARTS, DATEFROMPARTS -> DATE_FROM_PARTS
2541            "DATEFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2542                "DATE_FROM_PARTS".to_string(),
2543                f.args,
2544            )))),
2545
2546            // TIME_FROM_PARTS, TIMEFROMPARTS -> TIME_FROM_PARTS
2547            "TIMEFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2548                "TIME_FROM_PARTS".to_string(),
2549                f.args,
2550            )))),
2551
2552            // DAYOFWEEK -> DAYOFWEEK (preserve)
2553            "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
2554
2555            // DAYOFMONTH -> DAYOFMONTH (preserve)
2556            "DAYOFMONTH" => Ok(Expression::Function(Box::new(f))),
2557
2558            // DAYOFYEAR -> DAYOFYEAR (preserve)
2559            "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
2560
2561            // MONTHNAME -> Monthname AST node (abbreviated=true for Snowflake)
2562            // Target dialects can then convert to their native form
2563            "MONTHNAME" if f.args.len() == 1 => {
2564                let arg = f.args.into_iter().next().unwrap();
2565                Ok(Expression::Monthname(Box::new(
2566                    crate::expressions::Monthname {
2567                        this: Box::new(arg),
2568                        abbreviated: Some(Box::new(Expression::Literal(Box::new(
2569                            Literal::String("true".to_string()),
2570                        )))),
2571                    },
2572                )))
2573            }
2574
2575            // DAYNAME -> Dayname AST node (abbreviated=true for Snowflake)
2576            // Target dialects can then convert to their native form
2577            "DAYNAME" if f.args.len() == 1 => {
2578                let arg = f.args.into_iter().next().unwrap();
2579                Ok(Expression::Dayname(Box::new(crate::expressions::Dayname {
2580                    this: Box::new(arg),
2581                    abbreviated: Some(Box::new(Expression::Literal(Box::new(Literal::String(
2582                        "true".to_string(),
2583                    ))))),
2584                })))
2585            }
2586
2587            // BOOLAND_AGG/BOOL_AND/LOGICAL_AND -> LogicalAnd AST node
2588            "BOOLAND_AGG" | "BOOL_AND" | "LOGICAL_AND" if !f.args.is_empty() => {
2589                let arg = f.args.into_iter().next().unwrap();
2590                Ok(Expression::LogicalAnd(Box::new(AggFunc {
2591                    this: arg,
2592                    distinct: false,
2593                    filter: None,
2594                    order_by: Vec::new(),
2595                    name: Some("BOOLAND_AGG".to_string()),
2596                    ignore_nulls: None,
2597                    having_max: None,
2598                    limit: None,
2599                    inferred_type: None,
2600                })))
2601            }
2602
2603            // BOOLOR_AGG/BOOL_OR/LOGICAL_OR -> LogicalOr AST node
2604            "BOOLOR_AGG" | "BOOL_OR" | "LOGICAL_OR" if !f.args.is_empty() => {
2605                let arg = f.args.into_iter().next().unwrap();
2606                Ok(Expression::LogicalOr(Box::new(AggFunc {
2607                    this: arg,
2608                    distinct: false,
2609                    filter: None,
2610                    order_by: Vec::new(),
2611                    name: Some("BOOLOR_AGG".to_string()),
2612                    ignore_nulls: None,
2613                    having_max: None,
2614                    limit: None,
2615                    inferred_type: None,
2616                })))
2617            }
2618
2619            // SKEW -> Skewness AST node for proper cross-dialect handling
2620            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
2621                let arg = f.args.into_iter().next().unwrap();
2622                Ok(Expression::Skewness(Box::new(AggFunc {
2623                    this: arg,
2624                    distinct: false,
2625                    filter: None,
2626                    order_by: Vec::new(),
2627                    name: Some("SKEW".to_string()),
2628                    ignore_nulls: None,
2629                    having_max: None,
2630                    limit: None,
2631                    inferred_type: None,
2632                })))
2633            }
2634
2635            // VAR_SAMP -> VARIANCE (Snowflake uses VARIANCE for sample variance)
2636            "VAR_SAMP" => Ok(Expression::Function(Box::new(Function::new(
2637                "VARIANCE".to_string(),
2638                f.args,
2639            )))),
2640
2641            // VAR_POP -> VARIANCE_POP
2642            "VAR_POP" => Ok(Expression::Function(Box::new(Function::new(
2643                "VARIANCE_POP".to_string(),
2644                f.args,
2645            )))),
2646
2647            // DATE(str) -> TO_DATE(str) (single-arg form)
2648            "DATE" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2649                "TO_DATE".to_string(),
2650                f.args,
2651            )))),
2652            // DATE(str, format) -> TO_DATE(str, normalized_format)
2653            // Python SQLGlot normalizes DATE(...) to TO_DATE(...) for formatted variants.
2654            // But _POLYGLOT_DATE(str, format) stays as DATE() (from BigQuery PARSE_DATE conversion)
2655            "DATE" if f.args.len() >= 2 => {
2656                let mut args = f.args;
2657                args[1] = Self::normalize_format_arg(args[1].clone());
2658                Ok(Expression::Function(Box::new(Function::new(
2659                    "TO_DATE".to_string(),
2660                    args,
2661                ))))
2662            }
2663            // Internal marker from BigQuery PARSE_DATE -> Snowflake conversion
2664            // _POLYGLOT_DATE stays as DATE() (not converted to TO_DATE)
2665            "_POLYGLOT_DATE" if f.args.len() >= 2 => {
2666                let mut args = f.args;
2667                args[1] = Self::normalize_format_arg(args[1].clone());
2668                Ok(Expression::Function(Box::new(Function::new(
2669                    "DATE".to_string(),
2670                    args,
2671                ))))
2672            }
2673
2674            // DESCRIBE/DESC normalization
2675            "DESCRIBE" => Ok(Expression::Function(Box::new(f))),
2676
2677            // MD5 -> MD5 (preserve) but MD5_HEX -> MD5
2678            "MD5_HEX" => Ok(Expression::Function(Box::new(Function::new(
2679                "MD5".to_string(),
2680                f.args,
2681            )))),
2682
2683            // SHA1_HEX -> SHA1
2684            "SHA1_HEX" => Ok(Expression::Function(Box::new(Function::new(
2685                "SHA1".to_string(),
2686                f.args,
2687            )))),
2688
2689            // SHA2_HEX -> SHA2
2690            "SHA2_HEX" => Ok(Expression::Function(Box::new(Function::new(
2691                "SHA2".to_string(),
2692                f.args,
2693            )))),
2694
2695            // EDITDISTANCE -> EDITDISTANCE (preserve Snowflake name)
2696            "LEVENSHTEIN" => Ok(Expression::Function(Box::new(Function::new(
2697                "EDITDISTANCE".to_string(),
2698                f.args,
2699            )))),
2700
2701            // BIT_NOT -> BITNOT
2702            "BIT_NOT" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2703                "BITNOT".to_string(),
2704                f.args,
2705            )))),
2706
2707            // BIT_AND -> BITAND
2708            "BIT_AND" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2709                "BITAND".to_string(),
2710                f.args,
2711            )))),
2712
2713            // BIT_OR -> BITOR
2714            "BIT_OR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2715                "BITOR".to_string(),
2716                f.args,
2717            )))),
2718
2719            // BIT_XOR -> BITXOR
2720            "BIT_XOR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2721                "BITXOR".to_string(),
2722                f.args,
2723            )))),
2724
2725            // BIT_SHIFTLEFT -> BITSHIFTLEFT
2726            "BIT_SHIFTLEFT" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(
2727                Function::new("BITSHIFTLEFT".to_string(), f.args),
2728            ))),
2729
2730            // BIT_SHIFTRIGHT -> BITSHIFTRIGHT
2731            "BIT_SHIFTRIGHT" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(
2732                Function::new("BITSHIFTRIGHT".to_string(), f.args),
2733            ))),
2734
2735            // SYSTIMESTAMP -> CURRENT_TIMESTAMP (preserving parens style)
2736            "SYSTIMESTAMP" => Ok(Expression::Function(Box::new(Function {
2737                name: "CURRENT_TIMESTAMP".to_string(),
2738                args: f.args,
2739                distinct: false,
2740                trailing_comments: Vec::new(),
2741                use_bracket_syntax: false,
2742                no_parens: f.no_parens,
2743                quoted: false,
2744                span: None,
2745                inferred_type: None,
2746            }))),
2747
2748            // LOCALTIMESTAMP -> CURRENT_TIMESTAMP (preserving parens style)
2749            "LOCALTIMESTAMP" => Ok(Expression::Function(Box::new(Function {
2750                name: "CURRENT_TIMESTAMP".to_string(),
2751                args: f.args,
2752                distinct: false,
2753                trailing_comments: Vec::new(),
2754                use_bracket_syntax: false,
2755                no_parens: f.no_parens,
2756                quoted: false,
2757                span: None,
2758                inferred_type: None,
2759            }))),
2760
2761            // SPACE(n) -> REPEAT(' ', n) in Snowflake
2762            "SPACE" if f.args.len() == 1 => {
2763                let arg = f.args.into_iter().next().unwrap();
2764                Ok(Expression::Function(Box::new(Function::new(
2765                    "REPEAT".to_string(),
2766                    vec![
2767                        Expression::Literal(Box::new(Literal::String(" ".to_string()))),
2768                        arg,
2769                    ],
2770                ))))
2771            }
2772
2773            // CEILING -> CEIL
2774            "CEILING" => Ok(Expression::Function(Box::new(Function::new(
2775                "CEIL".to_string(),
2776                f.args,
2777            )))),
2778
2779            // LOG without base -> LN
2780            "LOG" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2781                "LN".to_string(),
2782                f.args,
2783            )))),
2784
2785            // REGEXP_SUBSTR_ALL is native to Snowflake
2786            "REGEXP_SUBSTR_ALL" => Ok(Expression::Function(Box::new(f))),
2787
2788            // GET_PATH - transform path argument:
2789            // - Convert colon notation to dot notation (y[0]:z -> y[0].z)
2790            // - Wrap unsafe keys in brackets ($id -> ["$id"])
2791            "GET_PATH" if f.args.len() >= 2 => {
2792                let mut args = f.args;
2793                // Transform the path argument (second argument)
2794                if let Expression::Literal(lit) = &args[1] {
2795                    if let crate::expressions::Literal::String(path) = lit.as_ref() {
2796                        let transformed = Self::transform_json_path(path);
2797                        args[1] = Expression::Literal(Box::new(
2798                            crate::expressions::Literal::String(transformed),
2799                        ));
2800                    }
2801                }
2802                Ok(Expression::Function(Box::new(Function::new(
2803                    "GET_PATH".to_string(),
2804                    args,
2805                ))))
2806            }
2807            "GET_PATH" => Ok(Expression::Function(Box::new(f))),
2808
2809            // FLATTEN is native to Snowflake
2810            "FLATTEN" => Ok(Expression::Function(Box::new(f))),
2811
2812            // DATE_TRUNC - transform unit to quoted string
2813            // DATE_TRUNC(yr, x) -> DATE_TRUNC('YEAR', x)
2814            "DATE_TRUNC" if f.args.len() >= 1 => {
2815                let mut args = f.args;
2816                // Transform the unit to canonical form and convert to string literal
2817                let unit_name = match &args[0] {
2818                    Expression::Identifier(id) => Some(id.name.as_str()),
2819                    Expression::Column(col) if col.table.is_none() => Some(col.name.name.as_str()),
2820                    _ => None,
2821                };
2822                if let Some(name) = unit_name {
2823                    let canonical = Self::map_date_part(name).unwrap_or(name);
2824                    args[0] = Expression::Literal(Box::new(crate::expressions::Literal::String(
2825                        canonical.to_uppercase(),
2826                    )));
2827                }
2828                Ok(Expression::Function(Box::new(Function::new(
2829                    "DATE_TRUNC".to_string(),
2830                    args,
2831                ))))
2832            }
2833
2834            // DATE_PART - transform unit argument
2835            // DATE_PART(yyy, x) -> DATE_PART(YEAR, x)
2836            // Only convert string literals to identifiers when the second arg is a typed literal
2837            // (e.g., TIMESTAMP '...', DATE '...'), indicating the function came from another dialect.
2838            // For native Snowflake DATE_PART('month', CAST(...)), preserve the string as-is.
2839            "DATE_PART" if f.args.len() >= 1 => {
2840                let mut args = f.args;
2841                let from_typed_literal = args.len() >= 2
2842                    && matches!(
2843                        &args[1],
2844                        Expression::Literal(lit) if matches!(lit.as_ref(),
2845                            crate::expressions::Literal::Timestamp(_)
2846                            | crate::expressions::Literal::Date(_)
2847                            | crate::expressions::Literal::Time(_)
2848                            | crate::expressions::Literal::Datetime(_)
2849                        )
2850                    );
2851                if from_typed_literal {
2852                    args[0] = self.transform_date_part_arg(args[0].clone());
2853                } else {
2854                    // For non-typed-literal cases, only normalize identifiers/columns
2855                    // (don't convert string literals to identifiers)
2856                    args[0] = self.transform_date_part_arg_identifiers_only(args[0].clone());
2857                }
2858                Ok(Expression::Function(Box::new(Function::new(
2859                    "DATE_PART".to_string(),
2860                    args,
2861                ))))
2862            }
2863
2864            // OBJECT_CONSTRUCT is native to Snowflake
2865            "OBJECT_CONSTRUCT" => Ok(Expression::Function(Box::new(f))),
2866
2867            // OBJECT_CONSTRUCT_KEEP_NULL is native to Snowflake
2868            "OBJECT_CONSTRUCT_KEEP_NULL" => Ok(Expression::Function(Box::new(f))),
2869
2870            // DESC -> DESCRIBE
2871            "DESC" => Ok(Expression::Function(Box::new(Function::new(
2872                "DESCRIBE".to_string(),
2873                f.args,
2874            )))),
2875
2876            // RLIKE -> REGEXP_LIKE
2877            "RLIKE" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2878                "REGEXP_LIKE".to_string(),
2879                f.args,
2880            )))),
2881
2882            // TRANSFORM function - handle typed lambda parameters
2883            // For typed lambdas like `a int -> a + 1`, we need to:
2884            // 1. Remove the type annotation from the parameter
2885            // 2. Wrap all references to the parameter in the body with CAST(param AS type)
2886            "TRANSFORM" => {
2887                let transformed_args: Vec<Expression> = f
2888                    .args
2889                    .into_iter()
2890                    .map(|arg| {
2891                        if let Expression::Lambda(lambda) = arg {
2892                            self.transform_typed_lambda(*lambda)
2893                        } else {
2894                            arg
2895                        }
2896                    })
2897                    .collect();
2898                Ok(Expression::Function(Box::new(Function::new(
2899                    "TRANSFORM".to_string(),
2900                    transformed_args,
2901                ))))
2902            }
2903
2904            // SEARCH function - convert to Search expression with canonical parameter ordering
2905            "SEARCH" if f.args.len() >= 2 => {
2906                let mut args = f.args.into_iter();
2907                let this = Box::new(args.next().unwrap());
2908                let expression = Box::new(args.next().unwrap());
2909
2910                let mut analyzer: Option<Box<Expression>> = None;
2911                let mut search_mode: Option<Box<Expression>> = None;
2912
2913                // Parse remaining named arguments
2914                for arg in args {
2915                    if let Expression::NamedArgument(na) = &arg {
2916                        let name_upper = na.name.name.to_uppercase();
2917                        match name_upper.as_str() {
2918                            "ANALYZER" => analyzer = Some(Box::new(arg)),
2919                            "SEARCH_MODE" => search_mode = Some(Box::new(arg)),
2920                            _ => {}
2921                        }
2922                    }
2923                }
2924
2925                Ok(Expression::Search(Box::new(crate::expressions::Search {
2926                    this,
2927                    expression,
2928                    json_scope: None,
2929                    analyzer,
2930                    analyzer_options: None,
2931                    search_mode,
2932                })))
2933            }
2934
2935            // ODBC CONVERT function: CONVERT(value, SQL_TYPE) -> CAST(value AS TYPE)
2936            // This handles the { fn CONVERT(...) } ODBC escape sequence syntax
2937            "CONVERT" if f.args.len() == 2 => {
2938                let value = f.args.get(0).cloned().unwrap();
2939                let type_arg = f.args.get(1).cloned().unwrap();
2940
2941                // Check if second argument is a SQL_ type identifier
2942                if let Expression::Column(col) = &type_arg {
2943                    let type_name = col.name.name.to_uppercase();
2944                    let data_type = match type_name.as_str() {
2945                        "SQL_DOUBLE" => Some(DataType::Double {
2946                            precision: None,
2947                            scale: None,
2948                        }),
2949                        "SQL_VARCHAR" => Some(DataType::VarChar {
2950                            length: None,
2951                            parenthesized_length: false,
2952                        }),
2953                        "SQL_INTEGER" | "SQL_INT" => Some(DataType::Int {
2954                            length: None,
2955                            integer_spelling: false,
2956                        }),
2957                        "SQL_BIGINT" => Some(DataType::BigInt { length: None }),
2958                        "SQL_SMALLINT" => Some(DataType::SmallInt { length: None }),
2959                        "SQL_FLOAT" => Some(DataType::Float {
2960                            precision: None,
2961                            scale: None,
2962                            real_spelling: false,
2963                        }),
2964                        "SQL_REAL" => Some(DataType::Float {
2965                            precision: None,
2966                            scale: None,
2967                            real_spelling: true,
2968                        }),
2969                        "SQL_DECIMAL" => Some(DataType::Decimal {
2970                            precision: None,
2971                            scale: None,
2972                        }),
2973                        "SQL_DATE" => Some(DataType::Date),
2974                        "SQL_TIME" => Some(DataType::Time {
2975                            precision: None,
2976                            timezone: false,
2977                        }),
2978                        "SQL_TIMESTAMP" => Some(DataType::Timestamp {
2979                            precision: None,
2980                            timezone: false,
2981                        }),
2982                        _ => None,
2983                    };
2984
2985                    if let Some(dt) = data_type {
2986                        return Ok(Expression::Cast(Box::new(Cast {
2987                            this: value,
2988                            to: dt,
2989                            double_colon_syntax: false,
2990                            trailing_comments: vec![],
2991                            format: None,
2992                            default: None,
2993                            inferred_type: None,
2994                        })));
2995                    }
2996                }
2997                // If not a SQL_ type, keep as regular CONVERT function
2998                Ok(Expression::Function(Box::new(f)))
2999            }
3000
3001            // TO_TIMESTAMP_TZ: single string arg -> CAST(... AS TIMESTAMPTZ), otherwise keep as function
3002            // Per Python sqlglot: _build_datetime converts TO_TIMESTAMP_TZ('string') to CAST('string' AS TIMESTAMPTZ)
3003            "TO_TIMESTAMP_TZ" => {
3004                if f.args.len() == 1 {
3005                    if let Expression::Literal(lit) = &f.args[0] {
3006                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3007                            return Ok(Expression::Cast(Box::new(Cast {
3008                                this: f.args.into_iter().next().unwrap(),
3009                                to: DataType::Custom {
3010                                    name: "TIMESTAMPTZ".to_string(),
3011                                },
3012                                double_colon_syntax: false,
3013                                trailing_comments: vec![],
3014                                format: None,
3015                                default: None,
3016                                inferred_type: None,
3017                            })));
3018                        }
3019                    }
3020                }
3021                Ok(Expression::Function(Box::new(f)))
3022            }
3023
3024            // TO_TIMESTAMP_NTZ: single string arg -> CAST(... AS TIMESTAMPNTZ), otherwise keep as function
3025            "TO_TIMESTAMP_NTZ" => {
3026                if f.args.len() == 1 {
3027                    if let Expression::Literal(lit) = &f.args[0] {
3028                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3029                            return Ok(Expression::Cast(Box::new(Cast {
3030                                this: f.args.into_iter().next().unwrap(),
3031                                to: DataType::Custom {
3032                                    name: "TIMESTAMPNTZ".to_string(),
3033                                },
3034                                double_colon_syntax: false,
3035                                trailing_comments: vec![],
3036                                format: None,
3037                                default: None,
3038                                inferred_type: None,
3039                            })));
3040                        }
3041                    }
3042                }
3043                Ok(Expression::Function(Box::new(f)))
3044            }
3045
3046            // TO_TIMESTAMP_LTZ: single string arg -> CAST(... AS TIMESTAMPLTZ), otherwise keep as function
3047            "TO_TIMESTAMP_LTZ" => {
3048                if f.args.len() == 1 {
3049                    if let Expression::Literal(lit) = &f.args[0] {
3050                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3051                            return Ok(Expression::Cast(Box::new(Cast {
3052                                this: f.args.into_iter().next().unwrap(),
3053                                to: DataType::Custom {
3054                                    name: "TIMESTAMPLTZ".to_string(),
3055                                },
3056                                double_colon_syntax: false,
3057                                trailing_comments: vec![],
3058                                format: None,
3059                                default: None,
3060                                inferred_type: None,
3061                            })));
3062                        }
3063                    }
3064                }
3065                Ok(Expression::Function(Box::new(f)))
3066            }
3067
3068            // UNIFORM -> keep as-is (Snowflake-specific)
3069            "UNIFORM" => Ok(Expression::Function(Box::new(f))),
3070
3071            // REPLACE with 2 args -> add empty string 3rd arg
3072            "REPLACE" if f.args.len() == 2 => {
3073                let mut args = f.args;
3074                args.push(Expression::Literal(Box::new(
3075                    crate::expressions::Literal::String(String::new()),
3076                )));
3077                Ok(Expression::Function(Box::new(Function::new(
3078                    "REPLACE".to_string(),
3079                    args,
3080                ))))
3081            }
3082
3083            // ARBITRARY -> ANY_VALUE in Snowflake
3084            "ARBITRARY" => Ok(Expression::Function(Box::new(Function::new(
3085                "ANY_VALUE".to_string(),
3086                f.args,
3087            )))),
3088
3089            // SAFE_DIVIDE(x, y) -> IFF(y <> 0, x / y, NULL)
3090            "SAFE_DIVIDE" if f.args.len() == 2 => {
3091                let mut args = f.args;
3092                let x = args.remove(0);
3093                let y = args.remove(0);
3094                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3095                    condition: Expression::Neq(Box::new(BinaryOp {
3096                        left: y.clone(),
3097                        right: Expression::number(0),
3098                        left_comments: Vec::new(),
3099                        operator_comments: Vec::new(),
3100                        trailing_comments: Vec::new(),
3101                        inferred_type: None,
3102                    })),
3103                    true_value: Expression::Div(Box::new(BinaryOp {
3104                        left: x,
3105                        right: y,
3106                        left_comments: Vec::new(),
3107                        operator_comments: Vec::new(),
3108                        trailing_comments: Vec::new(),
3109                        inferred_type: None,
3110                    })),
3111                    false_value: Some(Expression::Null(crate::expressions::Null)),
3112                    original_name: Some("IFF".to_string()),
3113                    inferred_type: None,
3114                })))
3115            }
3116
3117            // TIMESTAMP(x) -> CAST(x AS TIMESTAMPTZ) in Snowflake
3118            "TIMESTAMP" if f.args.len() == 1 => {
3119                let arg = f.args.into_iter().next().unwrap();
3120                Ok(Expression::Cast(Box::new(Cast {
3121                    this: arg,
3122                    to: DataType::Custom {
3123                        name: "TIMESTAMPTZ".to_string(),
3124                    },
3125                    trailing_comments: Vec::new(),
3126                    double_colon_syntax: false,
3127                    format: None,
3128                    default: None,
3129                    inferred_type: None,
3130                })))
3131            }
3132
3133            // TIMESTAMP(x, tz) -> CONVERT_TIMEZONE(tz, CAST(x AS TIMESTAMP)) in Snowflake
3134            "TIMESTAMP" if f.args.len() == 2 => {
3135                let mut args = f.args;
3136                let value = args.remove(0);
3137                let tz = args.remove(0);
3138                Ok(Expression::Function(Box::new(Function::new(
3139                    "CONVERT_TIMEZONE".to_string(),
3140                    vec![
3141                        tz,
3142                        Expression::Cast(Box::new(Cast {
3143                            this: value,
3144                            to: DataType::Timestamp {
3145                                precision: None,
3146                                timezone: false,
3147                            },
3148                            trailing_comments: Vec::new(),
3149                            double_colon_syntax: false,
3150                            format: None,
3151                            default: None,
3152                            inferred_type: None,
3153                        })),
3154                    ],
3155                ))))
3156            }
3157
3158            // TIME(h, m, s) -> TIME_FROM_PARTS(h, m, s) in Snowflake
3159            "TIME" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
3160                "TIME_FROM_PARTS".to_string(),
3161                f.args,
3162            )))),
3163
3164            // DIV0(x, y) -> IFF(y = 0 AND NOT x IS NULL, 0, x / y)
3165            "DIV0" if f.args.len() == 2 => {
3166                let mut args = f.args;
3167                let x = args.remove(0);
3168                let y = args.remove(0);
3169                // Need parens around complex expressions
3170                let x_expr = Self::maybe_paren(x.clone());
3171                let y_expr = Self::maybe_paren(y.clone());
3172                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3173                    condition: Expression::And(Box::new(BinaryOp::new(
3174                        Expression::Eq(Box::new(BinaryOp::new(
3175                            y_expr.clone(),
3176                            Expression::number(0),
3177                        ))),
3178                        Expression::Not(Box::new(crate::expressions::UnaryOp {
3179                            this: Expression::IsNull(Box::new(crate::expressions::IsNull {
3180                                this: x_expr.clone(),
3181                                not: false,
3182                                postfix_form: false,
3183                            })),
3184                            inferred_type: None,
3185                        })),
3186                    ))),
3187                    true_value: Expression::number(0),
3188                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3189                    original_name: Some("IFF".to_string()),
3190                    inferred_type: None,
3191                })))
3192            }
3193
3194            // DIV0NULL(x, y) -> IFF(y = 0 OR y IS NULL, 0, x / y)
3195            "DIV0NULL" if f.args.len() == 2 => {
3196                let mut args = f.args;
3197                let x = args.remove(0);
3198                let y = args.remove(0);
3199                let x_expr = Self::maybe_paren(x.clone());
3200                let y_expr = Self::maybe_paren(y.clone());
3201                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3202                    condition: Expression::Or(Box::new(BinaryOp::new(
3203                        Expression::Eq(Box::new(BinaryOp::new(
3204                            y_expr.clone(),
3205                            Expression::number(0),
3206                        ))),
3207                        Expression::IsNull(Box::new(crate::expressions::IsNull {
3208                            this: y_expr.clone(),
3209                            not: false,
3210                            postfix_form: false,
3211                        })),
3212                    ))),
3213                    true_value: Expression::number(0),
3214                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3215                    original_name: Some("IFF".to_string()),
3216                    inferred_type: None,
3217                })))
3218            }
3219
3220            // ZEROIFNULL(x) -> IFF(x IS NULL, 0, x)
3221            "ZEROIFNULL" if f.args.len() == 1 => {
3222                let x = f.args.into_iter().next().unwrap();
3223                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3224                    condition: Expression::IsNull(Box::new(crate::expressions::IsNull {
3225                        this: x.clone(),
3226                        not: false,
3227                        postfix_form: false,
3228                    })),
3229                    true_value: Expression::number(0),
3230                    false_value: Some(x),
3231                    original_name: Some("IFF".to_string()),
3232                    inferred_type: None,
3233                })))
3234            }
3235
3236            // NULLIFZERO(x) -> IFF(x = 0, NULL, x)
3237            "NULLIFZERO" if f.args.len() == 1 => {
3238                let x = f.args.into_iter().next().unwrap();
3239                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3240                    condition: Expression::Eq(Box::new(BinaryOp::new(
3241                        x.clone(),
3242                        Expression::number(0),
3243                    ))),
3244                    true_value: Expression::Null(crate::expressions::Null),
3245                    false_value: Some(x),
3246                    original_name: Some("IFF".to_string()),
3247                    inferred_type: None,
3248                })))
3249            }
3250
3251            // TRY_TO_TIME('string') -> TRY_CAST('string' AS TIME) when single string arg
3252            "TRY_TO_TIME" => {
3253                if f.args.len() == 1 {
3254                    if let Expression::Literal(lit) = &f.args[0] {
3255                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3256                            return Ok(Expression::TryCast(Box::new(Cast {
3257                                this: f.args.into_iter().next().unwrap(),
3258                                to: crate::expressions::DataType::Time {
3259                                    precision: None,
3260                                    timezone: false,
3261                                },
3262                                double_colon_syntax: false,
3263                                trailing_comments: Vec::new(),
3264                                format: None,
3265                                default: None,
3266                                inferred_type: None,
3267                            })));
3268                        }
3269                    }
3270                }
3271                // Normalize format string (2nd arg) if present
3272                let mut args = f.args;
3273                if args.len() >= 2 {
3274                    args[1] = Self::normalize_format_arg(args[1].clone());
3275                }
3276                Ok(Expression::Function(Box::new(Function::new(
3277                    "TRY_TO_TIME".to_string(),
3278                    args,
3279                ))))
3280            }
3281
3282            // TRY_TO_TIMESTAMP('string') -> TRY_CAST('string' AS TIMESTAMP) when single string arg
3283            // Convert if the string is NOT a pure numeric/epoch value
3284            "TRY_TO_TIMESTAMP" => {
3285                if f.args.len() == 1 {
3286                    if let Expression::Literal(lit) = &f.args[0] {
3287                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
3288                            if !Self::looks_like_epoch(s) {
3289                                return Ok(Expression::TryCast(Box::new(Cast {
3290                                    this: f.args.into_iter().next().unwrap(),
3291                                    to: DataType::Timestamp {
3292                                        precision: None,
3293                                        timezone: false,
3294                                    },
3295                                    double_colon_syntax: false,
3296                                    trailing_comments: Vec::new(),
3297                                    format: None,
3298                                    default: None,
3299                                    inferred_type: None,
3300                                })));
3301                            }
3302                        }
3303                    }
3304                }
3305                // Normalize format string (2nd arg) if present
3306                let mut args = f.args;
3307                if args.len() >= 2 {
3308                    args[1] = Self::normalize_format_arg(args[1].clone());
3309                }
3310                Ok(Expression::Function(Box::new(Function::new(
3311                    "TRY_TO_TIMESTAMP".to_string(),
3312                    args,
3313                ))))
3314            }
3315
3316            // TRY_TO_DATE('string') -> TRY_CAST('string' AS DATE) when single string arg
3317            "TRY_TO_DATE" => {
3318                if f.args.len() == 1 {
3319                    if let Expression::Literal(lit) = &f.args[0] {
3320                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
3321                            // Only convert if the string looks like a date
3322                            if s.contains('-') && s.len() >= 8 && s.len() <= 12 {
3323                                return Ok(Expression::TryCast(Box::new(Cast {
3324                                    this: f.args.into_iter().next().unwrap(),
3325                                    to: crate::expressions::DataType::Date,
3326                                    double_colon_syntax: false,
3327                                    trailing_comments: Vec::new(),
3328                                    format: None,
3329                                    default: None,
3330                                    inferred_type: None,
3331                                })));
3332                            }
3333                        }
3334                    }
3335                }
3336                // Normalize format string (2nd arg) if present
3337                let mut args = f.args;
3338                if args.len() >= 2 {
3339                    args[1] = Self::normalize_format_arg(args[1].clone());
3340                }
3341                Ok(Expression::Function(Box::new(Function::new(
3342                    "TRY_TO_DATE".to_string(),
3343                    args,
3344                ))))
3345            }
3346
3347            // TRY_TO_DOUBLE -> keep as TRY_TO_DOUBLE in Snowflake (native function)
3348            "TRY_TO_DOUBLE" => Ok(Expression::Function(Box::new(f))),
3349
3350            // REGEXP_REPLACE with 2 args -> add empty string replacement
3351            "REGEXP_REPLACE" if f.args.len() == 2 => {
3352                let mut args = f.args;
3353                args.push(Expression::Literal(Box::new(
3354                    crate::expressions::Literal::String(String::new()),
3355                )));
3356                Ok(Expression::Function(Box::new(Function::new(
3357                    "REGEXP_REPLACE".to_string(),
3358                    args,
3359                ))))
3360            }
3361
3362            // LAST_DAY(x, MONTH) -> LAST_DAY(x) in Snowflake (strip MONTH default)
3363            "LAST_DAY" if f.args.len() == 2 => {
3364                let mut args = f.args;
3365                let date = args.remove(0);
3366                let unit = args.remove(0);
3367                let unit_str = match &unit {
3368                    Expression::Column(c) => c.name.name.to_uppercase(),
3369                    Expression::Identifier(i) => i.name.to_uppercase(),
3370                    _ => String::new(),
3371                };
3372                if unit_str == "MONTH" {
3373                    Ok(Expression::Function(Box::new(Function::new(
3374                        "LAST_DAY".to_string(),
3375                        vec![date],
3376                    ))))
3377                } else {
3378                    Ok(Expression::Function(Box::new(Function::new(
3379                        "LAST_DAY".to_string(),
3380                        vec![date, unit],
3381                    ))))
3382                }
3383            }
3384
3385            // EXTRACT('field', expr) function-call syntax -> DATE_PART('field', expr)
3386            "EXTRACT" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
3387                "DATE_PART".to_string(),
3388                f.args,
3389            )))),
3390
3391            // ENDS_WITH/ENDSWITH -> EndsWith AST node
3392            "ENDS_WITH" | "ENDSWITH" if f.args.len() == 2 => {
3393                let mut args = f.args;
3394                let this = args.remove(0);
3395                let expr = args.remove(0);
3396                Ok(Expression::EndsWith(Box::new(
3397                    crate::expressions::BinaryFunc {
3398                        original_name: None,
3399                        this,
3400                        expression: expr,
3401                        inferred_type: None,
3402                    },
3403                )))
3404            }
3405
3406            // Pass through everything else
3407            _ => Ok(Expression::Function(Box::new(f))),
3408        }
3409    }
3410
3411    /// Check if a string looks like a datetime (contains date separators, not just digits)
3412    fn looks_like_datetime(s: &str) -> bool {
3413        // A datetime string typically contains dashes, colons, or spaces
3414        // A numeric/epoch string is just digits (possibly with a dot)
3415        s.contains('-') || s.contains(':') || s.contains(' ') || s.contains('/')
3416    }
3417
3418    /// Check if a string looks like an epoch number (only digits, possibly with a dot)
3419    fn looks_like_epoch(s: &str) -> bool {
3420        !s.is_empty() && s.chars().all(|c| c.is_ascii_digit() || c == '.')
3421    }
3422
3423    /// Wrap an expression in parentheses if it's a complex expression (binary op, etc.)
3424    fn maybe_paren(expr: Expression) -> Expression {
3425        match &expr {
3426            Expression::Sub(_) | Expression::Add(_) | Expression::Mul(_) | Expression::Div(_) => {
3427                Expression::Paren(Box::new(crate::expressions::Paren {
3428                    this: expr,
3429                    trailing_comments: Vec::new(),
3430                }))
3431            }
3432            _ => expr,
3433        }
3434    }
3435
3436    /// Normalize Snowflake date/time format strings to canonical lowercase form.
3437    /// YYYY -> yyyy, MM -> mm, DD -> DD (stays), HH24 -> hh24, HH12 -> hh12,
3438    /// MI -> mi, SS -> ss, FF -> ff, AM/PM -> pm, quoted "T" -> T
3439    fn normalize_snowflake_format(format: &str) -> String {
3440        let mut result = String::new();
3441        let chars: Vec<char> = format.chars().collect();
3442        let mut i = 0;
3443        while i < chars.len() {
3444            // Handle quoted strings like "T" -> T
3445            if chars[i] == '"' {
3446                i += 1;
3447                while i < chars.len() && chars[i] != '"' {
3448                    result.push(chars[i]);
3449                    i += 1;
3450                }
3451                if i < chars.len() {
3452                    i += 1; // skip closing quote
3453                }
3454                continue;
3455            }
3456
3457            let remaining = &format[i..];
3458            let remaining_upper = remaining.to_uppercase();
3459
3460            // Multi-char patterns (check longest first)
3461            if remaining_upper.starts_with("YYYY") {
3462                result.push_str("yyyy");
3463                i += 4;
3464            } else if remaining_upper.starts_with("YY") {
3465                result.push_str("yy");
3466                i += 2;
3467            } else if remaining_upper.starts_with("MMMM") {
3468                result.push_str("mmmm");
3469                i += 4;
3470            } else if remaining_upper.starts_with("MON") {
3471                result.push_str("mon");
3472                i += 3;
3473            } else if remaining_upper.starts_with("MM") {
3474                result.push_str("mm");
3475                i += 2;
3476            } else if remaining_upper.starts_with("DD") {
3477                result.push_str("DD");
3478                i += 2;
3479            } else if remaining_upper.starts_with("DY") {
3480                result.push_str("dy");
3481                i += 2;
3482            } else if remaining_upper.starts_with("HH24") {
3483                result.push_str("hh24");
3484                i += 4;
3485            } else if remaining_upper.starts_with("HH12") {
3486                result.push_str("hh12");
3487                i += 4;
3488            } else if remaining_upper.starts_with("HH") {
3489                result.push_str("hh");
3490                i += 2;
3491            } else if remaining_upper.starts_with("MISS") {
3492                // MISS = MI + SS
3493                result.push_str("miss");
3494                i += 4;
3495            } else if remaining_upper.starts_with("MI") {
3496                result.push_str("mi");
3497                i += 2;
3498            } else if remaining_upper.starts_with("SS") {
3499                result.push_str("ss");
3500                i += 2;
3501            } else if remaining_upper.starts_with("FF") {
3502                // FF followed by a digit (FF1-FF9) keeps the digit
3503                let ff_len = 2;
3504                let digit = if i + ff_len < chars.len() && chars[i + ff_len].is_ascii_digit() {
3505                    let d = chars[i + ff_len];
3506                    Some(d)
3507                } else {
3508                    None
3509                };
3510                if let Some(d) = digit {
3511                    result.push_str("ff");
3512                    result.push(d);
3513                    i += 3;
3514                } else {
3515                    // Plain FF -> ff9
3516                    result.push_str("ff9");
3517                    i += 2;
3518                }
3519            } else if remaining_upper.starts_with("AM") || remaining_upper.starts_with("PM") {
3520                result.push_str("pm");
3521                i += 2;
3522            } else if remaining_upper.starts_with("TZH") {
3523                result.push_str("tzh");
3524                i += 3;
3525            } else if remaining_upper.starts_with("TZM") {
3526                result.push_str("tzm");
3527                i += 3;
3528            } else {
3529                // Keep separators and other characters as-is
3530                result.push(chars[i]);
3531                i += 1;
3532            }
3533        }
3534        result
3535    }
3536
3537    /// Normalize format string argument if it's a string literal
3538    fn normalize_format_arg(expr: Expression) -> Expression {
3539        if let Expression::Literal(lit) = &expr {
3540            if let crate::expressions::Literal::String(s) = lit.as_ref() {
3541                let normalized = Self::normalize_snowflake_format(s);
3542                Expression::Literal(Box::new(crate::expressions::Literal::String(normalized)))
3543            } else {
3544                expr.clone()
3545            }
3546        } else {
3547            expr
3548        }
3549    }
3550
3551    /// Transform a lambda with typed parameters for Snowflake
3552    /// For `a int -> a + a + 1`, transforms to `a -> CAST(a AS INT) + CAST(a AS INT) + 1`
3553    fn transform_typed_lambda(&self, lambda: crate::expressions::LambdaExpr) -> Expression {
3554        use crate::expressions::{DataType, LambdaExpr};
3555        use std::collections::HashMap;
3556
3557        // Build mapping of parameter names to their types
3558        let mut param_types: HashMap<String, DataType> = HashMap::new();
3559        for (i, param) in lambda.parameters.iter().enumerate() {
3560            if let Some(Some(dt)) = lambda.parameter_types.get(i) {
3561                param_types.insert(param.name.to_uppercase(), dt.clone());
3562            }
3563        }
3564
3565        // If no typed parameters, return lambda unchanged
3566        if param_types.is_empty() {
3567            return Expression::Lambda(Box::new(lambda));
3568        }
3569
3570        // Transform the body by replacing parameter references with CAST expressions
3571        let transformed_body = self.replace_lambda_params_with_cast(lambda.body, &param_types);
3572
3573        // Return new lambda without type annotations (they're now embedded in CAST)
3574        Expression::Lambda(Box::new(LambdaExpr {
3575            parameters: lambda.parameters,
3576            body: transformed_body,
3577            colon: lambda.colon,
3578            parameter_types: Vec::new(), // Clear type annotations
3579        }))
3580    }
3581
3582    /// Recursively replace column/identifier references to typed lambda parameters with CAST expressions
3583    fn replace_lambda_params_with_cast(
3584        &self,
3585        expr: Expression,
3586        param_types: &std::collections::HashMap<String, crate::expressions::DataType>,
3587    ) -> Expression {
3588        use crate::expressions::{BinaryOp, Cast, Paren};
3589
3590        match expr {
3591            // Column reference - check if it matches a typed parameter
3592            Expression::Column(col) if col.table.is_none() => {
3593                let name_upper = col.name.name.to_uppercase();
3594                if let Some(dt) = param_types.get(&name_upper) {
3595                    // Wrap in CAST
3596                    Expression::Cast(Box::new(Cast {
3597                        this: Expression::Column(col),
3598                        to: dt.clone(),
3599                        double_colon_syntax: false,
3600                        trailing_comments: Vec::new(),
3601                        format: None,
3602                        default: None,
3603                        inferred_type: None,
3604                    }))
3605                } else {
3606                    Expression::Column(col)
3607                }
3608            }
3609
3610            // Identifier reference - check if it matches a typed parameter
3611            Expression::Identifier(id) => {
3612                let name_upper = id.name.to_uppercase();
3613                if let Some(dt) = param_types.get(&name_upper) {
3614                    // Wrap in CAST
3615                    Expression::Cast(Box::new(Cast {
3616                        this: Expression::Identifier(id),
3617                        to: dt.clone(),
3618                        double_colon_syntax: false,
3619                        trailing_comments: Vec::new(),
3620                        format: None,
3621                        default: None,
3622                        inferred_type: None,
3623                    }))
3624                } else {
3625                    Expression::Identifier(id)
3626                }
3627            }
3628
3629            // Binary operations - recursively transform both sides
3630            Expression::Add(op) => Expression::Add(Box::new(BinaryOp::new(
3631                self.replace_lambda_params_with_cast(op.left, param_types),
3632                self.replace_lambda_params_with_cast(op.right, param_types),
3633            ))),
3634            Expression::Sub(op) => Expression::Sub(Box::new(BinaryOp::new(
3635                self.replace_lambda_params_with_cast(op.left, param_types),
3636                self.replace_lambda_params_with_cast(op.right, param_types),
3637            ))),
3638            Expression::Mul(op) => Expression::Mul(Box::new(BinaryOp::new(
3639                self.replace_lambda_params_with_cast(op.left, param_types),
3640                self.replace_lambda_params_with_cast(op.right, param_types),
3641            ))),
3642            Expression::Div(op) => Expression::Div(Box::new(BinaryOp::new(
3643                self.replace_lambda_params_with_cast(op.left, param_types),
3644                self.replace_lambda_params_with_cast(op.right, param_types),
3645            ))),
3646            Expression::Mod(op) => Expression::Mod(Box::new(BinaryOp::new(
3647                self.replace_lambda_params_with_cast(op.left, param_types),
3648                self.replace_lambda_params_with_cast(op.right, param_types),
3649            ))),
3650
3651            // Parenthesized expression
3652            Expression::Paren(p) => Expression::Paren(Box::new(Paren {
3653                this: self.replace_lambda_params_with_cast(p.this, param_types),
3654                trailing_comments: p.trailing_comments,
3655            })),
3656
3657            // Function calls - transform arguments
3658            Expression::Function(mut f) => {
3659                f.args = f
3660                    .args
3661                    .into_iter()
3662                    .map(|arg| self.replace_lambda_params_with_cast(arg, param_types))
3663                    .collect();
3664                Expression::Function(f)
3665            }
3666
3667            // Comparison operators
3668            Expression::Eq(op) => Expression::Eq(Box::new(BinaryOp::new(
3669                self.replace_lambda_params_with_cast(op.left, param_types),
3670                self.replace_lambda_params_with_cast(op.right, param_types),
3671            ))),
3672            Expression::Neq(op) => Expression::Neq(Box::new(BinaryOp::new(
3673                self.replace_lambda_params_with_cast(op.left, param_types),
3674                self.replace_lambda_params_with_cast(op.right, param_types),
3675            ))),
3676            Expression::Lt(op) => Expression::Lt(Box::new(BinaryOp::new(
3677                self.replace_lambda_params_with_cast(op.left, param_types),
3678                self.replace_lambda_params_with_cast(op.right, param_types),
3679            ))),
3680            Expression::Lte(op) => Expression::Lte(Box::new(BinaryOp::new(
3681                self.replace_lambda_params_with_cast(op.left, param_types),
3682                self.replace_lambda_params_with_cast(op.right, param_types),
3683            ))),
3684            Expression::Gt(op) => Expression::Gt(Box::new(BinaryOp::new(
3685                self.replace_lambda_params_with_cast(op.left, param_types),
3686                self.replace_lambda_params_with_cast(op.right, param_types),
3687            ))),
3688            Expression::Gte(op) => Expression::Gte(Box::new(BinaryOp::new(
3689                self.replace_lambda_params_with_cast(op.left, param_types),
3690                self.replace_lambda_params_with_cast(op.right, param_types),
3691            ))),
3692
3693            // And/Or
3694            Expression::And(op) => Expression::And(Box::new(BinaryOp::new(
3695                self.replace_lambda_params_with_cast(op.left, param_types),
3696                self.replace_lambda_params_with_cast(op.right, param_types),
3697            ))),
3698            Expression::Or(op) => Expression::Or(Box::new(BinaryOp::new(
3699                self.replace_lambda_params_with_cast(op.left, param_types),
3700                self.replace_lambda_params_with_cast(op.right, param_types),
3701            ))),
3702
3703            // Other expressions - return unchanged
3704            other => other,
3705        }
3706    }
3707
3708    fn transform_aggregate_function(
3709        &self,
3710        f: Box<crate::expressions::AggregateFunction>,
3711    ) -> Result<Expression> {
3712        let name_upper = f.name.to_uppercase();
3713        match name_upper.as_str() {
3714            // GROUP_CONCAT -> LISTAGG
3715            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3716                Function::new("LISTAGG".to_string(), f.args),
3717            ))),
3718
3719            // STRING_AGG -> LISTAGG
3720            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3721                Function::new("LISTAGG".to_string(), f.args),
3722            ))),
3723
3724            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT
3725            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3726                Function::new("APPROX_COUNT_DISTINCT".to_string(), f.args),
3727            ))),
3728
3729            // BIT_AND -> BITAND_AGG
3730            "BIT_AND" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3731                "BITAND_AGG".to_string(),
3732                f.args,
3733            )))),
3734
3735            // BIT_OR -> BITOR_AGG
3736            "BIT_OR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3737                "BITOR_AGG".to_string(),
3738                f.args,
3739            )))),
3740
3741            // BIT_XOR -> BITXOR_AGG
3742            "BIT_XOR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3743                "BITXOR_AGG".to_string(),
3744                f.args,
3745            )))),
3746
3747            // BOOL_AND/BOOLAND_AGG/LOGICAL_AND -> LogicalAnd AST node
3748            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" if !f.args.is_empty() => {
3749                let arg = f.args.into_iter().next().unwrap();
3750                Ok(Expression::LogicalAnd(Box::new(AggFunc {
3751                    this: arg,
3752                    distinct: f.distinct,
3753                    filter: f.filter,
3754                    order_by: Vec::new(),
3755                    name: Some("BOOLAND_AGG".to_string()),
3756                    ignore_nulls: None,
3757                    having_max: None,
3758                    limit: None,
3759                    inferred_type: None,
3760                })))
3761            }
3762
3763            // BOOL_OR/BOOLOR_AGG/LOGICAL_OR -> LogicalOr AST node
3764            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if !f.args.is_empty() => {
3765                let arg = f.args.into_iter().next().unwrap();
3766                Ok(Expression::LogicalOr(Box::new(AggFunc {
3767                    this: arg,
3768                    distinct: f.distinct,
3769                    filter: f.filter,
3770                    order_by: Vec::new(),
3771                    name: Some("BOOLOR_AGG".to_string()),
3772                    ignore_nulls: None,
3773                    having_max: None,
3774                    limit: None,
3775                    inferred_type: None,
3776                })))
3777            }
3778
3779            // APPROX_TOP_K - add default k=1 if only one argument
3780            "APPROX_TOP_K" if f.args.len() == 1 => {
3781                let mut args = f.args;
3782                args.push(Expression::number(1));
3783                Ok(Expression::AggregateFunction(Box::new(
3784                    crate::expressions::AggregateFunction {
3785                        name: "APPROX_TOP_K".to_string(),
3786                        args,
3787                        distinct: f.distinct,
3788                        filter: f.filter,
3789                        order_by: Vec::new(),
3790                        limit: None,
3791                        ignore_nulls: None,
3792                        inferred_type: None,
3793                    },
3794                )))
3795            }
3796
3797            // SKEW/SKEWNESS -> Skewness AST node
3798            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
3799                let arg = f.args.into_iter().next().unwrap();
3800                Ok(Expression::Skewness(Box::new(AggFunc {
3801                    this: arg,
3802                    distinct: f.distinct,
3803                    filter: f.filter,
3804                    order_by: Vec::new(),
3805                    name: Some("SKEW".to_string()),
3806                    ignore_nulls: None,
3807                    having_max: None,
3808                    limit: None,
3809                    inferred_type: None,
3810                })))
3811            }
3812
3813            // Pass through everything else
3814            _ => Ok(Expression::AggregateFunction(f)),
3815        }
3816    }
3817}
3818
3819/// Convert strftime format specifiers to Snowflake format specifiers
3820fn strftime_to_snowflake_format(fmt: &str) -> String {
3821    let mut result = String::new();
3822    let chars: Vec<char> = fmt.chars().collect();
3823    let mut i = 0;
3824    while i < chars.len() {
3825        if chars[i] == '%' && i + 1 < chars.len() {
3826            match chars[i + 1] {
3827                'Y' => {
3828                    result.push_str("yyyy");
3829                    i += 2;
3830                }
3831                'y' => {
3832                    result.push_str("yy");
3833                    i += 2;
3834                }
3835                'm' => {
3836                    result.push_str("mm");
3837                    i += 2;
3838                }
3839                'd' => {
3840                    result.push_str("DD");
3841                    i += 2;
3842                }
3843                'H' => {
3844                    result.push_str("hh24");
3845                    i += 2;
3846                }
3847                'M' => {
3848                    result.push_str("mmmm");
3849                    i += 2;
3850                } // %M = full month name
3851                'i' => {
3852                    result.push_str("mi");
3853                    i += 2;
3854                }
3855                'S' | 's' => {
3856                    result.push_str("ss");
3857                    i += 2;
3858                }
3859                'f' => {
3860                    result.push_str("ff");
3861                    i += 2;
3862                }
3863                'w' => {
3864                    result.push_str("dy");
3865                    i += 2;
3866                } // day of week number
3867                'a' => {
3868                    result.push_str("DY");
3869                    i += 2;
3870                } // abbreviated day name
3871                'b' => {
3872                    result.push_str("mon");
3873                    i += 2;
3874                } // abbreviated month name
3875                'T' => {
3876                    result.push_str("hh24:mi:ss");
3877                    i += 2;
3878                } // time shorthand
3879                _ => {
3880                    result.push(chars[i]);
3881                    result.push(chars[i + 1]);
3882                    i += 2;
3883                }
3884            }
3885        } else {
3886            result.push(chars[i]);
3887            i += 1;
3888        }
3889    }
3890    result
3891}
3892
3893#[cfg(test)]
3894mod tests {
3895    use super::*;
3896    use crate::dialects::Dialect;
3897
3898    fn transpile_to_snowflake(sql: &str) -> String {
3899        let dialect = Dialect::get(DialectType::Generic);
3900        let result = dialect
3901            .transpile_to(sql, DialectType::Snowflake)
3902            .expect("Transpile failed");
3903        result[0].clone()
3904    }
3905
3906    #[test]
3907    fn test_ifnull_to_coalesce() {
3908        let result = transpile_to_snowflake("SELECT IFNULL(a, b)");
3909        assert!(
3910            result.contains("COALESCE"),
3911            "Expected COALESCE, got: {}",
3912            result
3913        );
3914    }
3915
3916    #[test]
3917    fn test_basic_select() {
3918        let result = transpile_to_snowflake("SELECT a, b FROM users WHERE id = 1");
3919        assert!(result.contains("SELECT"));
3920        assert!(result.contains("FROM users"));
3921    }
3922
3923    #[test]
3924    fn test_group_concat_to_listagg() {
3925        let result = transpile_to_snowflake("SELECT GROUP_CONCAT(name)");
3926        assert!(
3927            result.contains("LISTAGG"),
3928            "Expected LISTAGG, got: {}",
3929            result
3930        );
3931    }
3932
3933    #[test]
3934    fn test_string_agg_to_listagg() {
3935        let result = transpile_to_snowflake("SELECT STRING_AGG(name)");
3936        assert!(
3937            result.contains("LISTAGG"),
3938            "Expected LISTAGG, got: {}",
3939            result
3940        );
3941    }
3942
3943    #[test]
3944    fn test_array_to_array_construct() {
3945        let result = transpile_to_snowflake("SELECT ARRAY(1, 2, 3)");
3946        // ARRAY(1, 2, 3) from Generic -> Snowflake uses [] bracket notation
3947        assert!(
3948            result.contains("[1, 2, 3]"),
3949            "Expected [1, 2, 3], got: {}",
3950            result
3951        );
3952    }
3953
3954    #[test]
3955    fn test_double_quote_identifiers() {
3956        // Snowflake uses double quotes for identifiers
3957        let dialect = SnowflakeDialect;
3958        let config = dialect.generator_config();
3959        assert_eq!(config.identifier_quote, '"');
3960    }
3961}