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