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                    inferred_type: None,
2393                },
2394            ))),
2395
2396            // APPROX_TOP_K - add default k=1 if not provided
2397            "APPROX_TOP_K" if f.args.len() == 1 => {
2398                let mut args = f.args;
2399                args.push(Expression::number(1));
2400                Ok(Expression::Function(Box::new(Function::new(
2401                    "APPROX_TOP_K".to_string(),
2402                    args,
2403                ))))
2404            }
2405
2406            // TO_DECIMAL, TO_NUMERIC -> TO_NUMBER
2407            "TO_DECIMAL" | "TO_NUMERIC" => Ok(Expression::Function(Box::new(Function::new(
2408                "TO_NUMBER".to_string(),
2409                f.args,
2410            )))),
2411
2412            // TRY_TO_DECIMAL, TRY_TO_NUMERIC -> TRY_TO_NUMBER
2413            "TRY_TO_DECIMAL" | "TRY_TO_NUMERIC" => Ok(Expression::Function(Box::new(
2414                Function::new("TRY_TO_NUMBER".to_string(), f.args),
2415            ))),
2416
2417            // STDDEV_SAMP -> STDDEV
2418            "STDDEV_SAMP" => Ok(Expression::Function(Box::new(Function::new(
2419                "STDDEV".to_string(),
2420                f.args,
2421            )))),
2422
2423            // STRTOK -> SPLIT_PART (with default delimiter and position)
2424            "STRTOK" if f.args.len() >= 1 => {
2425                let mut args = f.args;
2426                // Add default delimiter (space) if missing
2427                if args.len() == 1 {
2428                    args.push(Expression::string(" ".to_string()));
2429                }
2430                // Add default position (1) if missing
2431                if args.len() == 2 {
2432                    args.push(Expression::number(1));
2433                }
2434                Ok(Expression::Function(Box::new(Function::new(
2435                    "STRTOK".to_string(),
2436                    args,
2437                ))))
2438            }
2439
2440            "STRTOK_TO_ARRAY" if f.args.len() == 1 => {
2441                let mut args = f.args;
2442                args.push(Expression::string(" ".to_string()));
2443                Ok(Expression::Function(Box::new(Function::new(
2444                    "STRTOK_TO_ARRAY".to_string(),
2445                    args,
2446                ))))
2447            }
2448
2449            // WEEKOFYEAR -> WEEK
2450            "WEEKOFYEAR" => Ok(Expression::Function(Box::new(Function::new(
2451                "WEEK".to_string(),
2452                f.args,
2453            )))),
2454
2455            // LIKE(col, pattern, escape) -> col LIKE pattern ESCAPE escape
2456            "LIKE" if f.args.len() >= 2 => {
2457                let mut args = f.args.into_iter();
2458                let left = args.next().unwrap();
2459                let right = args.next().unwrap();
2460                let escape = args.next();
2461                Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
2462                    left,
2463                    right,
2464                    escape,
2465                    quantifier: None,
2466                    inferred_type: None,
2467                })))
2468            }
2469
2470            // ILIKE(col, pattern, escape) -> col ILIKE pattern ESCAPE escape
2471            "ILIKE" if f.args.len() >= 2 => {
2472                let mut args = f.args.into_iter();
2473                let left = args.next().unwrap();
2474                let right = args.next().unwrap();
2475                let escape = args.next();
2476                Ok(Expression::ILike(Box::new(crate::expressions::LikeOp {
2477                    left,
2478                    right,
2479                    escape,
2480                    quantifier: None,
2481                    inferred_type: None,
2482                })))
2483            }
2484
2485            // RLIKE -> REGEXP_LIKE
2486            "RLIKE" if f.args.len() >= 2 => {
2487                let mut args = f.args.into_iter();
2488                let left = args.next().unwrap();
2489                let pattern = args.next().unwrap();
2490                let flags = args.next();
2491                Ok(Expression::RegexpLike(Box::new(
2492                    crate::expressions::RegexpFunc {
2493                        this: left,
2494                        pattern,
2495                        flags,
2496                    },
2497                )))
2498            }
2499
2500            // IFF -> convert to IfFunc AST node for proper cross-dialect handling
2501            "IFF" if f.args.len() >= 2 => {
2502                let mut args = f.args;
2503                let condition = args.remove(0);
2504                let true_value = args.remove(0);
2505                let false_value = if !args.is_empty() {
2506                    Some(args.remove(0))
2507                } else {
2508                    None
2509                };
2510                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
2511                    condition,
2512                    true_value,
2513                    false_value,
2514                    original_name: Some("IFF".to_string()),
2515                    inferred_type: None,
2516                })))
2517            }
2518
2519            // TIMESTAMP_NTZ_FROM_PARTS, TIMESTAMPFROMPARTS, TIMESTAMPNTZFROMPARTS -> TIMESTAMP_FROM_PARTS
2520            "TIMESTAMP_NTZ_FROM_PARTS" | "TIMESTAMPFROMPARTS" | "TIMESTAMPNTZFROMPARTS" => {
2521                Ok(Expression::Function(Box::new(Function::new(
2522                    "TIMESTAMP_FROM_PARTS".to_string(),
2523                    f.args,
2524                ))))
2525            }
2526
2527            // TIMESTAMPLTZFROMPARTS -> TIMESTAMP_LTZ_FROM_PARTS
2528            "TIMESTAMPLTZFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2529                "TIMESTAMP_LTZ_FROM_PARTS".to_string(),
2530                f.args,
2531            )))),
2532
2533            // TIMESTAMPTZFROMPARTS -> TIMESTAMP_TZ_FROM_PARTS
2534            "TIMESTAMPTZFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2535                "TIMESTAMP_TZ_FROM_PARTS".to_string(),
2536                f.args,
2537            )))),
2538
2539            // DATEADD with 3 args - transform the unit (first arg) using date part mapping
2540            "DATEADD" if f.args.len() >= 1 => {
2541                let mut args = f.args;
2542                args[0] = self.transform_date_part_arg(args[0].clone());
2543                Ok(Expression::Function(Box::new(Function::new(
2544                    "DATEADD".to_string(),
2545                    args,
2546                ))))
2547            }
2548
2549            // DATEDIFF with 3 args - transform the unit (first arg) using date part mapping
2550            // Also convert _POLYGLOT_TO_DATE back to TO_DATE (from cross-dialect normalize)
2551            "DATEDIFF" if f.args.len() >= 1 => {
2552                let mut args = f.args;
2553                args[0] = self.transform_date_part_arg(args[0].clone());
2554                // Convert _POLYGLOT_TO_DATE back to TO_DATE for date args
2555                // (_POLYGLOT_TO_DATE is an internal marker from cross-dialect normalize)
2556                for i in 1..args.len() {
2557                    if let Expression::Function(ref func) = args[i] {
2558                        if func.name == "_POLYGLOT_TO_DATE" {
2559                            let inner_args = func.args.clone();
2560                            args[i] = Expression::Function(Box::new(Function::new(
2561                                "TO_DATE".to_string(),
2562                                inner_args,
2563                            )));
2564                        }
2565                    }
2566                }
2567                Ok(Expression::Function(Box::new(Function::new(
2568                    "DATEDIFF".to_string(),
2569                    args,
2570                ))))
2571            }
2572
2573            // TIMEDIFF -> DATEDIFF
2574            "TIMEDIFF" => Ok(Expression::Function(Box::new(Function::new(
2575                "DATEDIFF".to_string(),
2576                f.args,
2577            )))),
2578
2579            // TIMESTAMPDIFF -> DATEDIFF
2580            "TIMESTAMPDIFF" => Ok(Expression::Function(Box::new(Function::new(
2581                "DATEDIFF".to_string(),
2582                f.args,
2583            )))),
2584
2585            // TIMESTAMPADD -> DATEADD
2586            "TIMESTAMPADD" => Ok(Expression::Function(Box::new(Function::new(
2587                "DATEADD".to_string(),
2588                f.args,
2589            )))),
2590
2591            // TIMEADD -> preserve it
2592            "TIMEADD" => Ok(Expression::Function(Box::new(f))),
2593
2594            // DATE_FROM_PARTS, DATEFROMPARTS -> DATE_FROM_PARTS
2595            "DATEFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2596                "DATE_FROM_PARTS".to_string(),
2597                f.args,
2598            )))),
2599
2600            // TIME_FROM_PARTS, TIMEFROMPARTS -> TIME_FROM_PARTS
2601            "TIMEFROMPARTS" => Ok(Expression::Function(Box::new(Function::new(
2602                "TIME_FROM_PARTS".to_string(),
2603                f.args,
2604            )))),
2605
2606            // DAYOFWEEK -> DAYOFWEEK (preserve)
2607            "DAYOFWEEK" => Ok(Expression::Function(Box::new(f))),
2608
2609            // DAYOFMONTH -> DAYOFMONTH (preserve)
2610            "DAYOFMONTH" => Ok(Expression::Function(Box::new(f))),
2611
2612            // DAYOFYEAR -> DAYOFYEAR (preserve)
2613            "DAYOFYEAR" => Ok(Expression::Function(Box::new(f))),
2614
2615            // MONTHNAME -> Monthname AST node (abbreviated=true for Snowflake)
2616            // Target dialects can then convert to their native form
2617            "MONTHNAME" if f.args.len() == 1 => {
2618                let arg = f.args.into_iter().next().unwrap();
2619                Ok(Expression::Monthname(Box::new(
2620                    crate::expressions::Monthname {
2621                        this: Box::new(arg),
2622                        abbreviated: Some(Box::new(Expression::Literal(Box::new(
2623                            Literal::String("true".to_string()),
2624                        )))),
2625                    },
2626                )))
2627            }
2628
2629            // DAYNAME -> Dayname AST node (abbreviated=true for Snowflake)
2630            // Target dialects can then convert to their native form
2631            "DAYNAME" if f.args.len() == 1 => {
2632                let arg = f.args.into_iter().next().unwrap();
2633                Ok(Expression::Dayname(Box::new(crate::expressions::Dayname {
2634                    this: Box::new(arg),
2635                    abbreviated: Some(Box::new(Expression::Literal(Box::new(Literal::String(
2636                        "true".to_string(),
2637                    ))))),
2638                })))
2639            }
2640
2641            // BOOLAND_AGG/BOOL_AND/LOGICAL_AND -> LogicalAnd AST node
2642            "BOOLAND_AGG" | "BOOL_AND" | "LOGICAL_AND" if !f.args.is_empty() => {
2643                let arg = f.args.into_iter().next().unwrap();
2644                Ok(Expression::LogicalAnd(Box::new(AggFunc {
2645                    this: arg,
2646                    distinct: false,
2647                    filter: None,
2648                    order_by: Vec::new(),
2649                    name: Some("BOOLAND_AGG".to_string()),
2650                    ignore_nulls: None,
2651                    having_max: None,
2652                    limit: None,
2653                    inferred_type: None,
2654                })))
2655            }
2656
2657            // BOOLOR_AGG/BOOL_OR/LOGICAL_OR -> LogicalOr AST node
2658            "BOOLOR_AGG" | "BOOL_OR" | "LOGICAL_OR" if !f.args.is_empty() => {
2659                let arg = f.args.into_iter().next().unwrap();
2660                Ok(Expression::LogicalOr(Box::new(AggFunc {
2661                    this: arg,
2662                    distinct: false,
2663                    filter: None,
2664                    order_by: Vec::new(),
2665                    name: Some("BOOLOR_AGG".to_string()),
2666                    ignore_nulls: None,
2667                    having_max: None,
2668                    limit: None,
2669                    inferred_type: None,
2670                })))
2671            }
2672
2673            // SKEW -> Skewness AST node for proper cross-dialect handling
2674            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
2675                let arg = f.args.into_iter().next().unwrap();
2676                Ok(Expression::Skewness(Box::new(AggFunc {
2677                    this: arg,
2678                    distinct: false,
2679                    filter: None,
2680                    order_by: Vec::new(),
2681                    name: Some("SKEW".to_string()),
2682                    ignore_nulls: None,
2683                    having_max: None,
2684                    limit: None,
2685                    inferred_type: None,
2686                })))
2687            }
2688
2689            // VAR_SAMP -> VARIANCE (Snowflake uses VARIANCE for sample variance)
2690            "VAR_SAMP" => Ok(Expression::Function(Box::new(Function::new(
2691                "VARIANCE".to_string(),
2692                f.args,
2693            )))),
2694
2695            // VAR_POP -> VARIANCE_POP
2696            "VAR_POP" => Ok(Expression::Function(Box::new(Function::new(
2697                "VARIANCE_POP".to_string(),
2698                f.args,
2699            )))),
2700
2701            // DATE(str) -> TO_DATE(str) (single-arg form)
2702            "DATE" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2703                "TO_DATE".to_string(),
2704                f.args,
2705            )))),
2706            // DATE(str, format) -> TO_DATE(str, normalized_format)
2707            // Python SQLGlot normalizes DATE(...) to TO_DATE(...) for formatted variants.
2708            // But _POLYGLOT_DATE(str, format) stays as DATE() (from BigQuery PARSE_DATE conversion)
2709            "DATE" if f.args.len() >= 2 => {
2710                let mut args = f.args;
2711                args[1] = Self::normalize_format_arg(args[1].clone());
2712                Ok(Expression::Function(Box::new(Function::new(
2713                    "TO_DATE".to_string(),
2714                    args,
2715                ))))
2716            }
2717            // Internal marker from BigQuery PARSE_DATE -> Snowflake conversion
2718            // _POLYGLOT_DATE stays as DATE() (not converted to TO_DATE)
2719            "_POLYGLOT_DATE" if f.args.len() >= 2 => {
2720                let mut args = f.args;
2721                args[1] = Self::normalize_format_arg(args[1].clone());
2722                Ok(Expression::Function(Box::new(Function::new(
2723                    "DATE".to_string(),
2724                    args,
2725                ))))
2726            }
2727
2728            // DESCRIBE/DESC normalization
2729            "DESCRIBE" => Ok(Expression::Function(Box::new(f))),
2730
2731            // MD5 -> MD5 (preserve) but MD5_HEX -> MD5
2732            "MD5_HEX" => Ok(Expression::Function(Box::new(Function::new(
2733                "MD5".to_string(),
2734                f.args,
2735            )))),
2736
2737            // SHA1_HEX -> SHA1
2738            "SHA1_HEX" => Ok(Expression::Function(Box::new(Function::new(
2739                "SHA1".to_string(),
2740                f.args,
2741            )))),
2742
2743            // SHA2_HEX -> SHA2
2744            "SHA2_HEX" => Ok(Expression::Function(Box::new(Function::new(
2745                "SHA2".to_string(),
2746                f.args,
2747            )))),
2748
2749            // EDITDISTANCE -> EDITDISTANCE (preserve Snowflake name)
2750            "LEVENSHTEIN" => Ok(Expression::Function(Box::new(Function::new(
2751                "EDITDISTANCE".to_string(),
2752                f.args,
2753            )))),
2754
2755            // BIT_NOT -> BITNOT
2756            "BIT_NOT" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2757                "BITNOT".to_string(),
2758                f.args,
2759            )))),
2760
2761            // BIT_AND -> BITAND
2762            "BIT_AND" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2763                "BITAND".to_string(),
2764                f.args,
2765            )))),
2766
2767            // BIT_OR -> BITOR
2768            "BIT_OR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2769                "BITOR".to_string(),
2770                f.args,
2771            )))),
2772
2773            // BIT_XOR -> BITXOR
2774            "BIT_XOR" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2775                "BITXOR".to_string(),
2776                f.args,
2777            )))),
2778
2779            // BIT_SHIFTLEFT -> BITSHIFTLEFT
2780            "BIT_SHIFTLEFT" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(
2781                Function::new("BITSHIFTLEFT".to_string(), f.args),
2782            ))),
2783
2784            // BIT_SHIFTRIGHT -> BITSHIFTRIGHT
2785            "BIT_SHIFTRIGHT" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(
2786                Function::new("BITSHIFTRIGHT".to_string(), f.args),
2787            ))),
2788
2789            // SYSTIMESTAMP -> CURRENT_TIMESTAMP (preserving parens style)
2790            "SYSTIMESTAMP" => Ok(Expression::Function(Box::new(Function {
2791                name: "CURRENT_TIMESTAMP".to_string(),
2792                args: f.args,
2793                distinct: false,
2794                trailing_comments: Vec::new(),
2795                use_bracket_syntax: false,
2796                no_parens: f.no_parens,
2797                quoted: false,
2798                span: None,
2799                inferred_type: None,
2800            }))),
2801
2802            // LOCALTIMESTAMP -> CURRENT_TIMESTAMP (preserving parens style)
2803            "LOCALTIMESTAMP" => Ok(Expression::Function(Box::new(Function {
2804                name: "CURRENT_TIMESTAMP".to_string(),
2805                args: f.args,
2806                distinct: false,
2807                trailing_comments: Vec::new(),
2808                use_bracket_syntax: false,
2809                no_parens: f.no_parens,
2810                quoted: false,
2811                span: None,
2812                inferred_type: None,
2813            }))),
2814
2815            // SPACE(n) -> REPEAT(' ', n) in Snowflake
2816            "SPACE" if f.args.len() == 1 => {
2817                let arg = f.args.into_iter().next().unwrap();
2818                Ok(Expression::Function(Box::new(Function::new(
2819                    "REPEAT".to_string(),
2820                    vec![
2821                        Expression::Literal(Box::new(Literal::String(" ".to_string()))),
2822                        arg,
2823                    ],
2824                ))))
2825            }
2826
2827            // CEILING -> CEIL
2828            "CEILING" => Ok(Expression::Function(Box::new(Function::new(
2829                "CEIL".to_string(),
2830                f.args,
2831            )))),
2832
2833            // LOG without base -> LN
2834            "LOG" if f.args.len() == 1 => Ok(Expression::Function(Box::new(Function::new(
2835                "LN".to_string(),
2836                f.args,
2837            )))),
2838
2839            // REGEXP_SUBSTR_ALL is native to Snowflake
2840            "REGEXP_SUBSTR_ALL" => Ok(Expression::Function(Box::new(f))),
2841
2842            // GET_PATH - transform path argument:
2843            // - Convert colon notation to dot notation (y[0]:z -> y[0].z)
2844            // - Wrap unsafe keys in brackets ($id -> ["$id"])
2845            "GET_PATH" if f.args.len() >= 2 => {
2846                let mut args = f.args;
2847                // Transform the path argument (second argument)
2848                if let Expression::Literal(lit) = &args[1] {
2849                    if let crate::expressions::Literal::String(path) = lit.as_ref() {
2850                        let transformed = Self::transform_json_path(path);
2851                        args[1] = Expression::Literal(Box::new(
2852                            crate::expressions::Literal::String(transformed),
2853                        ));
2854                    }
2855                }
2856                Ok(Expression::Function(Box::new(Function::new(
2857                    "GET_PATH".to_string(),
2858                    args,
2859                ))))
2860            }
2861            "GET_PATH" => Ok(Expression::Function(Box::new(f))),
2862
2863            // FLATTEN is native to Snowflake
2864            "FLATTEN" => Ok(Expression::Function(Box::new(f))),
2865
2866            // DATE_TRUNC - transform unit to quoted string
2867            // DATE_TRUNC(yr, x) -> DATE_TRUNC('YEAR', x)
2868            "DATE_TRUNC" if f.args.len() >= 1 => {
2869                let mut args = f.args;
2870                // Transform the unit to canonical form and convert to string literal
2871                let unit_name = match &args[0] {
2872                    Expression::Identifier(id) => Some(id.name.as_str()),
2873                    Expression::Var(v) => Some(v.this.as_str()),
2874                    Expression::Column(col) if col.table.is_none() => Some(col.name.name.as_str()),
2875                    _ => None,
2876                };
2877                if let Some(name) = unit_name {
2878                    let canonical = Self::map_date_part(name).unwrap_or(name);
2879                    args[0] = Expression::Literal(Box::new(crate::expressions::Literal::String(
2880                        canonical.to_uppercase(),
2881                    )));
2882                }
2883                Ok(Expression::Function(Box::new(Function::new(
2884                    "DATE_TRUNC".to_string(),
2885                    args,
2886                ))))
2887            }
2888
2889            // DATE_PART - transform unit argument
2890            // DATE_PART(yyy, x) -> DATE_PART(YEAR, x)
2891            // Only convert string literals to identifiers when the second arg is a typed literal
2892            // (e.g., TIMESTAMP '...', DATE '...'), indicating the function came from another dialect.
2893            // For native Snowflake DATE_PART('month', CAST(...)), preserve the string as-is.
2894            "DATE_PART" if f.args.len() >= 1 => {
2895                let mut args = f.args;
2896                let from_typed_literal = args.len() >= 2
2897                    && matches!(
2898                        &args[1],
2899                        Expression::Literal(lit) if matches!(lit.as_ref(),
2900                            crate::expressions::Literal::Timestamp(_)
2901                            | crate::expressions::Literal::Date(_)
2902                            | crate::expressions::Literal::Time(_)
2903                            | crate::expressions::Literal::Datetime(_)
2904                        )
2905                    );
2906                if from_typed_literal {
2907                    args[0] = self.transform_date_part_arg(args[0].clone());
2908                } else {
2909                    // For non-typed-literal cases, only normalize identifiers/columns
2910                    // (don't convert string literals to identifiers)
2911                    args[0] = self.transform_date_part_arg_identifiers_only(args[0].clone());
2912                }
2913                Ok(Expression::Function(Box::new(Function::new(
2914                    "DATE_PART".to_string(),
2915                    args,
2916                ))))
2917            }
2918
2919            // OBJECT_CONSTRUCT is native to Snowflake
2920            "OBJECT_CONSTRUCT" => Ok(Expression::Function(Box::new(f))),
2921
2922            // OBJECT_CONSTRUCT_KEEP_NULL is native to Snowflake
2923            "OBJECT_CONSTRUCT_KEEP_NULL" => Ok(Expression::Function(Box::new(f))),
2924
2925            // DESC -> DESCRIBE
2926            "DESC" => Ok(Expression::Function(Box::new(Function::new(
2927                "DESCRIBE".to_string(),
2928                f.args,
2929            )))),
2930
2931            // RLIKE -> REGEXP_LIKE
2932            "RLIKE" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2933                "REGEXP_LIKE".to_string(),
2934                f.args,
2935            )))),
2936
2937            // TRANSFORM function - handle typed lambda parameters
2938            // For typed lambdas like `a int -> a + 1`, we need to:
2939            // 1. Remove the type annotation from the parameter
2940            // 2. Wrap all references to the parameter in the body with CAST(param AS type)
2941            "TRANSFORM" => {
2942                let transformed_args: Vec<Expression> = f
2943                    .args
2944                    .into_iter()
2945                    .map(|arg| {
2946                        if let Expression::Lambda(lambda) = arg {
2947                            self.transform_typed_lambda(*lambda)
2948                        } else {
2949                            arg
2950                        }
2951                    })
2952                    .collect();
2953                Ok(Expression::Function(Box::new(Function::new(
2954                    "TRANSFORM".to_string(),
2955                    transformed_args,
2956                ))))
2957            }
2958
2959            // SEARCH function - convert to Search expression with canonical parameter ordering
2960            "SEARCH" if f.args.len() >= 2 => {
2961                let mut args = f.args.into_iter();
2962                let this = Box::new(args.next().unwrap());
2963                let expression = Box::new(args.next().unwrap());
2964
2965                let mut analyzer: Option<Box<Expression>> = None;
2966                let mut search_mode: Option<Box<Expression>> = None;
2967
2968                // Parse remaining named arguments
2969                for arg in args {
2970                    if let Expression::NamedArgument(na) = &arg {
2971                        let name_upper = na.name.name.to_uppercase();
2972                        match name_upper.as_str() {
2973                            "ANALYZER" => analyzer = Some(Box::new(arg)),
2974                            "SEARCH_MODE" => search_mode = Some(Box::new(arg)),
2975                            _ => {}
2976                        }
2977                    }
2978                }
2979
2980                Ok(Expression::Search(Box::new(crate::expressions::Search {
2981                    this,
2982                    expression,
2983                    json_scope: None,
2984                    analyzer,
2985                    analyzer_options: None,
2986                    search_mode,
2987                })))
2988            }
2989
2990            // ODBC CONVERT function: CONVERT(value, SQL_TYPE) -> CAST(value AS TYPE)
2991            // This handles the { fn CONVERT(...) } ODBC escape sequence syntax
2992            "CONVERT" if f.args.len() == 2 => {
2993                let value = f.args.get(0).cloned().unwrap();
2994                let type_arg = f.args.get(1).cloned().unwrap();
2995
2996                // Check if second argument is a SQL_ type identifier
2997                if let Expression::Column(col) = &type_arg {
2998                    let type_name = col.name.name.to_uppercase();
2999                    let data_type = match type_name.as_str() {
3000                        "SQL_DOUBLE" => Some(DataType::Double {
3001                            precision: None,
3002                            scale: None,
3003                        }),
3004                        "SQL_VARCHAR" => Some(DataType::VarChar {
3005                            length: None,
3006                            parenthesized_length: false,
3007                        }),
3008                        "SQL_INTEGER" | "SQL_INT" => Some(DataType::Int {
3009                            length: None,
3010                            integer_spelling: false,
3011                        }),
3012                        "SQL_BIGINT" => Some(DataType::BigInt { length: None }),
3013                        "SQL_SMALLINT" => Some(DataType::SmallInt { length: None }),
3014                        "SQL_FLOAT" => Some(DataType::Float {
3015                            precision: None,
3016                            scale: None,
3017                            real_spelling: false,
3018                        }),
3019                        "SQL_REAL" => Some(DataType::Float {
3020                            precision: None,
3021                            scale: None,
3022                            real_spelling: true,
3023                        }),
3024                        "SQL_DECIMAL" => Some(DataType::Decimal {
3025                            precision: None,
3026                            scale: None,
3027                        }),
3028                        "SQL_DATE" => Some(DataType::Date),
3029                        "SQL_TIME" => Some(DataType::Time {
3030                            precision: None,
3031                            timezone: false,
3032                        }),
3033                        "SQL_TIMESTAMP" => Some(DataType::Timestamp {
3034                            precision: None,
3035                            timezone: false,
3036                        }),
3037                        _ => None,
3038                    };
3039
3040                    if let Some(dt) = data_type {
3041                        return Ok(Expression::Cast(Box::new(Cast {
3042                            this: value,
3043                            to: dt,
3044                            double_colon_syntax: false,
3045                            trailing_comments: vec![],
3046                            format: None,
3047                            default: None,
3048                            inferred_type: None,
3049                        })));
3050                    }
3051                }
3052                // If not a SQL_ type, keep as regular CONVERT function
3053                Ok(Expression::Function(Box::new(f)))
3054            }
3055
3056            // TO_TIMESTAMP_TZ: single string arg -> CAST(... AS TIMESTAMPTZ), otherwise keep as function
3057            // Per Python sqlglot: _build_datetime converts TO_TIMESTAMP_TZ('string') to CAST('string' AS TIMESTAMPTZ)
3058            "TO_TIMESTAMP_TZ" => {
3059                if f.args.len() == 1 {
3060                    if let Expression::Literal(lit) = &f.args[0] {
3061                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3062                            return Ok(Expression::Cast(Box::new(Cast {
3063                                this: f.args.into_iter().next().unwrap(),
3064                                to: DataType::Custom {
3065                                    name: "TIMESTAMPTZ".to_string(),
3066                                },
3067                                double_colon_syntax: false,
3068                                trailing_comments: vec![],
3069                                format: None,
3070                                default: None,
3071                                inferred_type: None,
3072                            })));
3073                        }
3074                    }
3075                }
3076                Ok(Expression::Function(Box::new(f)))
3077            }
3078
3079            // TO_TIMESTAMP_NTZ: single string arg -> CAST(... AS TIMESTAMPNTZ), otherwise keep as function
3080            "TO_TIMESTAMP_NTZ" => {
3081                if f.args.len() == 1 {
3082                    if let Expression::Literal(lit) = &f.args[0] {
3083                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3084                            return Ok(Expression::Cast(Box::new(Cast {
3085                                this: f.args.into_iter().next().unwrap(),
3086                                to: DataType::Custom {
3087                                    name: "TIMESTAMPNTZ".to_string(),
3088                                },
3089                                double_colon_syntax: false,
3090                                trailing_comments: vec![],
3091                                format: None,
3092                                default: None,
3093                                inferred_type: None,
3094                            })));
3095                        }
3096                    }
3097                }
3098                Ok(Expression::Function(Box::new(f)))
3099            }
3100
3101            // TO_TIMESTAMP_LTZ: single string arg -> CAST(... AS TIMESTAMPLTZ), otherwise keep as function
3102            "TO_TIMESTAMP_LTZ" => {
3103                if f.args.len() == 1 {
3104                    if let Expression::Literal(lit) = &f.args[0] {
3105                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3106                            return Ok(Expression::Cast(Box::new(Cast {
3107                                this: f.args.into_iter().next().unwrap(),
3108                                to: DataType::Custom {
3109                                    name: "TIMESTAMPLTZ".to_string(),
3110                                },
3111                                double_colon_syntax: false,
3112                                trailing_comments: vec![],
3113                                format: None,
3114                                default: None,
3115                                inferred_type: None,
3116                            })));
3117                        }
3118                    }
3119                }
3120                Ok(Expression::Function(Box::new(f)))
3121            }
3122
3123            // UNIFORM -> keep as-is (Snowflake-specific)
3124            "UNIFORM" => Ok(Expression::Function(Box::new(f))),
3125
3126            // REPLACE with 2 args -> add empty string 3rd arg
3127            "REPLACE" if f.args.len() == 2 => {
3128                let mut args = f.args;
3129                args.push(Expression::Literal(Box::new(
3130                    crate::expressions::Literal::String(String::new()),
3131                )));
3132                Ok(Expression::Function(Box::new(Function::new(
3133                    "REPLACE".to_string(),
3134                    args,
3135                ))))
3136            }
3137
3138            // ARBITRARY -> ANY_VALUE in Snowflake
3139            "ARBITRARY" => Ok(Expression::Function(Box::new(Function::new(
3140                "ANY_VALUE".to_string(),
3141                f.args,
3142            )))),
3143
3144            // SAFE_DIVIDE(x, y) -> IFF(y <> 0, x / y, NULL)
3145            "SAFE_DIVIDE" if f.args.len() == 2 => {
3146                let mut args = f.args;
3147                let x = args.remove(0);
3148                let y = args.remove(0);
3149                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3150                    condition: Expression::Neq(Box::new(BinaryOp {
3151                        left: y.clone(),
3152                        right: Expression::number(0),
3153                        left_comments: Vec::new(),
3154                        operator_comments: Vec::new(),
3155                        trailing_comments: Vec::new(),
3156                        inferred_type: None,
3157                    })),
3158                    true_value: Expression::Div(Box::new(BinaryOp {
3159                        left: x,
3160                        right: y,
3161                        left_comments: Vec::new(),
3162                        operator_comments: Vec::new(),
3163                        trailing_comments: Vec::new(),
3164                        inferred_type: None,
3165                    })),
3166                    false_value: Some(Expression::Null(crate::expressions::Null)),
3167                    original_name: Some("IFF".to_string()),
3168                    inferred_type: None,
3169                })))
3170            }
3171
3172            // TIMESTAMP(x) -> CAST(x AS TIMESTAMPTZ) in Snowflake
3173            "TIMESTAMP" if f.args.len() == 1 => {
3174                let arg = f.args.into_iter().next().unwrap();
3175                Ok(Expression::Cast(Box::new(Cast {
3176                    this: arg,
3177                    to: DataType::Custom {
3178                        name: "TIMESTAMPTZ".to_string(),
3179                    },
3180                    trailing_comments: Vec::new(),
3181                    double_colon_syntax: false,
3182                    format: None,
3183                    default: None,
3184                    inferred_type: None,
3185                })))
3186            }
3187
3188            // TIMESTAMP(x, tz) -> CONVERT_TIMEZONE(tz, CAST(x AS TIMESTAMP)) in Snowflake
3189            "TIMESTAMP" if f.args.len() == 2 => {
3190                let mut args = f.args;
3191                let value = args.remove(0);
3192                let tz = args.remove(0);
3193                Ok(Expression::Function(Box::new(Function::new(
3194                    "CONVERT_TIMEZONE".to_string(),
3195                    vec![
3196                        tz,
3197                        Expression::Cast(Box::new(Cast {
3198                            this: value,
3199                            to: DataType::Timestamp {
3200                                precision: None,
3201                                timezone: false,
3202                            },
3203                            trailing_comments: Vec::new(),
3204                            double_colon_syntax: false,
3205                            format: None,
3206                            default: None,
3207                            inferred_type: None,
3208                        })),
3209                    ],
3210                ))))
3211            }
3212
3213            // TIME(h, m, s) -> TIME_FROM_PARTS(h, m, s) in Snowflake
3214            "TIME" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
3215                "TIME_FROM_PARTS".to_string(),
3216                f.args,
3217            )))),
3218
3219            // DIV0(x, y) -> IFF(y = 0 AND NOT x IS NULL, 0, x / y)
3220            "DIV0" if f.args.len() == 2 => {
3221                let mut args = f.args;
3222                let x = args.remove(0);
3223                let y = args.remove(0);
3224                // Need parens around complex expressions
3225                let x_expr = Self::maybe_paren(x.clone());
3226                let y_expr = Self::maybe_paren(y.clone());
3227                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3228                    condition: Expression::And(Box::new(BinaryOp::new(
3229                        Expression::Eq(Box::new(BinaryOp::new(
3230                            y_expr.clone(),
3231                            Expression::number(0),
3232                        ))),
3233                        Expression::Not(Box::new(crate::expressions::UnaryOp {
3234                            this: Expression::IsNull(Box::new(crate::expressions::IsNull {
3235                                this: x_expr.clone(),
3236                                not: false,
3237                                postfix_form: false,
3238                            })),
3239                            inferred_type: None,
3240                        })),
3241                    ))),
3242                    true_value: Expression::number(0),
3243                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3244                    original_name: Some("IFF".to_string()),
3245                    inferred_type: None,
3246                })))
3247            }
3248
3249            // DIV0NULL(x, y) -> IFF(y = 0 OR y IS NULL, 0, x / y)
3250            "DIV0NULL" if f.args.len() == 2 => {
3251                let mut args = f.args;
3252                let x = args.remove(0);
3253                let y = args.remove(0);
3254                let x_expr = Self::maybe_paren(x.clone());
3255                let y_expr = Self::maybe_paren(y.clone());
3256                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3257                    condition: Expression::Or(Box::new(BinaryOp::new(
3258                        Expression::Eq(Box::new(BinaryOp::new(
3259                            y_expr.clone(),
3260                            Expression::number(0),
3261                        ))),
3262                        Expression::IsNull(Box::new(crate::expressions::IsNull {
3263                            this: y_expr.clone(),
3264                            not: false,
3265                            postfix_form: false,
3266                        })),
3267                    ))),
3268                    true_value: Expression::number(0),
3269                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3270                    original_name: Some("IFF".to_string()),
3271                    inferred_type: None,
3272                })))
3273            }
3274
3275            // ZEROIFNULL(x) -> IFF(x IS NULL, 0, x)
3276            "ZEROIFNULL" if f.args.len() == 1 => {
3277                let x = f.args.into_iter().next().unwrap();
3278                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3279                    condition: Expression::IsNull(Box::new(crate::expressions::IsNull {
3280                        this: x.clone(),
3281                        not: false,
3282                        postfix_form: false,
3283                    })),
3284                    true_value: Expression::number(0),
3285                    false_value: Some(x),
3286                    original_name: Some("IFF".to_string()),
3287                    inferred_type: None,
3288                })))
3289            }
3290
3291            // NULLIFZERO(x) -> IFF(x = 0, NULL, x)
3292            "NULLIFZERO" if f.args.len() == 1 => {
3293                let x = f.args.into_iter().next().unwrap();
3294                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3295                    condition: Expression::Eq(Box::new(BinaryOp::new(
3296                        x.clone(),
3297                        Expression::number(0),
3298                    ))),
3299                    true_value: Expression::Null(crate::expressions::Null),
3300                    false_value: Some(x),
3301                    original_name: Some("IFF".to_string()),
3302                    inferred_type: None,
3303                })))
3304            }
3305
3306            // TRY_TO_TIME('string') -> TRY_CAST('string' AS TIME) when single string arg
3307            "TRY_TO_TIME" => {
3308                if f.args.len() == 1 {
3309                    if let Expression::Literal(lit) = &f.args[0] {
3310                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
3311                            return Ok(Expression::TryCast(Box::new(Cast {
3312                                this: f.args.into_iter().next().unwrap(),
3313                                to: crate::expressions::DataType::Time {
3314                                    precision: None,
3315                                    timezone: false,
3316                                },
3317                                double_colon_syntax: false,
3318                                trailing_comments: Vec::new(),
3319                                format: None,
3320                                default: None,
3321                                inferred_type: None,
3322                            })));
3323                        }
3324                    }
3325                }
3326                // Normalize format string (2nd arg) if present
3327                let mut args = f.args;
3328                if args.len() >= 2 {
3329                    args[1] = Self::normalize_format_arg(args[1].clone());
3330                }
3331                Ok(Expression::Function(Box::new(Function::new(
3332                    "TRY_TO_TIME".to_string(),
3333                    args,
3334                ))))
3335            }
3336
3337            // TRY_TO_TIMESTAMP('string') -> TRY_CAST('string' AS TIMESTAMP) when single string arg
3338            // Convert if the string is NOT a pure numeric/epoch value
3339            "TRY_TO_TIMESTAMP" => {
3340                if f.args.len() == 1 {
3341                    if let Expression::Literal(lit) = &f.args[0] {
3342                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
3343                            if !Self::looks_like_epoch(s) {
3344                                return Ok(Expression::TryCast(Box::new(Cast {
3345                                    this: f.args.into_iter().next().unwrap(),
3346                                    to: DataType::Timestamp {
3347                                        precision: None,
3348                                        timezone: false,
3349                                    },
3350                                    double_colon_syntax: false,
3351                                    trailing_comments: Vec::new(),
3352                                    format: None,
3353                                    default: None,
3354                                    inferred_type: None,
3355                                })));
3356                            }
3357                        }
3358                    }
3359                }
3360                // Normalize format string (2nd arg) if present
3361                let mut args = f.args;
3362                if args.len() >= 2 {
3363                    args[1] = Self::normalize_format_arg(args[1].clone());
3364                }
3365                Ok(Expression::Function(Box::new(Function::new(
3366                    "TRY_TO_TIMESTAMP".to_string(),
3367                    args,
3368                ))))
3369            }
3370
3371            // TRY_TO_DATE('string') -> TRY_CAST('string' AS DATE) when single string arg
3372            "TRY_TO_DATE" => {
3373                if f.args.len() == 1 {
3374                    if let Expression::Literal(lit) = &f.args[0] {
3375                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
3376                            // Only convert if the string looks like a date
3377                            if s.contains('-') && s.len() >= 8 && s.len() <= 12 {
3378                                return Ok(Expression::TryCast(Box::new(Cast {
3379                                    this: f.args.into_iter().next().unwrap(),
3380                                    to: crate::expressions::DataType::Date,
3381                                    double_colon_syntax: false,
3382                                    trailing_comments: Vec::new(),
3383                                    format: None,
3384                                    default: None,
3385                                    inferred_type: None,
3386                                })));
3387                            }
3388                        }
3389                    }
3390                }
3391                // Normalize format string (2nd arg) if present
3392                let mut args = f.args;
3393                if args.len() >= 2 {
3394                    args[1] = Self::normalize_format_arg(args[1].clone());
3395                }
3396                Ok(Expression::Function(Box::new(Function::new(
3397                    "TRY_TO_DATE".to_string(),
3398                    args,
3399                ))))
3400            }
3401
3402            // Canonicalize TRY_TO_DOUBLE so targets with nullable conversion
3403            // functions can preserve Snowflake's NULL-on-failure behavior.
3404            "TRY_TO_DOUBLE" if f.args.len() == 1 => {
3405                let this = f.args.into_iter().next().unwrap();
3406                Ok(Expression::ToDouble(Box::new(
3407                    crate::expressions::ToDouble {
3408                        this: Box::new(this),
3409                        format: None,
3410                        safe: Some(Box::new(Expression::Boolean(
3411                            crate::expressions::BooleanLiteral { value: true },
3412                        ))),
3413                    },
3414                )))
3415            }
3416            "TRY_TO_DOUBLE"
3417                if f.args.len() == 2
3418                    && matches!(&f.args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))) =>
3419            {
3420                let mut args = f.args;
3421                let this = args.remove(0);
3422                let Literal::String(format) = (match args.remove(0) {
3423                    Expression::Literal(lit) => *lit,
3424                    _ => unreachable!(),
3425                }) else {
3426                    unreachable!()
3427                };
3428                Ok(Expression::ToDouble(Box::new(
3429                    crate::expressions::ToDouble {
3430                        this: Box::new(this),
3431                        format: Some(format),
3432                        safe: Some(Box::new(Expression::Boolean(
3433                            crate::expressions::BooleanLiteral { value: true },
3434                        ))),
3435                    },
3436                )))
3437            }
3438            "TRY_TO_DOUBLE" => Ok(Expression::Function(Box::new(f))),
3439
3440            // REGEXP_REPLACE with 2 args -> add empty string replacement
3441            "REGEXP_REPLACE" if f.args.len() == 2 => {
3442                let mut args = f.args;
3443                args.push(Expression::Literal(Box::new(
3444                    crate::expressions::Literal::String(String::new()),
3445                )));
3446                Ok(Expression::Function(Box::new(Function::new(
3447                    "REGEXP_REPLACE".to_string(),
3448                    args,
3449                ))))
3450            }
3451
3452            // LAST_DAY(x, MONTH) -> LAST_DAY(x) in Snowflake (strip MONTH default)
3453            "LAST_DAY" if f.args.len() == 2 => {
3454                let mut args = f.args;
3455                let date = args.remove(0);
3456                let unit = args.remove(0);
3457                let unit_str = match &unit {
3458                    Expression::Column(c) => c.name.name.to_uppercase(),
3459                    Expression::Identifier(i) => i.name.to_uppercase(),
3460                    _ => String::new(),
3461                };
3462                if unit_str == "MONTH" {
3463                    Ok(Expression::Function(Box::new(Function::new(
3464                        "LAST_DAY".to_string(),
3465                        vec![date],
3466                    ))))
3467                } else {
3468                    Ok(Expression::Function(Box::new(Function::new(
3469                        "LAST_DAY".to_string(),
3470                        vec![date, unit],
3471                    ))))
3472                }
3473            }
3474
3475            // EXTRACT('field', expr) function-call syntax -> DATE_PART('field', expr)
3476            "EXTRACT" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
3477                "DATE_PART".to_string(),
3478                f.args,
3479            )))),
3480
3481            // ENDS_WITH/ENDSWITH -> EndsWith AST node
3482            "ENDS_WITH" | "ENDSWITH" if f.args.len() == 2 => {
3483                let mut args = f.args;
3484                let this = args.remove(0);
3485                let expr = args.remove(0);
3486                Ok(Expression::EndsWith(Box::new(
3487                    crate::expressions::BinaryFunc {
3488                        original_name: None,
3489                        this,
3490                        expression: expr,
3491                        inferred_type: None,
3492                    },
3493                )))
3494            }
3495
3496            // Pass through everything else
3497            _ => Ok(Expression::Function(Box::new(f))),
3498        }
3499    }
3500
3501    /// Check if a string looks like a datetime (contains date separators, not just digits)
3502    fn looks_like_datetime(s: &str) -> bool {
3503        // A datetime string typically contains dashes, colons, or spaces
3504        // A numeric/epoch string is just digits (possibly with a dot)
3505        s.contains('-') || s.contains(':') || s.contains(' ') || s.contains('/')
3506    }
3507
3508    /// Check if a string looks like an epoch number (only digits, possibly with a dot)
3509    fn looks_like_epoch(s: &str) -> bool {
3510        !s.is_empty() && s.chars().all(|c| c.is_ascii_digit() || c == '.')
3511    }
3512
3513    /// Wrap an expression in parentheses if it's a complex expression (binary op, etc.)
3514    fn maybe_paren(expr: Expression) -> Expression {
3515        match &expr {
3516            Expression::Sub(_) | Expression::Add(_) | Expression::Mul(_) | Expression::Div(_) => {
3517                Expression::Paren(Box::new(crate::expressions::Paren {
3518                    this: expr,
3519                    trailing_comments: Vec::new(),
3520                }))
3521            }
3522            _ => expr,
3523        }
3524    }
3525
3526    /// Normalize Snowflake date/time format strings to canonical lowercase form.
3527    /// YYYY -> yyyy, MM -> mm, DD -> DD (stays), HH24 -> hh24, HH12 -> hh12,
3528    /// MI -> mi, SS -> ss, FF -> ff, AM/PM -> pm, quoted "T" -> T
3529    fn normalize_snowflake_format(format: &str) -> String {
3530        let mut result = String::new();
3531        let chars: Vec<char> = format.chars().collect();
3532        let mut i = 0;
3533        while i < chars.len() {
3534            // Handle quoted strings like "T" -> T
3535            if chars[i] == '"' {
3536                i += 1;
3537                while i < chars.len() && chars[i] != '"' {
3538                    result.push(chars[i]);
3539                    i += 1;
3540                }
3541                if i < chars.len() {
3542                    i += 1; // skip closing quote
3543                }
3544                continue;
3545            }
3546
3547            let remaining = &format[i..];
3548            let remaining_upper = remaining.to_uppercase();
3549
3550            // Multi-char patterns (check longest first)
3551            if remaining_upper.starts_with("YYYY") {
3552                result.push_str("yyyy");
3553                i += 4;
3554            } else if remaining_upper.starts_with("YY") {
3555                result.push_str("yy");
3556                i += 2;
3557            } else if remaining_upper.starts_with("MMMM") {
3558                result.push_str("mmmm");
3559                i += 4;
3560            } else if remaining_upper.starts_with("MON") {
3561                result.push_str("mon");
3562                i += 3;
3563            } else if remaining_upper.starts_with("MM") {
3564                result.push_str("mm");
3565                i += 2;
3566            } else if remaining_upper.starts_with("DD") {
3567                result.push_str("DD");
3568                i += 2;
3569            } else if remaining_upper.starts_with("DY") {
3570                result.push_str("dy");
3571                i += 2;
3572            } else if remaining_upper.starts_with("HH24") {
3573                result.push_str("hh24");
3574                i += 4;
3575            } else if remaining_upper.starts_with("HH12") {
3576                result.push_str("hh12");
3577                i += 4;
3578            } else if remaining_upper.starts_with("HH") {
3579                result.push_str("hh");
3580                i += 2;
3581            } else if remaining_upper.starts_with("MISS") {
3582                // MISS = MI + SS
3583                result.push_str("miss");
3584                i += 4;
3585            } else if remaining_upper.starts_with("MI") {
3586                result.push_str("mi");
3587                i += 2;
3588            } else if remaining_upper.starts_with("SS") {
3589                result.push_str("ss");
3590                i += 2;
3591            } else if remaining_upper.starts_with("FF") {
3592                // FF followed by a digit (FF1-FF9) keeps the digit
3593                let ff_len = 2;
3594                let digit = if i + ff_len < chars.len() && chars[i + ff_len].is_ascii_digit() {
3595                    let d = chars[i + ff_len];
3596                    Some(d)
3597                } else {
3598                    None
3599                };
3600                if let Some(d) = digit {
3601                    result.push_str("ff");
3602                    result.push(d);
3603                    i += 3;
3604                } else {
3605                    // Plain FF -> ff9
3606                    result.push_str("ff9");
3607                    i += 2;
3608                }
3609            } else if remaining_upper.starts_with("AM") || remaining_upper.starts_with("PM") {
3610                result.push_str("pm");
3611                i += 2;
3612            } else if remaining_upper.starts_with("TZH") {
3613                result.push_str("tzh");
3614                i += 3;
3615            } else if remaining_upper.starts_with("TZM") {
3616                result.push_str("tzm");
3617                i += 3;
3618            } else {
3619                // Keep separators and other characters as-is
3620                result.push(chars[i]);
3621                i += 1;
3622            }
3623        }
3624        result
3625    }
3626
3627    fn is_temporal_expr(expr: &Expression) -> bool {
3628        match expr {
3629            Expression::CurrentDate(_)
3630            | Expression::CurrentTime(_)
3631            | Expression::CurrentTimestamp(_)
3632            | Expression::CurrentTimestampLTZ(_)
3633            | Expression::CurrentDatetime(_)
3634            | Expression::Localtime(_)
3635            | Expression::Localtimestamp(_)
3636            | Expression::Systimestamp(_)
3637            | Expression::UtcTime(_)
3638            | Expression::UtcTimestamp(_)
3639            | Expression::Date(_)
3640            | Expression::Time(_)
3641            | Expression::ToDate(_)
3642            | Expression::ToTimestamp(_)
3643            | Expression::DateStrToDate(_)
3644            | Expression::TimeStrToTime(_)
3645            | Expression::StrToTime(_) => true,
3646            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
3647                matches!(
3648                    cast.to,
3649                    DataType::Date | DataType::Time { .. } | DataType::Timestamp { .. }
3650                )
3651            }
3652            Expression::Paren(paren) => Self::is_temporal_expr(&paren.this),
3653            other => matches!(
3654                other.inferred_type(),
3655                Some(DataType::Date | DataType::Time { .. } | DataType::Timestamp { .. })
3656            ),
3657        }
3658    }
3659
3660    /// Normalize format string argument if it's a string literal
3661    fn normalize_format_arg(expr: Expression) -> Expression {
3662        if let Expression::Literal(lit) = &expr {
3663            if let crate::expressions::Literal::String(s) = lit.as_ref() {
3664                let normalized = Self::normalize_snowflake_format(s);
3665                Expression::Literal(Box::new(crate::expressions::Literal::String(normalized)))
3666            } else {
3667                expr.clone()
3668            }
3669        } else {
3670            expr
3671        }
3672    }
3673
3674    /// Transform a lambda with typed parameters for Snowflake
3675    /// For `a int -> a + a + 1`, transforms to `a -> CAST(a AS INT) + CAST(a AS INT) + 1`
3676    fn transform_typed_lambda(&self, lambda: crate::expressions::LambdaExpr) -> Expression {
3677        use crate::expressions::{DataType, LambdaExpr};
3678        use std::collections::HashMap;
3679
3680        // Build mapping of parameter names to their types
3681        let mut param_types: HashMap<String, DataType> = HashMap::new();
3682        for (i, param) in lambda.parameters.iter().enumerate() {
3683            if let Some(Some(dt)) = lambda.parameter_types.get(i) {
3684                param_types.insert(param.name.to_uppercase(), dt.clone());
3685            }
3686        }
3687
3688        // If no typed parameters, return lambda unchanged
3689        if param_types.is_empty() {
3690            return Expression::Lambda(Box::new(lambda));
3691        }
3692
3693        // Transform the body by replacing parameter references with CAST expressions
3694        let transformed_body = self.replace_lambda_params_with_cast(lambda.body, &param_types);
3695
3696        // Return new lambda without type annotations (they're now embedded in CAST)
3697        Expression::Lambda(Box::new(LambdaExpr {
3698            parameters: lambda.parameters,
3699            body: transformed_body,
3700            colon: lambda.colon,
3701            parameter_types: Vec::new(), // Clear type annotations
3702        }))
3703    }
3704
3705    /// Recursively replace column/identifier references to typed lambda parameters with CAST expressions
3706    fn replace_lambda_params_with_cast(
3707        &self,
3708        expr: Expression,
3709        param_types: &std::collections::HashMap<String, crate::expressions::DataType>,
3710    ) -> Expression {
3711        use crate::expressions::{BinaryOp, Cast, Paren};
3712
3713        match expr {
3714            // Column reference - check if it matches a typed parameter
3715            Expression::Column(col) if col.table.is_none() => {
3716                let name_upper = col.name.name.to_uppercase();
3717                if let Some(dt) = param_types.get(&name_upper) {
3718                    // Wrap in CAST
3719                    Expression::Cast(Box::new(Cast {
3720                        this: Expression::Column(col),
3721                        to: dt.clone(),
3722                        double_colon_syntax: false,
3723                        trailing_comments: Vec::new(),
3724                        format: None,
3725                        default: None,
3726                        inferred_type: None,
3727                    }))
3728                } else {
3729                    Expression::Column(col)
3730                }
3731            }
3732
3733            // Identifier reference - check if it matches a typed parameter
3734            Expression::Identifier(id) => {
3735                let name_upper = id.name.to_uppercase();
3736                if let Some(dt) = param_types.get(&name_upper) {
3737                    // Wrap in CAST
3738                    Expression::Cast(Box::new(Cast {
3739                        this: Expression::Identifier(id),
3740                        to: dt.clone(),
3741                        double_colon_syntax: false,
3742                        trailing_comments: Vec::new(),
3743                        format: None,
3744                        default: None,
3745                        inferred_type: None,
3746                    }))
3747                } else {
3748                    Expression::Identifier(id)
3749                }
3750            }
3751
3752            // Binary operations - recursively transform both sides
3753            Expression::Add(op) => Expression::Add(Box::new(BinaryOp::new(
3754                self.replace_lambda_params_with_cast(op.left, param_types),
3755                self.replace_lambda_params_with_cast(op.right, param_types),
3756            ))),
3757            Expression::Sub(op) => Expression::Sub(Box::new(BinaryOp::new(
3758                self.replace_lambda_params_with_cast(op.left, param_types),
3759                self.replace_lambda_params_with_cast(op.right, param_types),
3760            ))),
3761            Expression::Mul(op) => Expression::Mul(Box::new(BinaryOp::new(
3762                self.replace_lambda_params_with_cast(op.left, param_types),
3763                self.replace_lambda_params_with_cast(op.right, param_types),
3764            ))),
3765            Expression::Div(op) => Expression::Div(Box::new(BinaryOp::new(
3766                self.replace_lambda_params_with_cast(op.left, param_types),
3767                self.replace_lambda_params_with_cast(op.right, param_types),
3768            ))),
3769            Expression::Mod(op) => Expression::Mod(Box::new(BinaryOp::new(
3770                self.replace_lambda_params_with_cast(op.left, param_types),
3771                self.replace_lambda_params_with_cast(op.right, param_types),
3772            ))),
3773
3774            // Parenthesized expression
3775            Expression::Paren(p) => Expression::Paren(Box::new(Paren {
3776                this: self.replace_lambda_params_with_cast(p.this, param_types),
3777                trailing_comments: p.trailing_comments,
3778            })),
3779
3780            // Function calls - transform arguments
3781            Expression::Function(mut f) => {
3782                f.args = f
3783                    .args
3784                    .into_iter()
3785                    .map(|arg| self.replace_lambda_params_with_cast(arg, param_types))
3786                    .collect();
3787                Expression::Function(f)
3788            }
3789
3790            // Comparison operators
3791            Expression::Eq(op) => Expression::Eq(Box::new(BinaryOp::new(
3792                self.replace_lambda_params_with_cast(op.left, param_types),
3793                self.replace_lambda_params_with_cast(op.right, param_types),
3794            ))),
3795            Expression::Neq(op) => Expression::Neq(Box::new(BinaryOp::new(
3796                self.replace_lambda_params_with_cast(op.left, param_types),
3797                self.replace_lambda_params_with_cast(op.right, param_types),
3798            ))),
3799            Expression::Lt(op) => Expression::Lt(Box::new(BinaryOp::new(
3800                self.replace_lambda_params_with_cast(op.left, param_types),
3801                self.replace_lambda_params_with_cast(op.right, param_types),
3802            ))),
3803            Expression::Lte(op) => Expression::Lte(Box::new(BinaryOp::new(
3804                self.replace_lambda_params_with_cast(op.left, param_types),
3805                self.replace_lambda_params_with_cast(op.right, param_types),
3806            ))),
3807            Expression::Gt(op) => Expression::Gt(Box::new(BinaryOp::new(
3808                self.replace_lambda_params_with_cast(op.left, param_types),
3809                self.replace_lambda_params_with_cast(op.right, param_types),
3810            ))),
3811            Expression::Gte(op) => Expression::Gte(Box::new(BinaryOp::new(
3812                self.replace_lambda_params_with_cast(op.left, param_types),
3813                self.replace_lambda_params_with_cast(op.right, param_types),
3814            ))),
3815
3816            // And/Or
3817            Expression::And(op) => Expression::And(Box::new(BinaryOp::new(
3818                self.replace_lambda_params_with_cast(op.left, param_types),
3819                self.replace_lambda_params_with_cast(op.right, param_types),
3820            ))),
3821            Expression::Or(op) => Expression::Or(Box::new(BinaryOp::new(
3822                self.replace_lambda_params_with_cast(op.left, param_types),
3823                self.replace_lambda_params_with_cast(op.right, param_types),
3824            ))),
3825
3826            // Other expressions - return unchanged
3827            other => other,
3828        }
3829    }
3830
3831    fn transform_aggregate_function(
3832        &self,
3833        f: Box<crate::expressions::AggregateFunction>,
3834    ) -> Result<Expression> {
3835        let name_upper = f.name.to_uppercase();
3836        match name_upper.as_str() {
3837            // GROUP_CONCAT -> LISTAGG
3838            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3839                Function::new("LISTAGG".to_string(), f.args),
3840            ))),
3841
3842            // STRING_AGG -> LISTAGG
3843            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3844                Function::new("LISTAGG".to_string(), f.args),
3845            ))),
3846
3847            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT
3848            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3849                Function::new("APPROX_COUNT_DISTINCT".to_string(), f.args),
3850            ))),
3851
3852            // BIT_AND -> BITAND_AGG
3853            "BIT_AND" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3854                "BITAND_AGG".to_string(),
3855                f.args,
3856            )))),
3857
3858            // BIT_OR -> BITOR_AGG
3859            "BIT_OR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3860                "BITOR_AGG".to_string(),
3861                f.args,
3862            )))),
3863
3864            // BIT_XOR -> BITXOR_AGG
3865            "BIT_XOR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3866                "BITXOR_AGG".to_string(),
3867                f.args,
3868            )))),
3869
3870            // BOOL_AND/BOOLAND_AGG/LOGICAL_AND -> LogicalAnd AST node
3871            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" if !f.args.is_empty() => {
3872                let arg = f.args.into_iter().next().unwrap();
3873                Ok(Expression::LogicalAnd(Box::new(AggFunc {
3874                    this: arg,
3875                    distinct: f.distinct,
3876                    filter: f.filter,
3877                    order_by: Vec::new(),
3878                    name: Some("BOOLAND_AGG".to_string()),
3879                    ignore_nulls: None,
3880                    having_max: None,
3881                    limit: None,
3882                    inferred_type: None,
3883                })))
3884            }
3885
3886            // BOOL_OR/BOOLOR_AGG/LOGICAL_OR -> LogicalOr AST node
3887            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if !f.args.is_empty() => {
3888                let arg = f.args.into_iter().next().unwrap();
3889                Ok(Expression::LogicalOr(Box::new(AggFunc {
3890                    this: arg,
3891                    distinct: f.distinct,
3892                    filter: f.filter,
3893                    order_by: Vec::new(),
3894                    name: Some("BOOLOR_AGG".to_string()),
3895                    ignore_nulls: None,
3896                    having_max: None,
3897                    limit: None,
3898                    inferred_type: None,
3899                })))
3900            }
3901
3902            // APPROX_TOP_K - add default k=1 if only one argument
3903            "APPROX_TOP_K" if f.args.len() == 1 => {
3904                let mut args = f.args;
3905                args.push(Expression::number(1));
3906                Ok(Expression::AggregateFunction(Box::new(
3907                    crate::expressions::AggregateFunction {
3908                        name: "APPROX_TOP_K".to_string(),
3909                        args,
3910                        distinct: f.distinct,
3911                        filter: f.filter,
3912                        order_by: Vec::new(),
3913                        limit: None,
3914                        ignore_nulls: None,
3915                        inferred_type: None,
3916                    },
3917                )))
3918            }
3919
3920            // SKEW/SKEWNESS -> Skewness AST node
3921            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
3922                let arg = f.args.into_iter().next().unwrap();
3923                Ok(Expression::Skewness(Box::new(AggFunc {
3924                    this: arg,
3925                    distinct: f.distinct,
3926                    filter: f.filter,
3927                    order_by: Vec::new(),
3928                    name: Some("SKEW".to_string()),
3929                    ignore_nulls: None,
3930                    having_max: None,
3931                    limit: None,
3932                    inferred_type: None,
3933                })))
3934            }
3935
3936            // Pass through everything else
3937            _ => Ok(Expression::AggregateFunction(f)),
3938        }
3939    }
3940}
3941
3942/// Convert strftime format specifiers to Snowflake format specifiers
3943fn strftime_to_snowflake_format(fmt: &str) -> String {
3944    let mut result = String::new();
3945    let chars: Vec<char> = fmt.chars().collect();
3946    let mut i = 0;
3947    while i < chars.len() {
3948        if chars[i] == '%' && i + 1 < chars.len() {
3949            match chars[i + 1] {
3950                'Y' => {
3951                    result.push_str("yyyy");
3952                    i += 2;
3953                }
3954                'y' => {
3955                    result.push_str("yy");
3956                    i += 2;
3957                }
3958                'm' => {
3959                    result.push_str("mm");
3960                    i += 2;
3961                }
3962                'd' => {
3963                    result.push_str("DD");
3964                    i += 2;
3965                }
3966                'H' => {
3967                    result.push_str("hh24");
3968                    i += 2;
3969                }
3970                'M' => {
3971                    result.push_str("mmmm");
3972                    i += 2;
3973                } // %M = full month name
3974                'i' => {
3975                    result.push_str("mi");
3976                    i += 2;
3977                }
3978                'S' | 's' => {
3979                    result.push_str("ss");
3980                    i += 2;
3981                }
3982                'f' => {
3983                    result.push_str("ff");
3984                    i += 2;
3985                }
3986                'w' => {
3987                    result.push_str("dy");
3988                    i += 2;
3989                } // day of week number
3990                'a' => {
3991                    result.push_str("DY");
3992                    i += 2;
3993                } // abbreviated day name
3994                'b' => {
3995                    result.push_str("mon");
3996                    i += 2;
3997                } // abbreviated month name
3998                'T' => {
3999                    result.push_str("hh24:mi:ss");
4000                    i += 2;
4001                } // time shorthand
4002                _ => {
4003                    result.push(chars[i]);
4004                    result.push(chars[i + 1]);
4005                    i += 2;
4006                }
4007            }
4008        } else {
4009            result.push(chars[i]);
4010            i += 1;
4011        }
4012    }
4013    result
4014}
4015
4016#[cfg(test)]
4017mod tests {
4018    use super::*;
4019    use crate::dialects::Dialect;
4020
4021    fn transpile_to_snowflake(sql: &str) -> String {
4022        let dialect = Dialect::get(DialectType::Generic);
4023        let result = dialect
4024            .transpile(sql, DialectType::Snowflake)
4025            .expect("Transpile failed");
4026        result[0].clone()
4027    }
4028
4029    #[test]
4030    fn test_ifnull_to_coalesce() {
4031        let result = transpile_to_snowflake("SELECT IFNULL(a, b)");
4032        assert!(
4033            result.contains("COALESCE"),
4034            "Expected COALESCE, got: {}",
4035            result
4036        );
4037    }
4038
4039    #[test]
4040    fn test_basic_select() {
4041        let result = transpile_to_snowflake("SELECT a, b FROM users WHERE id = 1");
4042        assert!(result.contains("SELECT"));
4043        assert!(result.contains("FROM users"));
4044    }
4045
4046    #[test]
4047    fn test_extract_string_comma_syntax_remains_date_part() {
4048        assert_eq!(
4049            transpile_to_snowflake("SELECT EXTRACT('month', a)"),
4050            "SELECT DATE_PART('month', a)"
4051        );
4052    }
4053
4054    #[test]
4055    fn test_snowflake_scripting_cursor_declare_block_roundtrip() {
4056        let sql = "DECLARE
4057  emp CURSOR FOR SELECT salary FROM employees;
4058BEGIN
4059  RETURN 1;
4060END";
4061
4062        let dialect = Dialect::get(DialectType::Snowflake);
4063        let ast = dialect.parse(sql).expect("Parse failed");
4064        let output = dialect.generate(&ast[0]).expect("Generate failed");
4065
4066        assert_eq!(output, sql);
4067    }
4068
4069    #[test]
4070    fn test_snowflake_scripting_cursor_return_table_roundtrip() {
4071        let sql = "DECLARE
4072  c1 CURSOR FOR SELECT * FROM invoices;
4073BEGIN
4074  OPEN c1;
4075  RETURN TABLE(RESULTSET_FROM_CURSOR(c1));
4076END";
4077
4078        let dialect = Dialect::get(DialectType::Snowflake);
4079        let ast = dialect.parse(sql).expect("Parse failed");
4080        let output = dialect.generate(&ast[0]).expect("Generate failed");
4081
4082        assert_eq!(output, sql);
4083    }
4084
4085    #[test]
4086    fn test_group_concat_to_listagg() {
4087        let result = transpile_to_snowflake("SELECT GROUP_CONCAT(name)");
4088        assert!(
4089            result.contains("LISTAGG"),
4090            "Expected LISTAGG, got: {}",
4091            result
4092        );
4093    }
4094
4095    #[test]
4096    fn test_string_agg_to_listagg() {
4097        let result = transpile_to_snowflake("SELECT STRING_AGG(name)");
4098        assert!(
4099            result.contains("LISTAGG"),
4100            "Expected LISTAGG, got: {}",
4101            result
4102        );
4103    }
4104
4105    #[test]
4106    fn test_array_to_array_construct() {
4107        let result = transpile_to_snowflake("SELECT ARRAY(1, 2, 3)");
4108        // ARRAY(1, 2, 3) from Generic -> Snowflake uses [] bracket notation
4109        assert!(
4110            result.contains("[1, 2, 3]"),
4111            "Expected [1, 2, 3], got: {}",
4112            result
4113        );
4114    }
4115
4116    #[test]
4117    fn test_double_quote_identifiers() {
4118        // Snowflake uses double quotes for identifiers
4119        let dialect = SnowflakeDialect;
4120        let config = dialect.generator_config();
4121        assert_eq!(config.identifier_quote, '"');
4122    }
4123}