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