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