Skip to main content

polyglot_sql/dialects/
snowflake.rs

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