Skip to main content

polyglot_sql/dialects/
snowflake.rs

1//! Snowflake Dialect
2//!
3//! Snowflake-specific transformations based on sqlglot patterns.
4//! Key differences:
5//! - TRY_ prefix for safe operations (TRY_CAST, TRY_TO_NUMBER)
6//! - FLATTEN for unnesting arrays
7//! - QUALIFY clause support
8//! - ARRAY_CONSTRUCT, OBJECT_CONSTRUCT for arrays/objects
9//! - Variant type handling
10//! - Default case-insensitive identifiers (unquoted)
11
12use super::{DialectImpl, DialectType};
13#[cfg(feature = "transpile")]
14use crate::error::Result;
15#[cfg(feature = "transpile")]
16use crate::expressions::{
17    AggFunc, BinaryOp, Cast, CeilFunc, DataType, Expression, Function, IntervalUnit, ListAggFunc,
18    Literal, UnaryFunc, VarArgFunc,
19};
20#[cfg(feature = "generate")]
21use crate::generator::GeneratorConfig;
22use crate::tokens::TokenizerConfig;
23
24/// Convert IntervalUnit to string for Snowflake syntax
25#[cfg(feature = "transpile")]
26fn interval_unit_to_str(unit: &IntervalUnit) -> String {
27    match unit {
28        IntervalUnit::Year => "YEAR".to_string(),
29        IntervalUnit::Quarter => "QUARTER".to_string(),
30        IntervalUnit::Month => "MONTH".to_string(),
31        IntervalUnit::Week => "WEEK".to_string(),
32        IntervalUnit::Day => "DAY".to_string(),
33        IntervalUnit::Hour => "HOUR".to_string(),
34        IntervalUnit::Minute => "MINUTE".to_string(),
35        IntervalUnit::Second => "SECOND".to_string(),
36        IntervalUnit::Millisecond => "MILLISECOND".to_string(),
37        IntervalUnit::Microsecond => "MICROSECOND".to_string(),
38        IntervalUnit::Nanosecond => "NANOSECOND".to_string(),
39    }
40}
41
42/// Snowflake dialect
43pub struct SnowflakeDialect;
44
45impl DialectImpl for SnowflakeDialect {
46    fn dialect_type(&self) -> DialectType {
47        DialectType::Snowflake
48    }
49
50    fn tokenizer_config(&self) -> TokenizerConfig {
51        let mut config = TokenizerConfig::default();
52        // Snowflake uses double quotes for identifiers
53        config.identifiers.insert('"', '"');
54        // Snowflake supports $$ string literals
55        config.quotes.insert("$$".to_string(), "$$".to_string());
56        // Snowflake accepts both doubled quotes and backslash escape sequences.
57        config.string_escapes.push('\\');
58        // Unknown backslash sequences discard the backslash. Listing the supported
59        // followers also prevents MySQL-only escapes from being decoded here.
60        config.escape_follow_chars = vec![
61            '\'', '"', '\\', 'b', 'f', 'n', 'r', 't', '0', '1', '2', '3', '4', '5', '6', '7', 'x',
62            'u',
63        ];
64        // Snowflake does NOT support nested comments (per Python sqlglot)
65        config.nested_comments = false;
66        // Snowflake supports // as single-line comments (in addition to --)
67        config.comments.insert("//".to_string(), None);
68        config
69    }
70
71    #[cfg(feature = "generate")]
72
73    fn generator_config(&self) -> GeneratorConfig {
74        use crate::generator::IdentifierQuoteStyle;
75        GeneratorConfig {
76            identifier_quote: '"',
77            identifier_quote_style: IdentifierQuoteStyle::DOUBLE_QUOTE,
78            dialect: Some(DialectType::Snowflake),
79            // Snowflake-specific settings from Python sqlglot
80            parameter_token: "$",
81            matched_by_source: false,
82            single_string_interval: true,
83            join_hints: false,
84            table_hints: false,
85            query_hints: false,
86            aggregate_filter_supported: false,
87            supports_table_copy: false,
88            collate_is_func: true,
89            limit_only_literals: true,
90            json_key_value_pair_sep: ",",
91            insert_overwrite: " OVERWRITE INTO",
92            struct_delimiter: ("(", ")"),
93            copy_params_are_wrapped: false,
94            copy_params_eq_required: true,
95            star_except: "EXCLUDE",
96            supports_exploding_projections: false,
97            array_concat_is_var_len: false,
98            supports_convert_timezone: true,
99            except_intersect_support_all_clause: false,
100            supports_median: true,
101            array_size_name: "ARRAY_SIZE",
102            supports_decode_case: true,
103            is_bool_allowed: false,
104            // Snowflake supports TRY_ prefix operations
105            try_supported: true,
106            // Snowflake supports NVL2
107            nvl2_supported: true,
108            // Snowflake uses FLATTEN for unnest
109            unnest_with_ordinality: false,
110            // Snowflake uses space before paren: ALL (subquery)
111            quantified_no_paren_space: false,
112            // Snowflake uses bracket-only array syntax: [1, 2, 3]
113            array_bracket_only: true,
114            ..Default::default()
115        }
116    }
117
118    #[cfg(feature = "transpile")]
119
120    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
121        match expr {
122            // ===== Data Type Mappings =====
123            Expression::DataType(dt) => self.transform_data_type(dt),
124
125            // ===== NOT IN transformation =====
126            // Snowflake treats `value NOT IN (subquery)` as `VALUE <> ALL (subquery)`
127            // See: https://docs.snowflake.com/en/sql-reference/functions/in
128            Expression::In(in_expr) if in_expr.not && in_expr.query.is_some() => {
129                // Transform NOT IN (subquery) -> <> ALL (subquery)
130                let inner = in_expr.query.unwrap();
131                // Wrap in Subquery so generator outputs ALL (subquery) with space
132                let subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
133                    this: inner,
134                    alias: None,
135                    column_aliases: Vec::new(),
136                    alias_explicit_as: false,
137                    alias_keyword: None,
138                    order_by: None,
139                    limit: None,
140                    offset: None,
141                    distribute_by: None,
142                    sort_by: None,
143                    cluster_by: None,
144                    lateral: false,
145                    modifiers_inside: false,
146                    trailing_comments: Vec::new(),
147                    inferred_type: None,
148                }));
149                Ok(Expression::All(Box::new(
150                    crate::expressions::QuantifiedExpr {
151                        this: in_expr.this,
152                        subquery,
153                        op: Some(crate::expressions::QuantifiedOp::Neq),
154                    },
155                )))
156            }
157
158            // NOT IN (values) -> NOT x IN (values)
159            Expression::In(in_expr) if in_expr.not => {
160                // Transform NOT x IN (values) by wrapping the In expression with not=false inside a Not
161                let in_without_not = crate::expressions::In {
162                    this: in_expr.this,
163                    expressions: in_expr.expressions,
164                    query: in_expr.query,
165                    not: false,
166                    global: in_expr.global,
167                    unnest: in_expr.unnest,
168                    is_field: in_expr.is_field,
169                };
170                Ok(Expression::Not(Box::new(crate::expressions::UnaryOp {
171                    this: Expression::In(Box::new(in_without_not)),
172                    inferred_type: None,
173                })))
174            }
175
176            // ===== Interval unit expansion =====
177            // Expand abbreviated units in interval string values (e.g., '1 w' -> '1 WEEK')
178            Expression::Interval(interval) => self.transform_interval(*interval),
179
180            // ===== Null handling =====
181            // IFNULL -> COALESCE (both work in Snowflake, but COALESCE is standard)
182            Expression::IfNull(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
183                original_name: None,
184                expressions: vec![f.this, f.expression],
185                inferred_type: None,
186            }))),
187
188            // NVL -> COALESCE (both work in Snowflake, but COALESCE is standard)
189            Expression::Nvl(f) => Ok(Expression::Coalesce(Box::new(VarArgFunc {
190                original_name: None,
191                expressions: vec![f.this, f.expression],
192                inferred_type: None,
193            }))),
194
195            // Coalesce with original_name (e.g., IFNULL parsed as Coalesce) -> clear original_name
196            Expression::Coalesce(mut f) => {
197                f.original_name = None;
198                Ok(Expression::Coalesce(f))
199            }
200
201            // GROUP_CONCAT -> LISTAGG in Snowflake
202            Expression::GroupConcat(f) => Ok(Expression::ListAgg(Box::new(ListAggFunc {
203                this: f.this,
204                separator: f.separator,
205                on_overflow: None,
206                order_by: f.order_by,
207                distinct: f.distinct,
208                filter: f.filter,
209                inferred_type: None,
210            }))),
211
212            // ===== Cast operations =====
213            // CAST(x AS GEOGRAPHY) -> TO_GEOGRAPHY(x)
214            // CAST(x AS GEOMETRY) -> TO_GEOMETRY(x)
215            Expression::Cast(c) => {
216                use crate::expressions::DataType;
217                // First, recursively transform the inner expression
218                let transformed_this = self.transform_expr(c.this)?;
219                match &c.to {
220                    DataType::Geography { .. } => Ok(Expression::Function(Box::new(
221                        Function::new("TO_GEOGRAPHY".to_string(), vec![transformed_this]),
222                    ))),
223                    DataType::Geometry { .. } => Ok(Expression::Function(Box::new(Function::new(
224                        "TO_GEOMETRY".to_string(),
225                        vec![transformed_this],
226                    )))),
227                    _ => {
228                        // Transform the data type
229                        let transformed_dt = match self.transform_data_type(c.to.clone())? {
230                            Expression::DataType(dt) => dt,
231                            _ => c.to.clone(),
232                        };
233                        Ok(Expression::Cast(Box::new(Cast {
234                            this: transformed_this,
235                            to: transformed_dt,
236                            double_colon_syntax: false, // Normalize :: to CAST()
237                            trailing_comments: c.trailing_comments,
238                            format: c.format,
239                            default: c.default,
240                            inferred_type: None,
241                        })))
242                    }
243                }
244            }
245
246            // TryCast stays as TryCast (Snowflake supports TRY_CAST)
247            // Recursively transform the inner expression
248            Expression::TryCast(c) => {
249                let transformed_this = self.transform_expr(c.this)?;
250                Ok(Expression::TryCast(Box::new(Cast {
251                    this: transformed_this,
252                    to: c.to,
253                    double_colon_syntax: false, // Normalize :: to CAST()
254                    trailing_comments: c.trailing_comments,
255                    format: c.format,
256                    default: c.default,
257                    inferred_type: None,
258                })))
259            }
260
261            // SafeCast -> Cast in Snowflake (Snowflake CAST is safe by default)
262            // Also convert TIMESTAMP to TIMESTAMPTZ (BigQuery TIMESTAMP = tz-aware)
263            Expression::SafeCast(c) => {
264                let to = match c.to {
265                    DataType::Timestamp { .. } => DataType::Custom {
266                        name: "TIMESTAMPTZ".to_string(),
267                    },
268                    DataType::Custom { name } if name.eq_ignore_ascii_case("TIMESTAMP") => {
269                        DataType::Custom {
270                            name: "TIMESTAMPTZ".to_string(),
271                        }
272                    }
273                    other => other,
274                };
275                let transformed_this = self.transform_expr(c.this)?;
276                Ok(Expression::Cast(Box::new(Cast {
277                    this: transformed_this,
278                    to,
279                    double_colon_syntax: c.double_colon_syntax,
280                    trailing_comments: c.trailing_comments,
281                    format: c.format,
282                    default: c.default,
283                    inferred_type: None,
284                })))
285            }
286
287            // ===== Typed Literals -> CAST =====
288            // TIMESTAMP '...' -> CAST('...' AS TIMESTAMP)
289            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
290                let Literal::Timestamp(s) = lit.as_ref() else {
291                    unreachable!()
292                };
293                Ok(Expression::Cast(Box::new(Cast {
294                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
295                    to: DataType::Timestamp {
296                        precision: None,
297                        timezone: false,
298                    },
299                    double_colon_syntax: false,
300                    trailing_comments: Vec::new(),
301                    format: None,
302                    default: None,
303                    inferred_type: None,
304                })))
305            }
306
307            // DATE '...' -> CAST('...' AS DATE)
308            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
309                let Literal::Date(s) = lit.as_ref() else {
310                    unreachable!()
311                };
312                Ok(Expression::Cast(Box::new(Cast {
313                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
314                    to: DataType::Date,
315                    double_colon_syntax: false,
316                    trailing_comments: Vec::new(),
317                    format: None,
318                    default: None,
319                    inferred_type: None,
320                })))
321            }
322
323            // TIME '...' -> CAST('...' AS TIME)
324            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)) => {
325                let Literal::Time(s) = lit.as_ref() else {
326                    unreachable!()
327                };
328                Ok(Expression::Cast(Box::new(Cast {
329                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
330                    to: DataType::Time {
331                        precision: None,
332                        timezone: false,
333                    },
334                    double_colon_syntax: false,
335                    trailing_comments: Vec::new(),
336                    format: None,
337                    default: None,
338                    inferred_type: None,
339                })))
340            }
341
342            // DATETIME '...' -> CAST('...' AS DATETIME)
343            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
344                let Literal::Datetime(s) = lit.as_ref() else {
345                    unreachable!()
346                };
347                Ok(Expression::Cast(Box::new(Cast {
348                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
349                    to: DataType::Custom {
350                        name: "DATETIME".to_string(),
351                    },
352                    double_colon_syntax: false,
353                    trailing_comments: Vec::new(),
354                    format: None,
355                    default: None,
356                    inferred_type: None,
357                })))
358            }
359
360            // ===== Pattern matching =====
361            // ILIKE is native to Snowflake (no transformation needed)
362            Expression::ILike(op) => Ok(Expression::ILike(op)),
363
364            // ===== Array operations =====
365            // EXPLODE -> FLATTEN in Snowflake
366            Expression::Explode(f) => Ok(Expression::Function(Box::new(Function::new(
367                "FLATTEN".to_string(),
368                vec![f.this],
369            )))),
370
371            // ExplodeOuter -> FLATTEN with OUTER => TRUE
372            Expression::ExplodeOuter(f) => Ok(Expression::Function(Box::new(Function::new(
373                "FLATTEN".to_string(),
374                vec![f.this],
375            )))),
376
377            // UNNEST -> TABLE(FLATTEN(INPUT => x)) AS _t0(seq, key, path, index, value, this)
378            Expression::Unnest(f) => {
379                // Create INPUT => x named argument
380                let input_arg =
381                    Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
382                        name: crate::expressions::Identifier::new("INPUT"),
383                        value: f.this,
384                        separator: crate::expressions::NamedArgSeparator::DArrow,
385                    }));
386
387                // Create FLATTEN(INPUT => x)
388                let flatten = Expression::Function(Box::new(Function::new(
389                    "FLATTEN".to_string(),
390                    vec![input_arg],
391                )));
392
393                // Wrap in TABLE(...)
394                let table_func =
395                    Expression::TableFromRows(Box::new(crate::expressions::TableFromRows {
396                        this: Box::new(flatten),
397                        alias: None,
398                        joins: vec![],
399                        pivots: None,
400                        sample: None,
401                    }));
402
403                // Add alias _t0(seq, key, path, index, value, this)
404                Ok(Expression::Alias(Box::new(crate::expressions::Alias {
405                    this: table_func,
406                    alias: crate::expressions::Identifier::new("_t0"),
407                    column_aliases: vec![
408                        crate::expressions::Identifier::new("seq"),
409                        crate::expressions::Identifier::new("key"),
410                        crate::expressions::Identifier::new("path"),
411                        crate::expressions::Identifier::new("index"),
412                        crate::expressions::Identifier::new("value"),
413                        crate::expressions::Identifier::new("this"),
414                    ],
415                    alias_explicit_as: false,
416                    alias_keyword: None,
417                    pre_alias_comments: vec![],
418                    trailing_comments: vec![],
419                    inferred_type: None,
420                })))
421            }
422
423            // Array constructor:
424            // - If bracket notation ([1, 2, 3]), preserve it in Snowflake
425            // - If ARRAY[...] syntax, convert to ARRAY_CONSTRUCT
426            Expression::ArrayFunc(arr) => {
427                if arr.bracket_notation {
428                    // Keep bracket notation in Snowflake
429                    Ok(Expression::ArrayFunc(arr))
430                } else {
431                    // Convert ARRAY[...] to ARRAY_CONSTRUCT
432                    Ok(Expression::Function(Box::new(Function::new(
433                        "ARRAY_CONSTRUCT".to_string(),
434                        arr.expressions,
435                    ))))
436                }
437            }
438
439            // ArrayConcat -> ARRAY_CAT
440            Expression::ArrayConcat(f) => Ok(Expression::Function(Box::new(Function::new(
441                "ARRAY_CAT".to_string(),
442                f.expressions,
443            )))),
444
445            // ArrayConcatAgg -> ARRAY_FLATTEN
446            Expression::ArrayConcatAgg(f) => Ok(Expression::Function(Box::new(Function::new(
447                "ARRAY_FLATTEN".to_string(),
448                vec![f.this],
449            )))),
450
451            // ArrayContains -> ARRAY_CONTAINS
452            Expression::ArrayContains(f) => Ok(Expression::Function(Box::new(Function::new(
453                "ARRAY_CONTAINS".to_string(),
454                vec![f.this, f.expression],
455            )))),
456
457            // ArrayIntersect -> ARRAY_INTERSECTION
458            Expression::ArrayIntersect(f) => Ok(Expression::Function(Box::new(Function::new(
459                "ARRAY_INTERSECTION".to_string(),
460                f.expressions,
461            )))),
462
463            // SortArray -> ARRAY_SORT
464            Expression::ArraySort(f) => Ok(Expression::Function(Box::new(Function::new(
465                "ARRAY_SORT".to_string(),
466                vec![f.this],
467            )))),
468
469            // StringToArray -> STRTOK_TO_ARRAY
470            Expression::StringToArray(f) => {
471                let mut args = vec![*f.this];
472                if let Some(expr) = f.expression {
473                    args.push(*expr);
474                }
475                Ok(Expression::Function(Box::new(Function::new(
476                    "STRTOK_TO_ARRAY".to_string(),
477                    args,
478                ))))
479            }
480
481            // ===== Bitwise operations =====
482            // BitwiseOr -> BITOR
483            Expression::BitwiseOr(f) => Ok(Expression::Function(Box::new(Function::new(
484                "BITOR".to_string(),
485                vec![f.left, f.right],
486            )))),
487
488            // BitwiseXor -> BITXOR
489            Expression::BitwiseXor(f) => Ok(Expression::Function(Box::new(Function::new(
490                "BITXOR".to_string(),
491                vec![f.left, f.right],
492            )))),
493
494            // BitwiseAnd -> BITAND
495            Expression::BitwiseAnd(f) => Ok(Expression::Function(Box::new(Function::new(
496                "BITAND".to_string(),
497                vec![f.left, f.right],
498            )))),
499
500            // BitwiseNot -> BITNOT
501            Expression::BitwiseNot(f) => Ok(Expression::Function(Box::new(Function::new(
502                "BITNOT".to_string(),
503                vec![f.this],
504            )))),
505
506            // BitwiseLeftShift -> BITSHIFTLEFT
507            Expression::BitwiseLeftShift(f) => Ok(Expression::Function(Box::new(Function::new(
508                "BITSHIFTLEFT".to_string(),
509                vec![f.left, f.right],
510            )))),
511
512            // BitwiseRightShift -> BITSHIFTRIGHT
513            Expression::BitwiseRightShift(f) => Ok(Expression::Function(Box::new(Function::new(
514                "BITSHIFTRIGHT".to_string(),
515                vec![f.left, f.right],
516            )))),
517
518            // BitwiseAndAgg -> BITAND_AGG
519            Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(Function::new(
520                "BITAND_AGG".to_string(),
521                vec![f.this],
522            )))),
523
524            // BitwiseOrAgg -> BITOR_AGG
525            Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(Function::new(
526                "BITOR_AGG".to_string(),
527                vec![f.this],
528            )))),
529
530            // BitwiseXorAgg -> BITXOR_AGG
531            Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(Function::new(
532                "BITXOR_AGG".to_string(),
533                vec![f.this],
534            )))),
535
536            // ===== Boolean aggregates =====
537            // LogicalAnd -> BOOLAND_AGG
538            Expression::LogicalAnd(f) => Ok(Expression::Function(Box::new(Function::new(
539                "BOOLAND_AGG".to_string(),
540                vec![f.this],
541            )))),
542
543            // LogicalOr -> BOOLOR_AGG
544            Expression::LogicalOr(f) => Ok(Expression::Function(Box::new(Function::new(
545                "BOOLOR_AGG".to_string(),
546                vec![f.this],
547            )))),
548
549            // Booland -> BOOLAND
550            Expression::Booland(f) => Ok(Expression::Function(Box::new(Function::new(
551                "BOOLAND".to_string(),
552                vec![*f.this, *f.expression],
553            )))),
554
555            // Boolor -> BOOLOR
556            Expression::Boolor(f) => Ok(Expression::Function(Box::new(Function::new(
557                "BOOLOR".to_string(),
558                vec![*f.this, *f.expression],
559            )))),
560
561            // Xor -> BOOLXOR
562            Expression::Xor(f) => {
563                let mut args = Vec::new();
564                if let Some(this) = f.this {
565                    args.push(*this);
566                }
567                if let Some(expr) = f.expression {
568                    args.push(*expr);
569                }
570                Ok(Expression::Function(Box::new(Function::new(
571                    "BOOLXOR".to_string(),
572                    args,
573                ))))
574            }
575
576            // ===== Date/time functions =====
577            // DayOfMonth -> DAYOFMONTH
578            Expression::DayOfMonth(f) => Ok(Expression::Function(Box::new(Function::new(
579                "DAYOFMONTH".to_string(),
580                vec![f.this],
581            )))),
582
583            // DayOfWeek -> DAYOFWEEK
584            Expression::DayOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
585                "DAYOFWEEK".to_string(),
586                vec![f.this],
587            )))),
588
589            // DayOfWeekIso -> DAYOFWEEKISO
590            Expression::DayOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
591                "DAYOFWEEKISO".to_string(),
592                vec![f.this],
593            )))),
594
595            // DayOfYear -> DAYOFYEAR
596            Expression::DayOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
597                "DAYOFYEAR".to_string(),
598                vec![f.this],
599            )))),
600
601            // WeekOfYear -> WEEK (Snowflake native function)
602            Expression::WeekOfYear(f) => Ok(Expression::Function(Box::new(Function::new(
603                "WEEK".to_string(),
604                vec![f.this],
605            )))),
606
607            // YearOfWeek -> YEAROFWEEK
608            Expression::YearOfWeek(f) => Ok(Expression::Function(Box::new(Function::new(
609                "YEAROFWEEK".to_string(),
610                vec![f.this],
611            )))),
612
613            // YearOfWeekIso -> YEAROFWEEKISO
614            Expression::YearOfWeekIso(f) => Ok(Expression::Function(Box::new(Function::new(
615                "YEAROFWEEKISO".to_string(),
616                vec![f.this],
617            )))),
618
619            // ByteLength -> OCTET_LENGTH
620            Expression::ByteLength(f) => Ok(Expression::Function(Box::new(Function::new(
621                "OCTET_LENGTH".to_string(),
622                vec![f.this],
623            )))),
624
625            // TimestampDiff -> TIMESTAMPDIFF
626            Expression::TimestampDiff(f) => {
627                let mut args = vec![];
628                // If unit is set (from cross-dialect normalize), use unit as first arg, this as second, expression as third
629                if let Some(ref unit_str) = f.unit {
630                    args.push(Expression::Identifier(crate::expressions::Identifier::new(
631                        unit_str.clone(),
632                    )));
633                    args.push(*f.this);
634                    args.push(*f.expression);
635                } else {
636                    args.push(*f.this);
637                    args.push(*f.expression);
638                }
639                Ok(Expression::Function(Box::new(Function::new(
640                    "TIMESTAMPDIFF".to_string(),
641                    args,
642                ))))
643            }
644
645            // TimestampAdd -> TIMESTAMPADD
646            Expression::TimestampAdd(f) => {
647                let mut args = vec![];
648                if let Some(ref unit_str) = f.unit {
649                    args.push(Expression::Identifier(crate::expressions::Identifier::new(
650                        unit_str.clone(),
651                    )));
652                    args.push(*f.this);
653                    args.push(*f.expression);
654                } else {
655                    args.push(*f.this);
656                    args.push(*f.expression);
657                }
658                Ok(Expression::Function(Box::new(Function::new(
659                    "TIMESTAMPADD".to_string(),
660                    args,
661                ))))
662            }
663
664            // ToArray -> TO_ARRAY
665            Expression::ToArray(f) => Ok(Expression::Function(Box::new(Function::new(
666                "TO_ARRAY".to_string(),
667                vec![f.this],
668            )))),
669
670            // DateAdd -> DATEADD (with unit, amount, date order)
671            Expression::DateAdd(f) => {
672                let unit_str = interval_unit_to_str(&f.unit);
673                let unit = Expression::Identifier(crate::expressions::Identifier {
674                    name: unit_str,
675                    quoted: false,
676                    trailing_comments: Vec::new(),
677                    span: None,
678                });
679                Ok(Expression::Function(Box::new(Function::new(
680                    "DATEADD".to_string(),
681                    vec![unit, f.interval, f.this],
682                ))))
683            }
684
685            // DateSub -> DATEADD with negated amount: val * -1
686            Expression::DateSub(f) => {
687                let unit_str = interval_unit_to_str(&f.unit);
688                let unit = Expression::Identifier(crate::expressions::Identifier {
689                    name: unit_str,
690                    quoted: false,
691                    trailing_comments: Vec::new(),
692                    span: None,
693                });
694                // Negate using val * -1 format (matching Python sqlglot output)
695                let neg_expr = Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
696                    f.interval,
697                    Expression::Neg(Box::new(crate::expressions::UnaryOp {
698                        this: Expression::number(1),
699                        inferred_type: None,
700                    })),
701                )));
702                Ok(Expression::Function(Box::new(Function::new(
703                    "DATEADD".to_string(),
704                    vec![unit, neg_expr, f.this],
705                ))))
706            }
707
708            // DateDiff -> DATEDIFF
709            Expression::DateDiff(f) => {
710                let unit_str =
711                    interval_unit_to_str(&f.unit.unwrap_or(crate::expressions::IntervalUnit::Day));
712                let unit = Expression::Identifier(crate::expressions::Identifier {
713                    name: unit_str,
714                    quoted: false,
715                    trailing_comments: Vec::new(),
716                    span: None,
717                });
718                Ok(Expression::Function(Box::new(Function::new(
719                    "DATEDIFF".to_string(),
720                    vec![unit, f.expression, f.this],
721                ))))
722            }
723
724            // ===== String functions =====
725            // StringAgg -> LISTAGG in Snowflake
726            Expression::StringAgg(f) => {
727                let mut args = vec![f.this.clone()];
728                if let Some(separator) = &f.separator {
729                    args.push(separator.clone());
730                }
731                Ok(Expression::Function(Box::new(Function::new(
732                    "LISTAGG".to_string(),
733                    args,
734                ))))
735            }
736
737            // StartsWith -> STARTSWITH
738            Expression::StartsWith(f) => Ok(Expression::Function(Box::new(Function::new(
739                "STARTSWITH".to_string(),
740                vec![f.this, f.expression],
741            )))),
742
743            // EndsWith -> keep as EndsWith AST node; generator outputs per-dialect
744            Expression::EndsWith(f) => Ok(Expression::EndsWith(f)),
745
746            // Stuff -> INSERT
747            Expression::Stuff(f) => {
748                let mut args = vec![*f.this];
749                if let Some(start) = f.start {
750                    args.push(*start);
751                }
752                if let Some(length) = f.length {
753                    args.push(Expression::number(length));
754                }
755                args.push(*f.expression);
756                Ok(Expression::Function(Box::new(Function::new(
757                    "INSERT".to_string(),
758                    args,
759                ))))
760            }
761
762            // ===== Hash functions =====
763            // SHA -> SHA1
764            Expression::SHA(f) => Ok(Expression::Function(Box::new(Function::new(
765                "SHA1".to_string(),
766                vec![f.this],
767            )))),
768
769            // SHA1Digest -> SHA1_BINARY
770            Expression::SHA1Digest(f) => Ok(Expression::Function(Box::new(Function::new(
771                "SHA1_BINARY".to_string(),
772                vec![f.this],
773            )))),
774
775            // SHA2Digest -> SHA2_BINARY
776            Expression::SHA2Digest(f) => Ok(Expression::Function(Box::new(Function::new(
777                "SHA2_BINARY".to_string(),
778                vec![*f.this],
779            )))),
780
781            // MD5Digest -> MD5_BINARY
782            Expression::MD5Digest(f) => Ok(Expression::Function(Box::new(Function::new(
783                "MD5_BINARY".to_string(),
784                vec![*f.this],
785            )))),
786
787            // MD5NumberLower64 -> MD5_NUMBER_LOWER64
788            Expression::MD5NumberLower64(f) => Ok(Expression::Function(Box::new(Function::new(
789                "MD5_NUMBER_LOWER64".to_string(),
790                vec![f.this],
791            )))),
792
793            // MD5NumberUpper64 -> MD5_NUMBER_UPPER64
794            Expression::MD5NumberUpper64(f) => Ok(Expression::Function(Box::new(Function::new(
795                "MD5_NUMBER_UPPER64".to_string(),
796                vec![f.this],
797            )))),
798
799            // ===== Vector functions =====
800            // CosineDistance -> VECTOR_COSINE_SIMILARITY
801            Expression::CosineDistance(f) => Ok(Expression::Function(Box::new(Function::new(
802                "VECTOR_COSINE_SIMILARITY".to_string(),
803                vec![*f.this, *f.expression],
804            )))),
805
806            // DotProduct -> VECTOR_INNER_PRODUCT
807            Expression::DotProduct(f) => Ok(Expression::Function(Box::new(Function::new(
808                "VECTOR_INNER_PRODUCT".to_string(),
809                vec![*f.this, *f.expression],
810            )))),
811
812            // EuclideanDistance -> VECTOR_L2_DISTANCE
813            Expression::EuclideanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
814                "VECTOR_L2_DISTANCE".to_string(),
815                vec![*f.this, *f.expression],
816            )))),
817
818            // ManhattanDistance -> VECTOR_L1_DISTANCE
819            Expression::ManhattanDistance(f) => Ok(Expression::Function(Box::new(Function::new(
820                "VECTOR_L1_DISTANCE".to_string(),
821                vec![*f.this, *f.expression],
822            )))),
823
824            // ===== JSON/Struct functions =====
825            // JSONFormat -> TO_JSON
826            Expression::JSONFormat(f) => {
827                let mut args = Vec::new();
828                if let Some(this) = f.this {
829                    args.push(*this);
830                }
831                Ok(Expression::Function(Box::new(Function::new(
832                    "TO_JSON".to_string(),
833                    args,
834                ))))
835            }
836
837            // JSONKeys -> OBJECT_KEYS
838            Expression::JSONKeys(f) => Ok(Expression::Function(Box::new(Function::new(
839                "OBJECT_KEYS".to_string(),
840                vec![*f.this],
841            )))),
842
843            // GetExtract -> GET
844            Expression::GetExtract(f) => Ok(Expression::Function(Box::new(Function::new(
845                "GET".to_string(),
846                vec![*f.this, *f.expression],
847            )))),
848
849            // StarMap -> OBJECT_CONSTRUCT
850            Expression::StarMap(f) => Ok(Expression::Function(Box::new(Function::new(
851                "OBJECT_CONSTRUCT".to_string(),
852                vec![f.this, f.expression],
853            )))),
854
855            // LowerHex -> TO_CHAR
856            Expression::LowerHex(f) => Ok(Expression::Function(Box::new(Function::new(
857                "TO_CHAR".to_string(),
858                vec![f.this],
859            )))),
860
861            // Skewness -> SKEW
862            Expression::Skewness(f) => Ok(Expression::Function(Box::new(Function::new(
863                "SKEW".to_string(),
864                vec![f.this],
865            )))),
866
867            // StPoint -> ST_MAKEPOINT
868            Expression::StPoint(f) => Ok(Expression::Function(Box::new(Function::new(
869                "ST_MAKEPOINT".to_string(),
870                vec![*f.this, *f.expression],
871            )))),
872
873            // FromTimeZone -> CONVERT_TIMEZONE
874            Expression::FromTimeZone(f) => Ok(Expression::Function(Box::new(Function::new(
875                "CONVERT_TIMEZONE".to_string(),
876                vec![*f.this],
877            )))),
878
879            // ===== Conversion functions =====
880            // Unhex -> HEX_DECODE_BINARY
881            Expression::Unhex(f) => Ok(Expression::Function(Box::new(Function::new(
882                "HEX_DECODE_BINARY".to_string(),
883                vec![*f.this],
884            )))),
885
886            // UnixToTime -> TO_TIMESTAMP
887            Expression::UnixToTime(f) => {
888                let mut args = vec![*f.this];
889                if let Some(scale) = f.scale {
890                    args.push(Expression::number(scale));
891                }
892                Ok(Expression::Function(Box::new(Function::new(
893                    "TO_TIMESTAMP".to_string(),
894                    args,
895                ))))
896            }
897
898            // ===== Conditional =====
899            // IfFunc -> keep as IfFunc with IFF name for Snowflake
900            Expression::IfFunc(f) => Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
901                condition: f.condition,
902                true_value: f.true_value,
903                false_value: Some(
904                    f.false_value
905                        .unwrap_or(Expression::Null(crate::expressions::Null)),
906                ),
907                original_name: Some("IFF".to_string()),
908                inferred_type: None,
909            }))),
910
911            // ===== Aggregate functions =====
912            // ApproxDistinct -> APPROX_COUNT_DISTINCT
913            Expression::ApproxDistinct(f) => Ok(Expression::Function(Box::new(Function::new(
914                "APPROX_COUNT_DISTINCT".to_string(),
915                vec![f.this],
916            )))),
917
918            // ArgMax -> MAX_BY
919            Expression::ArgMax(f) => Ok(Expression::Function(Box::new(Function::new(
920                "MAX_BY".to_string(),
921                vec![*f.this, *f.expression],
922            )))),
923
924            // ArgMin -> MIN_BY
925            Expression::ArgMin(f) => Ok(Expression::Function(Box::new(Function::new(
926                "MIN_BY".to_string(),
927                vec![*f.this, *f.expression],
928            )))),
929
930            // ===== Random =====
931            // RANDOM is native to Snowflake - keep as-is
932            Expression::Random(_) => Ok(Expression::Random(crate::expressions::Random)),
933
934            // Rand - keep as-is (generator outputs RANDOM for Snowflake)
935            Expression::Rand(r) => Ok(Expression::Rand(r)),
936
937            // ===== UUID =====
938            // Uuid -> keep as Uuid node; generator will output UUID_STRING for Snowflake
939            Expression::Uuid(u) => Ok(Expression::Uuid(u)),
940
941            // ===== Map/Object =====
942            // Map -> OBJECT_CONSTRUCT
943            Expression::Map(f) => Ok(Expression::Function(Box::new(Function::new(
944                "OBJECT_CONSTRUCT".to_string(),
945                f.keys
946                    .into_iter()
947                    .zip(f.values.into_iter())
948                    .flat_map(|(k, v)| vec![k, v])
949                    .collect(),
950            )))),
951
952            // MapFunc (curly brace syntax) -> OBJECT_CONSTRUCT
953            Expression::MapFunc(f) => Ok(Expression::Function(Box::new(Function::new(
954                "OBJECT_CONSTRUCT".to_string(),
955                f.keys
956                    .into_iter()
957                    .zip(f.values.into_iter())
958                    .flat_map(|(k, v)| vec![k, v])
959                    .collect(),
960            )))),
961
962            // VarMap -> OBJECT_CONSTRUCT
963            Expression::VarMap(f) => Ok(Expression::Function(Box::new(Function::new(
964                "OBJECT_CONSTRUCT".to_string(),
965                f.keys
966                    .into_iter()
967                    .zip(f.values.into_iter())
968                    .flat_map(|(k, v)| vec![k, v])
969                    .collect(),
970            )))),
971
972            // ===== JSON =====
973            // JSONObject -> OBJECT_CONSTRUCT_KEEP_NULL
974            Expression::JsonObject(f) => Ok(Expression::Function(Box::new(Function::new(
975                "OBJECT_CONSTRUCT_KEEP_NULL".to_string(),
976                f.pairs.into_iter().flat_map(|(k, v)| vec![k, v]).collect(),
977            )))),
978
979            // JSONExtractScalar -> JSON_EXTRACT_PATH_TEXT
980            Expression::JsonExtractScalar(f) => Ok(Expression::Function(Box::new(Function::new(
981                "JSON_EXTRACT_PATH_TEXT".to_string(),
982                vec![f.this, f.path],
983            )))),
984
985            // ===== Struct =====
986            // Struct -> OBJECT_CONSTRUCT
987            Expression::Struct(f) => Ok(Expression::Function(Box::new(Function::new(
988                "OBJECT_CONSTRUCT".to_string(),
989                f.fields
990                    .into_iter()
991                    .flat_map(|(name, expr)| {
992                        let key = match name {
993                            Some(n) => Expression::string(n),
994                            None => Expression::Null(crate::expressions::Null),
995                        };
996                        vec![key, expr]
997                    })
998                    .collect(),
999            )))),
1000
1001            // ===== JSON Path =====
1002            // JSONPathRoot -> empty string ($ is implicit in Snowflake)
1003            Expression::JSONPathRoot(_) => Ok(Expression::Literal(Box::new(
1004                crate::expressions::Literal::String(String::new()),
1005            ))),
1006
1007            // ===== VarSamp -> VARIANCE (Snowflake) =====
1008            // Snowflake uses VARIANCE instead of VAR_SAMP
1009            Expression::VarSamp(agg) => Ok(Expression::Variance(agg)),
1010
1011            // ===== VarPop -> keep as VarPop =====
1012            // The generator handles dialect-specific naming (VARIANCE_POP for Snowflake)
1013            Expression::VarPop(agg) => Ok(Expression::VarPop(agg)),
1014
1015            // ===== EXTRACT -> DATE_PART =====
1016            // Snowflake uses DATE_PART instead of EXTRACT
1017            Expression::Extract(f) => {
1018                use crate::expressions::DateTimeField;
1019                // Recursively transform the inner expression (e.g., CAST(... AS TIMESTAMP_NTZ) -> CAST(... AS TIMESTAMPNTZ))
1020                let transformed_this = self.transform_expr(f.this)?;
1021                let field_name = match &f.field {
1022                    DateTimeField::Year => "YEAR",
1023                    DateTimeField::Month => "MONTH",
1024                    DateTimeField::Day => "DAY",
1025                    DateTimeField::Hour => "HOUR",
1026                    DateTimeField::Minute => "MINUTE",
1027                    DateTimeField::Second => "SECOND",
1028                    DateTimeField::Millisecond => "MILLISECOND",
1029                    DateTimeField::Microsecond => "MICROSECOND",
1030                    DateTimeField::Week => "WEEK",
1031                    DateTimeField::WeekWithModifier(m) => {
1032                        return Ok(Expression::Function(Box::new(Function::new(
1033                            "DATE_PART".to_string(),
1034                            vec![
1035                                Expression::Identifier(crate::expressions::Identifier {
1036                                    name: format!("WEEK({})", m),
1037                                    quoted: false,
1038                                    trailing_comments: Vec::new(),
1039                                    span: None,
1040                                }),
1041                                transformed_this,
1042                            ],
1043                        ))))
1044                    }
1045                    DateTimeField::DayOfWeek => "DAYOFWEEK",
1046                    DateTimeField::DayOfYear => "DAYOFYEAR",
1047                    DateTimeField::Quarter => "QUARTER",
1048                    DateTimeField::Epoch => "EPOCH",
1049                    DateTimeField::Timezone => "TIMEZONE",
1050                    DateTimeField::TimezoneHour => "TIMEZONE_HOUR",
1051                    DateTimeField::TimezoneMinute => "TIMEZONE_MINUTE",
1052                    DateTimeField::Date => "DATE",
1053                    DateTimeField::Time => "TIME",
1054                    DateTimeField::Custom(s) => {
1055                        // Map common EXTRACT field names to Snowflake DATE_PART names
1056                        match s.to_uppercase().as_str() {
1057                            "DAYOFMONTH" => "DAY",
1058                            "DOW" => "DAYOFWEEK",
1059                            "DOY" => "DAYOFYEAR",
1060                            "ISODOW" => "DAYOFWEEKISO",
1061                            "EPOCH_SECOND" | "EPOCH_SECONDS" => "EPOCH_SECOND",
1062                            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => "EPOCH_MILLISECOND",
1063                            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => "EPOCH_MICROSECOND",
1064                            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => "EPOCH_NANOSECOND",
1065                            _ => {
1066                                return {
1067                                    let field_ident =
1068                                        Expression::Identifier(crate::expressions::Identifier {
1069                                            name: s.to_string(),
1070                                            quoted: false,
1071                                            trailing_comments: Vec::new(),
1072                                            span: None,
1073                                        });
1074                                    Ok(Expression::Function(Box::new(Function::new(
1075                                        "DATE_PART".to_string(),
1076                                        vec![field_ident, transformed_this],
1077                                    ))))
1078                                }
1079                            }
1080                        }
1081                    }
1082                };
1083                let field_ident = Expression::Identifier(crate::expressions::Identifier {
1084                    name: field_name.to_string(),
1085                    quoted: false,
1086                    trailing_comments: Vec::new(),
1087                    span: None,
1088                });
1089                Ok(Expression::Function(Box::new(Function::new(
1090                    "DATE_PART".to_string(),
1091                    vec![field_ident, transformed_this],
1092                ))))
1093            }
1094
1095            // Generic function transformations
1096            Expression::Function(f) => self.transform_function(*f),
1097
1098            // SUM - recursively transform inner expression
1099            Expression::Sum(mut agg) => {
1100                agg.this = self.transform_expr(agg.this)?;
1101                Ok(Expression::Sum(agg))
1102            }
1103
1104            // Generic aggregate function transformations
1105            Expression::AggregateFunction(f) => self.transform_aggregate_function(f),
1106
1107            // Handle NamedArgument - recursively transform the value
1108            Expression::NamedArgument(na) => {
1109                let transformed_value = self.transform_expr(na.value)?;
1110                Ok(Expression::NamedArgument(Box::new(
1111                    crate::expressions::NamedArgument {
1112                        name: na.name,
1113                        value: transformed_value,
1114                        separator: na.separator,
1115                    },
1116                )))
1117            }
1118
1119            // Handle CreateTable - transform column data types and default/computed expressions
1120            Expression::CreateTable(mut ct) => {
1121                for col in &mut ct.columns {
1122                    if let Expression::DataType(new_dt) =
1123                        self.transform_data_type(col.data_type.clone())?
1124                    {
1125                        col.data_type = new_dt;
1126                    }
1127                    // Also transform computed/default expressions (e.g., AS (parse_json(x):COL3::number))
1128                    if let Some(default_expr) = col.default.take() {
1129                        col.default = Some(self.transform_expr(default_expr)?);
1130                    }
1131                    // Transform expressions in column constraints (computed columns)
1132                    for constraint in &mut col.constraints {
1133                        if let crate::expressions::ColumnConstraint::ComputedColumn(cc) = constraint
1134                        {
1135                            let transformed = self.transform_expr(*cc.expression.clone())?;
1136                            cc.expression = Box::new(transformed);
1137                        }
1138                    }
1139                }
1140
1141                // For EXTERNAL tables, convert with_properties to Raw properties
1142                // with proper Snowflake formatting (no WITH wrapper, specific key casing)
1143                if ct.table_modifier.as_deref() == Some("EXTERNAL")
1144                    && !ct.with_properties.is_empty()
1145                {
1146                    for (key, value) in ct.with_properties.drain(..) {
1147                        let formatted = Self::format_external_table_property(&key, &value);
1148                        ct.properties
1149                            .push(Expression::Raw(crate::expressions::Raw { sql: formatted }));
1150                    }
1151                }
1152
1153                Ok(Expression::CreateTable(ct))
1154            }
1155
1156            // Handle AlterTable - transform column data types in ADD operations
1157            Expression::AlterTable(mut at) => {
1158                for action in &mut at.actions {
1159                    if let crate::expressions::AlterTableAction::AddColumn { column, .. } = action {
1160                        if let Expression::DataType(new_dt) =
1161                            self.transform_data_type(column.data_type.clone())?
1162                        {
1163                            column.data_type = new_dt;
1164                        }
1165                    }
1166                }
1167                Ok(Expression::AlterTable(at))
1168            }
1169
1170            // Handle Table reference - transform HistoricalData (AT/BEFORE time travel clauses)
1171            Expression::Table(mut t) => {
1172                if let Some(when) = t.when.take() {
1173                    // Recursively transform the expression inside HistoricalData
1174                    let transformed_expr = self.transform_expr(*when.expression)?;
1175                    t.when = Some(Box::new(crate::expressions::HistoricalData {
1176                        this: when.this,
1177                        kind: when.kind,
1178                        expression: Box::new(transformed_expr),
1179                    }));
1180                }
1181                Ok(Expression::Table(t))
1182            }
1183
1184            // Handle Subscript - recursively transform inner expression
1185            Expression::Subscript(s) => {
1186                let transformed_this = self.transform_expr(s.this)?;
1187                let transformed_index = self.transform_expr(s.index)?;
1188                Ok(Expression::Subscript(Box::new(
1189                    crate::expressions::Subscript {
1190                        this: transformed_this,
1191                        index: transformed_index,
1192                    },
1193                )))
1194            }
1195
1196            // Recursively transform parenthesized expressions
1197            Expression::Paren(p) => {
1198                let transformed = self.transform_expr(p.this)?;
1199                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
1200                    this: transformed,
1201                    trailing_comments: p.trailing_comments,
1202                })))
1203            }
1204
1205            // Implicit NULL ordering is handled by cross-dialect normalization,
1206            // not here: this transform also runs on Snowflake targets and identity
1207            // queries, where missing clauses must retain server-dependent semantics.
1208
1209            // LATERAL FLATTEN: add default column aliases (SEQ, KEY, PATH, INDEX, VALUE, THIS)
1210            Expression::Lateral(mut lat) => {
1211                // Check if the inner expression is a FLATTEN function
1212                let is_flatten = match lat.this.as_ref() {
1213                    Expression::Function(f) => f.name.to_uppercase() == "FLATTEN",
1214                    _ => false,
1215                };
1216                if is_flatten && lat.column_aliases.is_empty() {
1217                    // Add default column aliases
1218                    lat.column_aliases = vec![
1219                        "SEQ".to_string(),
1220                        "KEY".to_string(),
1221                        "PATH".to_string(),
1222                        "INDEX".to_string(),
1223                        "VALUE".to_string(),
1224                        "THIS".to_string(),
1225                    ];
1226                    // If no alias, add _flattened
1227                    if lat.alias.is_none() {
1228                        lat.alias = Some("_flattened".to_string());
1229                    }
1230                }
1231                Ok(Expression::Lateral(lat))
1232            }
1233
1234            // Pass through everything else
1235            _ => Ok(expr),
1236        }
1237    }
1238}
1239
1240#[cfg(feature = "transpile")]
1241impl SnowflakeDialect {
1242    /// Format a Snowflake external table property for output.
1243    /// Some properties like LOCATION and FILE_FORMAT are uppercased keywords.
1244    fn format_external_table_property(key: &str, value: &str) -> String {
1245        let lower_key = key.to_lowercase();
1246        match lower_key.as_str() {
1247            "location" => format!("LOCATION={}", value),
1248            "file_format" => {
1249                // Format file_format value: remove spaces around =, uppercase booleans
1250                let formatted_value = Self::format_file_format_value(value);
1251                format!("FILE_FORMAT={}", formatted_value)
1252            }
1253            _ => format!("{}={}", key, value),
1254        }
1255    }
1256
1257    /// Format file_format property value:
1258    /// - Remove spaces around = signs
1259    /// - Uppercase boolean values (false -> FALSE, true -> TRUE)
1260    fn format_file_format_value(value: &str) -> String {
1261        if !value.starts_with('(') {
1262            return value.to_string();
1263        }
1264        // Strip outer parens, process inner key=value pairs
1265        let inner = value[1..value.len() - 1].trim();
1266        // Parse space-separated key=value pairs (may have spaces around =)
1267        let mut result = String::from("(");
1268        let mut parts: Vec<String> = Vec::new();
1269        // Split by whitespace and reconstruct key=value pairs
1270        let tokens: Vec<&str> = inner.split_whitespace().collect();
1271        let mut i = 0;
1272        while i < tokens.len() {
1273            let token = tokens[i];
1274            if i + 2 < tokens.len() && tokens[i + 1] == "=" {
1275                // key = value pattern
1276                let val = Self::format_property_value(tokens[i + 2]);
1277                parts.push(format!("{}={}", token, val));
1278                i += 3;
1279            } else if token.contains('=') {
1280                // key=value already joined
1281                let eq_pos = token.find('=').unwrap();
1282                let k = &token[..eq_pos];
1283                let v = Self::format_property_value(&token[eq_pos + 1..]);
1284                parts.push(format!("{}={}", k, v));
1285                i += 1;
1286            } else {
1287                parts.push(token.to_string());
1288                i += 1;
1289            }
1290        }
1291        result.push_str(&parts.join(" "));
1292        result.push(')');
1293        result
1294    }
1295
1296    /// Format a property value - uppercase boolean literals
1297    fn format_property_value(value: &str) -> String {
1298        match value.to_lowercase().as_str() {
1299            "true" => "TRUE".to_string(),
1300            "false" => "FALSE".to_string(),
1301            _ => value.to_string(),
1302        }
1303    }
1304
1305    /// Transform data types according to Snowflake TYPE_MAPPING
1306    fn transform_data_type(&self, dt: crate::expressions::DataType) -> Result<Expression> {
1307        use crate::expressions::DataType;
1308        let transformed = match dt {
1309            // TEXT -> VARCHAR
1310            DataType::Text => DataType::VarChar {
1311                length: None,
1312                parenthesized_length: false,
1313            },
1314            // STRUCT -> OBJECT
1315            DataType::Struct { fields, .. } => {
1316                // Snowflake uses OBJECT for struct types
1317                let _ = fields; // Snowflake OBJECT doesn't preserve field names in the same way
1318                DataType::Custom {
1319                    name: "OBJECT".to_string(),
1320                }
1321            }
1322            // Custom type transformations
1323            DataType::Custom { name } => {
1324                let upper_name = name.to_uppercase();
1325                match upper_name.as_str() {
1326                    // NVARCHAR -> VARCHAR (SQL Server type)
1327                    "NVARCHAR" | "NCHAR" | "NATIONAL CHARACTER VARYING" | "NATIONAL CHAR" => {
1328                        DataType::VarChar {
1329                            length: None,
1330                            parenthesized_length: false,
1331                        }
1332                    }
1333                    // STRING -> VARCHAR (Snowflake accepts both, but normalizes to VARCHAR)
1334                    "STRING" => DataType::VarChar {
1335                        length: None,
1336                        parenthesized_length: false,
1337                    },
1338                    // BIGDECIMAL -> DOUBLE
1339                    "BIGDECIMAL" => DataType::Double {
1340                        precision: None,
1341                        scale: None,
1342                    },
1343                    // NESTED -> OBJECT
1344                    "NESTED" => DataType::Custom {
1345                        name: "OBJECT".to_string(),
1346                    },
1347                    // BYTEINT -> INT
1348                    "BYTEINT" => DataType::Int {
1349                        length: None,
1350                        integer_spelling: false,
1351                    },
1352                    // CHAR VARYING -> VARCHAR
1353                    "CHAR VARYING" | "CHARACTER VARYING" => DataType::VarChar {
1354                        length: None,
1355                        parenthesized_length: false,
1356                    },
1357                    // SQL_DOUBLE -> DOUBLE
1358                    "SQL_DOUBLE" => DataType::Double {
1359                        precision: None,
1360                        scale: None,
1361                    },
1362                    // SQL_VARCHAR -> VARCHAR
1363                    "SQL_VARCHAR" => DataType::VarChar {
1364                        length: None,
1365                        parenthesized_length: false,
1366                    },
1367                    // TIMESTAMP_NTZ -> TIMESTAMPNTZ (normalize underscore form)
1368                    "TIMESTAMP_NTZ" => DataType::Custom {
1369                        name: "TIMESTAMPNTZ".to_string(),
1370                    },
1371                    // TIMESTAMP_LTZ -> TIMESTAMPLTZ (normalize underscore form)
1372                    "TIMESTAMP_LTZ" => DataType::Custom {
1373                        name: "TIMESTAMPLTZ".to_string(),
1374                    },
1375                    // TIMESTAMP_TZ -> TIMESTAMPTZ (normalize underscore form)
1376                    "TIMESTAMP_TZ" => DataType::Custom {
1377                        name: "TIMESTAMPTZ".to_string(),
1378                    },
1379                    // NCHAR VARYING -> VARCHAR
1380                    "NCHAR VARYING" => DataType::VarChar {
1381                        length: None,
1382                        parenthesized_length: false,
1383                    },
1384                    // NUMBER -> DECIMAL(38, 0) (Snowflake's default NUMBER is DECIMAL(38, 0))
1385                    "NUMBER" => DataType::Decimal {
1386                        precision: Some(38),
1387                        scale: Some(0),
1388                    },
1389                    _ if name.starts_with("NUMBER(") => {
1390                        // NUMBER(precision, scale) -> DECIMAL(precision, scale)
1391                        // Parse: "NUMBER(38, 0)" -> precision=38, scale=0
1392                        let inner = &name[7..name.len() - 1]; // strip "NUMBER(" and ")"
1393                        let parts: Vec<&str> = inner.split(',').map(|s| s.trim()).collect();
1394                        let precision = parts.first().and_then(|p| p.parse::<u32>().ok());
1395                        let scale = parts.get(1).and_then(|s| s.parse::<u32>().ok());
1396                        DataType::Decimal { precision, scale }
1397                    }
1398                    _ => DataType::Custom { name },
1399                }
1400            }
1401            // DECIMAL without precision -> DECIMAL(38, 0) (Snowflake default)
1402            DataType::Decimal {
1403                precision: None,
1404                scale: None,
1405            } => DataType::Decimal {
1406                precision: Some(38),
1407                scale: Some(0),
1408            },
1409            // FLOAT -> DOUBLE (Snowflake FLOAT is actually 64-bit DOUBLE)
1410            DataType::Float { .. } => DataType::Double {
1411                precision: None,
1412                scale: None,
1413            },
1414            // Keep all other types as-is (Snowflake is quite flexible)
1415            other => other,
1416        };
1417        Ok(Expression::DataType(transformed))
1418    }
1419
1420    /// Map date part abbreviation to canonical form (from Python SQLGlot DATE_PART_MAPPING)
1421    fn map_date_part(abbr: &str) -> Option<&'static str> {
1422        match abbr.to_uppercase().as_str() {
1423            // Year
1424            "Y" | "YY" | "YYY" | "YYYY" | "YR" | "YEARS" | "YRS" => Some("YEAR"),
1425            // Month
1426            "MM" | "MON" | "MONS" | "MONTHS" => Some("MONTH"),
1427            // Day
1428            "D" | "DD" | "DAYS" | "DAYOFMONTH" => Some("DAY"),
1429            // Day of week
1430            "DAY OF WEEK" | "WEEKDAY" | "DOW" | "DW" => Some("DAYOFWEEK"),
1431            "WEEKDAY_ISO" | "DOW_ISO" | "DW_ISO" | "DAYOFWEEK_ISO" => Some("DAYOFWEEKISO"),
1432            // Day of year
1433            "DAY OF YEAR" | "DOY" | "DY" => Some("DAYOFYEAR"),
1434            // Week
1435            "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY" => Some("WEEK"),
1436            "WEEK_ISO" | "WEEKOFYEARISO" | "WEEKOFYEAR_ISO" => Some("WEEKISO"),
1437            // Quarter
1438            "Q" | "QTR" | "QTRS" | "QUARTERS" => Some("QUARTER"),
1439            // Hour
1440            "H" | "HH" | "HR" | "HOURS" | "HRS" => Some("HOUR"),
1441            // Minute (note: 'M' could be minute in some contexts, but we keep it simple)
1442            "MI" | "MIN" | "MINUTES" | "MINS" => Some("MINUTE"),
1443            // Second
1444            "S" | "SEC" | "SECONDS" | "SECS" => Some("SECOND"),
1445            // Millisecond
1446            "MS" | "MSEC" | "MSECS" | "MSECOND" | "MSECONDS" | "MILLISEC" | "MILLISECS"
1447            | "MILLISECON" | "MILLISECONDS" => Some("MILLISECOND"),
1448            // Microsecond
1449            "US" | "USEC" | "USECS" | "MICROSEC" | "MICROSECS" | "USECOND" | "USECONDS"
1450            | "MICROSECONDS" => Some("MICROSECOND"),
1451            // Nanosecond
1452            "NS" | "NSEC" | "NANOSEC" | "NSECOND" | "NSECONDS" | "NANOSECS" => Some("NANOSECOND"),
1453            // Epoch variants
1454            "EPOCH_SECOND" | "EPOCH_SECONDS" => Some("EPOCH_SECOND"),
1455            "EPOCH_MILLISECOND" | "EPOCH_MILLISECONDS" => Some("EPOCH_MILLISECOND"),
1456            "EPOCH_MICROSECOND" | "EPOCH_MICROSECONDS" => Some("EPOCH_MICROSECOND"),
1457            "EPOCH_NANOSECOND" | "EPOCH_NANOSECONDS" => Some("EPOCH_NANOSECOND"),
1458            // Timezone
1459            "TZH" => Some("TIMEZONE_HOUR"),
1460            "TZM" => Some("TIMEZONE_MINUTE"),
1461            // Decade
1462            "DEC" | "DECS" | "DECADES" => Some("DECADE"),
1463            // Millennium
1464            "MIL" | "MILS" | "MILLENIA" => Some("MILLENNIUM"),
1465            // Century
1466            "C" | "CENT" | "CENTS" | "CENTURIES" => Some("CENTURY"),
1467            // No mapping needed (already canonical or unknown)
1468            _ => None,
1469        }
1470    }
1471
1472    /// Transform a date part identifier/expression using the mapping
1473    fn transform_date_part_arg(&self, expr: Expression) -> Expression {
1474        match &expr {
1475            // Handle string literal: 'minute' -> minute (unquoted identifier, preserving case)
1476            Expression::Literal(lit)
1477                if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) =>
1478            {
1479                let crate::expressions::Literal::String(s) = lit.as_ref() else {
1480                    unreachable!()
1481                };
1482                Expression::Identifier(crate::expressions::Identifier {
1483                    name: s.clone(),
1484                    quoted: false,
1485                    trailing_comments: Vec::new(),
1486                    span: None,
1487                })
1488            }
1489            // Handle Identifier (rare case)
1490            Expression::Identifier(id) => {
1491                if let Some(canonical) = Self::map_date_part(&id.name) {
1492                    Expression::Identifier(crate::expressions::Identifier {
1493                        name: canonical.to_string(),
1494                        quoted: false,
1495                        trailing_comments: Vec::new(),
1496                        span: None,
1497                    })
1498                } else {
1499                    // No mapping needed, keep original (Python sqlglot preserves case)
1500                    expr
1501                }
1502            }
1503            Expression::Var(v) => {
1504                if let Some(canonical) = Self::map_date_part(&v.this) {
1505                    Expression::Identifier(crate::expressions::Identifier {
1506                        name: canonical.to_string(),
1507                        quoted: false,
1508                        trailing_comments: Vec::new(),
1509                        span: None,
1510                    })
1511                } else {
1512                    expr
1513                }
1514            }
1515            // Handle Column (more common - parser treats unqualified names as columns)
1516            Expression::Column(col) if col.table.is_none() => {
1517                if let Some(canonical) = Self::map_date_part(&col.name.name) {
1518                    Expression::Identifier(crate::expressions::Identifier {
1519                        name: canonical.to_string(),
1520                        quoted: false,
1521                        trailing_comments: Vec::new(),
1522                        span: None,
1523                    })
1524                } else {
1525                    // No mapping needed, keep original (Python sqlglot preserves case)
1526                    expr
1527                }
1528            }
1529            _ => expr,
1530        }
1531    }
1532
1533    /// Like transform_date_part_arg but only handles Identifier/Column, never String literals.
1534    /// Used for native Snowflake DATE_PART where string args should stay as strings.
1535    fn transform_date_part_arg_identifiers_only(&self, expr: Expression) -> Expression {
1536        match &expr {
1537            Expression::Identifier(id) => {
1538                if let Some(canonical) = Self::map_date_part(&id.name) {
1539                    Expression::Identifier(crate::expressions::Identifier {
1540                        name: canonical.to_string(),
1541                        quoted: false,
1542                        trailing_comments: Vec::new(),
1543                        span: None,
1544                    })
1545                } else {
1546                    expr
1547                }
1548            }
1549            Expression::Var(v) => {
1550                if let Some(canonical) = Self::map_date_part(&v.this) {
1551                    Expression::Identifier(crate::expressions::Identifier {
1552                        name: canonical.to_string(),
1553                        quoted: false,
1554                        trailing_comments: Vec::new(),
1555                        span: None,
1556                    })
1557                } else {
1558                    expr
1559                }
1560            }
1561            Expression::Column(col) if col.table.is_none() => {
1562                if let Some(canonical) = Self::map_date_part(&col.name.name) {
1563                    Expression::Identifier(crate::expressions::Identifier {
1564                        name: canonical.to_string(),
1565                        quoted: false,
1566                        trailing_comments: Vec::new(),
1567                        span: None,
1568                    })
1569                } else {
1570                    expr
1571                }
1572            }
1573            _ => expr,
1574        }
1575    }
1576
1577    /// Transform JSON path for Snowflake GET_PATH function
1578    /// - Convert colon notation to dot notation (y[0]:z -> y[0].z)
1579    /// - Wrap unsafe keys in brackets ($id -> ["$id"])
1580    fn transform_json_path(path: &str) -> String {
1581        // Check if path is just a single key that needs bracket wrapping
1582        // A safe identifier is alphanumeric + underscore, starting with letter/underscore
1583        fn is_safe_identifier(s: &str) -> bool {
1584            if s.is_empty() {
1585                return false;
1586            }
1587            let mut chars = s.chars();
1588            match chars.next() {
1589                Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
1590                _ => return false,
1591            }
1592            chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
1593        }
1594
1595        // Simple path: just a key like "$id" or "field"
1596        // If no dots, brackets, or colons, it's a simple key
1597        if !path.contains('.') && !path.contains('[') && !path.contains(':') {
1598            if is_safe_identifier(path) {
1599                return path.to_string();
1600            } else {
1601                // Wrap unsafe key in bracket notation
1602                return format!("[\"{}\"]", path);
1603            }
1604        }
1605
1606        // Complex path: replace colons with dots
1607        // e.g., y[0]:z -> y[0].z
1608        let result = path.replace(':', ".");
1609        result
1610    }
1611
1612    /// Transform interval to expand abbreviated units (e.g., 'w' -> 'WEEK')
1613    fn transform_interval(&self, interval: crate::expressions::Interval) -> Result<Expression> {
1614        use crate::expressions::{Interval, Literal};
1615
1616        // Unit abbreviation mapping (from Python SQLGlot UNABBREVIATED_UNIT_NAME)
1617        fn expand_unit(abbr: &str) -> &'static str {
1618            match abbr.to_uppercase().as_str() {
1619                "D" => "DAY",
1620                "H" => "HOUR",
1621                "M" => "MINUTE",
1622                "MS" => "MILLISECOND",
1623                "NS" => "NANOSECOND",
1624                "Q" => "QUARTER",
1625                "S" => "SECOND",
1626                "US" => "MICROSECOND",
1627                "W" => "WEEK",
1628                "Y" => "YEAR",
1629                // Full forms (normalize to singular, uppercase)
1630                "WEEK" | "WEEKS" => "WEEK",
1631                "DAY" | "DAYS" => "DAY",
1632                "HOUR" | "HOURS" => "HOUR",
1633                "MINUTE" | "MINUTES" => "MINUTE",
1634                "SECOND" | "SECONDS" => "SECOND",
1635                "MONTH" | "MONTHS" => "MONTH",
1636                "YEAR" | "YEARS" => "YEAR",
1637                "QUARTER" | "QUARTERS" => "QUARTER",
1638                "MILLISECOND" | "MILLISECONDS" => "MILLISECOND",
1639                "MICROSECOND" | "MICROSECONDS" => "MICROSECOND",
1640                "NANOSECOND" | "NANOSECONDS" => "NANOSECOND",
1641                _ => "", // Unknown unit, return empty to indicate no match
1642            }
1643        }
1644
1645        /// Parse an interval string like "1 w" into (value, unit)
1646        fn parse_interval_string(s: &str) -> Option<(&str, &str)> {
1647            let s = s.trim();
1648
1649            // Find where the number ends and the unit begins
1650            // Number can be: optional -, digits, optional decimal point, more digits
1651            let mut num_end = 0;
1652            let mut chars = s.chars().peekable();
1653
1654            // Skip leading minus
1655            if chars.peek() == Some(&'-') {
1656                chars.next();
1657                num_end += 1;
1658            }
1659
1660            // Skip digits
1661            while let Some(&c) = chars.peek() {
1662                if c.is_ascii_digit() {
1663                    chars.next();
1664                    num_end += 1;
1665                } else {
1666                    break;
1667                }
1668            }
1669
1670            // Skip optional decimal point and more digits
1671            if chars.peek() == Some(&'.') {
1672                chars.next();
1673                num_end += 1;
1674                while let Some(&c) = chars.peek() {
1675                    if c.is_ascii_digit() {
1676                        chars.next();
1677                        num_end += 1;
1678                    } else {
1679                        break;
1680                    }
1681                }
1682            }
1683
1684            if num_end == 0 || (num_end == 1 && s.starts_with('-')) {
1685                return None; // No number found
1686            }
1687
1688            let value = &s[..num_end];
1689            let rest = s[num_end..].trim();
1690
1691            // Rest should be alphabetic (the unit)
1692            if rest.is_empty() || !rest.chars().all(|c| c.is_ascii_alphabetic()) {
1693                return None;
1694            }
1695
1696            Some((value, rest))
1697        }
1698
1699        // Check if the interval value is a string literal with embedded value+unit
1700        if let Some(Expression::Literal(ref lit)) = interval.this {
1701            if let Literal::String(ref s) = lit.as_ref() {
1702                if let Some((value, unit)) = parse_interval_string(s) {
1703                    let expanded = expand_unit(unit);
1704                    if !expanded.is_empty() {
1705                        // Construct new string with expanded unit
1706                        let new_value = format!("{} {}", value, expanded);
1707
1708                        return Ok(Expression::Interval(Box::new(Interval {
1709                            this: Some(Expression::Literal(Box::new(Literal::String(new_value)))),
1710                            unit: None, // Unit is now part of the string (SINGLE_STRING_INTERVAL style)
1711                        })));
1712                    }
1713                }
1714            }
1715        }
1716
1717        // No transformation needed
1718        Ok(Expression::Interval(Box::new(interval)))
1719    }
1720
1721    fn transform_function(&self, f: Function) -> Result<Expression> {
1722        // First, recursively transform all function arguments
1723        let transformed_args: Vec<Expression> = f
1724            .args
1725            .into_iter()
1726            .map(|arg| self.transform_expr(arg))
1727            .collect::<Result<Vec<_>>>()?;
1728
1729        let f = Function {
1730            name: f.name,
1731            args: transformed_args,
1732            distinct: f.distinct,
1733            trailing_comments: f.trailing_comments,
1734            use_bracket_syntax: f.use_bracket_syntax,
1735            no_parens: f.no_parens,
1736            quoted: f.quoted,
1737            span: None,
1738            inferred_type: None,
1739        };
1740
1741        let name_upper = f.name.to_uppercase();
1742        match name_upper.as_str() {
1743            // IFNULL -> COALESCE (standardize to COALESCE)
1744            "IFNULL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1745                original_name: None,
1746                expressions: f.args,
1747                inferred_type: None,
1748            }))),
1749
1750            // NVL -> COALESCE (both work in Snowflake, but COALESCE is standard per SQLGlot)
1751            "NVL" if f.args.len() == 2 => Ok(Expression::Coalesce(Box::new(VarArgFunc {
1752                original_name: None,
1753                expressions: f.args,
1754                inferred_type: None,
1755            }))),
1756
1757            // NVL2 is native to Snowflake
1758            "NVL2" => Ok(Expression::Function(Box::new(f))),
1759
1760            // GROUP_CONCAT -> LISTAGG in Snowflake
1761            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1762                Function::new("LISTAGG".to_string(), f.args),
1763            ))),
1764
1765            // STRING_AGG -> LISTAGG in Snowflake
1766            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
1767                Function::new("LISTAGG".to_string(), f.args),
1768            ))),
1769
1770            // SUBSTR -> SUBSTRING (both work in Snowflake)
1771            "SUBSTR" => Ok(Expression::Function(Box::new(Function::new(
1772                "SUBSTRING".to_string(),
1773                f.args,
1774            )))),
1775
1776            // UNNEST -> FLATTEN
1777            "UNNEST" => Ok(Expression::Function(Box::new(Function::new(
1778                "FLATTEN".to_string(),
1779                f.args,
1780            )))),
1781
1782            // EXPLODE -> FLATTEN
1783            "EXPLODE" => Ok(Expression::Function(Box::new(Function::new(
1784                "FLATTEN".to_string(),
1785                f.args,
1786            )))),
1787
1788            // CURRENT_DATE is native
1789            "CURRENT_DATE" => Ok(Expression::CurrentDate(crate::expressions::CurrentDate)),
1790
1791            // NOW -> CURRENT_TIMESTAMP (preserving parens style)
1792            "NOW" => Ok(Expression::Function(Box::new(Function {
1793                name: "CURRENT_TIMESTAMP".to_string(),
1794                args: f.args,
1795                distinct: false,
1796                trailing_comments: Vec::new(),
1797                use_bracket_syntax: false,
1798                no_parens: f.no_parens,
1799                quoted: false,
1800                span: None,
1801                inferred_type: None,
1802            }))),
1803
1804            // GETDATE -> CURRENT_TIMESTAMP (preserving parens style)
1805            "GETDATE" => Ok(Expression::Function(Box::new(Function {
1806                name: "CURRENT_TIMESTAMP".to_string(),
1807                args: f.args,
1808                distinct: false,
1809                trailing_comments: Vec::new(),
1810                use_bracket_syntax: false,
1811                no_parens: f.no_parens,
1812                quoted: false,
1813                span: None,
1814                inferred_type: None,
1815            }))),
1816
1817            // CURRENT_TIMESTAMP - always output with parens in Snowflake
1818            // Note: LOCALTIMESTAMP converts to CURRENT_TIMESTAMP without parens,
1819            // but explicit CURRENT_TIMESTAMP calls should have parens
1820            "CURRENT_TIMESTAMP" if f.args.is_empty() => {
1821                Ok(Expression::Function(Box::new(Function {
1822                    name: "CURRENT_TIMESTAMP".to_string(),
1823                    args: Vec::new(),
1824                    distinct: false,
1825                    trailing_comments: Vec::new(),
1826                    use_bracket_syntax: false,
1827                    no_parens: false, // Always output with parens
1828                    quoted: false,
1829                    span: None,
1830                    inferred_type: None,
1831                })))
1832            }
1833
1834            // TO_DATE with single string arg that looks like a date -> CAST(arg AS DATE)
1835            // Per Python SQLGlot: TO_DATE('2013-04-05') -> CAST('2013-04-05' AS DATE)
1836            // But TO_DATE('12345') stays as is (doesn't look like a date)
1837            "TO_DATE" => {
1838                if f.args.len() == 1 {
1839                    if let Expression::Literal(lit) = &f.args[0] {
1840                        if let crate::expressions::Literal::String(s) = lit.as_ref() {
1841                            // Check if the string looks like a date (contains dashes like 2013-04-05)
1842                            if s.contains('-') && s.len() >= 8 && s.len() <= 12 {
1843                                return Ok(Expression::Cast(Box::new(Cast {
1844                                    this: f.args.into_iter().next().unwrap(),
1845                                    to: crate::expressions::DataType::Date,
1846                                    double_colon_syntax: false,
1847                                    trailing_comments: Vec::new(),
1848                                    format: None,
1849                                    default: None,
1850                                    inferred_type: None,
1851                                })));
1852                            }
1853                        }
1854                    }
1855                }
1856                // Normalize format string (2nd arg) if present
1857                let mut args = f.args;
1858                if args.len() >= 2 {
1859                    args[1] = Self::normalize_format_arg(args[1].clone());
1860                }
1861                Ok(Expression::Function(Box::new(Function::new(
1862                    "TO_DATE".to_string(),
1863                    args,
1864                ))))
1865            }
1866
1867            // TO_TIME with single string arg -> CAST(arg AS TIME)
1868            "TO_TIME" => {
1869                if f.args.len() == 1 {
1870                    if let Expression::Literal(lit) = &f.args[0] {
1871                        if let crate::expressions::Literal::String(_) = lit.as_ref() {
1872                            return Ok(Expression::Cast(Box::new(Cast {
1873                                this: f.args.into_iter().next().unwrap(),
1874                                to: crate::expressions::DataType::Time {
1875                                    precision: None,
1876                                    timezone: false,
1877                                },
1878                                double_colon_syntax: false,
1879                                trailing_comments: Vec::new(),
1880                                format: None,
1881                                default: None,
1882                                inferred_type: None,
1883                            })));
1884                        }
1885                    }
1886                }
1887                // Normalize format string (2nd arg) if present
1888                let mut args = f.args;
1889                if args.len() >= 2 {
1890                    args[1] = Self::normalize_format_arg(args[1].clone());
1891                }
1892                Ok(Expression::Function(Box::new(Function::new(
1893                    "TO_TIME".to_string(),
1894                    args,
1895                ))))
1896            }
1897
1898            // TO_TIMESTAMP: Snowflake has multiple forms:
1899            // 1. TO_TIMESTAMP('datetime_string') -> CAST('...' AS TIMESTAMP)
1900            // 2. TO_TIMESTAMP('epoch_string') -> UnixToTime(epoch_string)
1901            // 3. TO_TIMESTAMP(number) -> UnixToTime(number)
1902            // 4. TO_TIMESTAMP(number, scale) where scale is int -> UnixToTime(number, scale)
1903            // 5. TO_TIMESTAMP(string, format) where format is string -> StrToTime(string, format)
1904            "TO_TIMESTAMP" => {
1905                let args = f.args;
1906                if args.len() == 1 {
1907                    let arg = &args[0];
1908                    match arg {
1909                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if Self::looks_like_datetime(s)) =>
1910                        {
1911                            let Literal::String(_) = lit.as_ref() else {
1912                                unreachable!()
1913                            };
1914                            // Case 1: datetime string -> CAST AS TIMESTAMP
1915                            return Ok(Expression::Cast(Box::new(Cast {
1916                                this: args.into_iter().next().unwrap(),
1917                                to: DataType::Timestamp {
1918                                    precision: None,
1919                                    timezone: false,
1920                                },
1921                                double_colon_syntax: false,
1922                                trailing_comments: vec![],
1923                                format: None,
1924                                default: None,
1925                                inferred_type: None,
1926                            })));
1927                        }
1928                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if Self::looks_like_epoch(s)) =>
1929                        {
1930                            let Literal::String(_) = lit.as_ref() else {
1931                                unreachable!()
1932                            };
1933                            // Case 2: epoch number as string -> UnixToTime
1934                            return Ok(Expression::UnixToTime(Box::new(
1935                                crate::expressions::UnixToTime {
1936                                    this: Box::new(args.into_iter().next().unwrap()),
1937                                    scale: None,
1938                                    zone: None,
1939                                    hours: None,
1940                                    minutes: None,
1941                                    format: None,
1942                                    target_type: None,
1943                                },
1944                            )));
1945                        }
1946                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1947                            // Case 3: number -> UnixToTime
1948                            return Ok(Expression::UnixToTime(Box::new(
1949                                crate::expressions::UnixToTime {
1950                                    this: Box::new(args.into_iter().next().unwrap()),
1951                                    scale: None,
1952                                    zone: None,
1953                                    hours: None,
1954                                    minutes: None,
1955                                    format: None,
1956                                    target_type: None,
1957                                },
1958                            )));
1959                        }
1960                        Expression::Neg(_) => {
1961                            // Case 3: number -> UnixToTime
1962                            return Ok(Expression::UnixToTime(Box::new(
1963                                crate::expressions::UnixToTime {
1964                                    this: Box::new(args.into_iter().next().unwrap()),
1965                                    scale: None,
1966                                    zone: None,
1967                                    hours: None,
1968                                    minutes: None,
1969                                    format: None,
1970                                    target_type: None,
1971                                },
1972                            )));
1973                        }
1974                        _ => {
1975                            // Unknown single arg, keep as function
1976                            return Ok(Expression::Function(Box::new(Function::new(
1977                                "TO_TIMESTAMP".to_string(),
1978                                args,
1979                            ))));
1980                        }
1981                    }
1982                } else if args.len() == 2 {
1983                    let second_arg = &args[1];
1984                    // Check if second arg is an integer (scale) or a format string
1985                    let is_int_scale = match second_arg {
1986                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
1987                            let Literal::Number(n) = lit.as_ref() else {
1988                                unreachable!()
1989                            };
1990                            n.parse::<i64>().is_ok()
1991                        }
1992                        _ => false,
1993                    };
1994
1995                    if is_int_scale {
1996                        // Case 4: TO_TIMESTAMP(number, scale) -> UnixToTime
1997                        let mut args_iter = args.into_iter();
1998                        let value = args_iter.next().unwrap();
1999                        let scale_expr = args_iter.next().unwrap();
2000                        let scale = if let Expression::Literal(lit) = &scale_expr {
2001                            if let Literal::Number(n) = lit.as_ref() {
2002                                n.parse::<i64>().ok()
2003                            } else {
2004                                None
2005                            }
2006                        } else {
2007                            None
2008                        };
2009                        return Ok(Expression::UnixToTime(Box::new(
2010                            crate::expressions::UnixToTime {
2011                                this: Box::new(value),
2012                                scale,
2013                                zone: None,
2014                                hours: None,
2015                                minutes: None,
2016                                format: None,
2017                                target_type: None,
2018                            },
2019                        )));
2020                    } else {
2021                        // Case 5: TO_TIMESTAMP(string, format) -> StrToTime
2022                        let mut args_iter = args.into_iter();
2023                        let value = args_iter.next().unwrap();
2024                        let format_expr = args_iter.next().unwrap();
2025                        let format_str = match &format_expr {
2026                            Expression::Literal(lit)
2027                                if matches!(lit.as_ref(), Literal::String(_)) =>
2028                            {
2029                                let Literal::String(s) = lit.as_ref() else {
2030                                    unreachable!()
2031                                };
2032                                s.clone()
2033                            }
2034                            _ => {
2035                                // Non-string format, keep as function
2036                                return Ok(Expression::Function(Box::new(Function::new(
2037                                    "TO_TIMESTAMP".to_string(),
2038                                    vec![value, format_expr],
2039                                ))));
2040                            }
2041                        };
2042                        // Normalize Snowflake format to target-neutral
2043                        let normalized_format = Self::normalize_snowflake_format(&format_str);
2044                        return Ok(Expression::StrToTime(Box::new(
2045                            crate::expressions::StrToTime {
2046                                this: Box::new(value),
2047                                format: normalized_format,
2048                                zone: None,
2049                                safe: None,
2050                                target_type: None,
2051                            },
2052                        )));
2053                    }
2054                }
2055                // More than 2 args or other cases, keep as function
2056                Ok(Expression::Function(Box::new(Function::new(
2057                    "TO_TIMESTAMP".to_string(),
2058                    args,
2059                ))))
2060            }
2061
2062            // Canonicalize temporal TO_CHAR so target generators can translate the
2063            // format model. Numeric/binary TO_CHAR overloads stay source-specific.
2064            "TO_CHAR"
2065                if f.args.len() == 2
2066                    && Self::is_temporal_expr(&f.args[0])
2067                    && matches!(&f.args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))) =>
2068            {
2069                let mut args = f.args;
2070                let this = args.remove(0);
2071                let Literal::String(format) = (match args.remove(0) {
2072                    Expression::Literal(lit) => *lit,
2073                    _ => unreachable!(),
2074                }) else {
2075                    unreachable!()
2076                };
2077
2078                Ok(Expression::TimeToStr(Box::new(
2079                    crate::expressions::TimeToStr {
2080                        this: Box::new(this),
2081                        format: Self::normalize_snowflake_format(&format),
2082                        culture: None,
2083                        zone: None,
2084                    },
2085                )))
2086            }
2087
2088            // Other TO_CHAR overloads are native to Snowflake.
2089            "TO_CHAR" => Ok(Expression::Function(Box::new(f))),
2090
2091            // ROUND with named args: ROUND(EXPR => x, SCALE => y, ROUNDING_MODE => z)
2092            // -> ROUND(x, y) or ROUND(x, y, z)
2093            "ROUND"
2094                if f.args
2095                    .iter()
2096                    .any(|a| matches!(a, Expression::NamedArgument(_))) =>
2097            {
2098                let mut expr_val = None;
2099                let mut scale_val = None;
2100                let mut rounding_mode_val = None;
2101                for arg in &f.args {
2102                    if let Expression::NamedArgument(na) = arg {
2103                        match na.name.name.to_uppercase().as_str() {
2104                            "EXPR" => expr_val = Some(na.value.clone()),
2105                            "SCALE" => scale_val = Some(na.value.clone()),
2106                            "ROUNDING_MODE" => rounding_mode_val = Some(na.value.clone()),
2107                            _ => {}
2108                        }
2109                    }
2110                }
2111                if let Some(expr) = expr_val {
2112                    let mut args = vec![expr];
2113                    if let Some(scale) = scale_val {
2114                        args.push(scale);
2115                    }
2116                    if let Some(mode) = rounding_mode_val {
2117                        args.push(mode);
2118                    }
2119                    Ok(Expression::Function(Box::new(Function::new(
2120                        "ROUND".to_string(),
2121                        args,
2122                    ))))
2123                } else {
2124                    Ok(Expression::Function(Box::new(f)))
2125                }
2126            }
2127
2128            // DATE_FORMAT -> TO_CHAR in Snowflake
2129            // Also converts strftime format to Snowflake format and wraps first arg in CAST AS TIMESTAMP
2130            "DATE_FORMAT" => {
2131                let mut args = f.args;
2132                // Wrap first arg in CAST AS TIMESTAMP if it's a string literal
2133                if !args.is_empty() {
2134                    if matches!(&args[0], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
2135                    {
2136                        args[0] = Expression::Cast(Box::new(crate::expressions::Cast {
2137                            this: args[0].clone(),
2138                            to: DataType::Timestamp {
2139                                precision: None,
2140                                timezone: false,
2141                            },
2142                            trailing_comments: Vec::new(),
2143                            double_colon_syntax: false,
2144                            format: None,
2145                            default: None,
2146                            inferred_type: None,
2147                        }));
2148                    }
2149                }
2150                // Convert strftime format to Snowflake format
2151                if args.len() >= 2 {
2152                    if let Expression::Literal(ref lit) = args[1] {
2153                        if let Literal::String(ref fmt) = lit.as_ref() {
2154                            let sf_fmt = strftime_to_snowflake_format(fmt);
2155                            args[1] = Expression::Literal(Box::new(Literal::String(sf_fmt)));
2156                        }
2157                    }
2158                }
2159                Ok(Expression::Function(Box::new(Function::new(
2160                    "TO_CHAR".to_string(),
2161                    args,
2162                ))))
2163            }
2164
2165            // ARRAY -> ARRAY_CONSTRUCT
2166            "ARRAY" => Ok(Expression::Function(Box::new(Function::new(
2167                "ARRAY_CONSTRUCT".to_string(),
2168                f.args,
2169            )))),
2170
2171            // STRUCT -> OBJECT_CONSTRUCT
2172            // Convert STRUCT(value AS name, ...) to OBJECT_CONSTRUCT('name', value, ...)
2173            "STRUCT" => {
2174                let mut oc_args = Vec::new();
2175                for arg in f.args {
2176                    match arg {
2177                        Expression::Alias(a) => {
2178                            // Named field: value AS name -> 'name', value
2179                            oc_args.push(Expression::Literal(Box::new(
2180                                crate::expressions::Literal::String(a.alias.name.clone()),
2181                            )));
2182                            oc_args.push(a.this);
2183                        }
2184                        other => {
2185                            // Unnamed field: just pass through
2186                            oc_args.push(other);
2187                        }
2188                    }
2189                }
2190                Ok(Expression::Function(Box::new(Function::new(
2191                    "OBJECT_CONSTRUCT".to_string(),
2192                    oc_args,
2193                ))))
2194            }
2195
2196            // JSON_EXTRACT -> GET_PATH or GET in Snowflake
2197            "JSON_EXTRACT" => Ok(Expression::Function(Box::new(Function::new(
2198                "GET_PATH".to_string(),
2199                f.args,
2200            )))),
2201
2202            // JSON_EXTRACT_SCALAR -> JSON_EXTRACT_PATH_TEXT
2203            "JSON_EXTRACT_SCALAR" => Ok(Expression::Function(Box::new(Function::new(
2204                "JSON_EXTRACT_PATH_TEXT".to_string(),
2205                f.args,
2206            )))),
2207
2208            // LEN -> LENGTH
2209            "LEN" if f.args.len() == 1 => Ok(Expression::Length(Box::new(UnaryFunc::new(
2210                f.args.into_iter().next().unwrap(),
2211            )))),
2212
2213            // CEILING -> CEIL (both work)
2214            "CEILING" if f.args.len() == 1 => Ok(Expression::Ceil(Box::new(CeilFunc {
2215                this: f.args.into_iter().next().unwrap(),
2216                decimals: None,
2217                to: None,
2218            }))),
2219
2220            // CHARINDEX -> POSITION or CHARINDEX (native)
2221            "CHARINDEX" => Ok(Expression::Function(Box::new(f))),
2222
2223            // SPLIT is native to Snowflake - keep as-is
2224            "SPLIT" => Ok(Expression::Function(Box::new(f))),
2225
2226            // ARRAY_AGG is native to Snowflake
2227            "ARRAY_AGG" => Ok(Expression::Function(Box::new(f))),
2228
2229            // PARSE_JSON for JSON parsing
2230            "JSON_PARSE" | "PARSE_JSON" => Ok(Expression::Function(Box::new(Function::new(
2231                "PARSE_JSON".to_string(),
2232                f.args,
2233            )))),
2234
2235            // RAND -> Rand (to use RANDOM in Snowflake)
2236            "RAND" => {
2237                let seed = f.args.first().cloned().map(Box::new);
2238                Ok(Expression::Rand(Box::new(crate::expressions::Rand {
2239                    seed,
2240                    lower: None,
2241                    upper: None,
2242                })))
2243            }
2244
2245            // SHA -> SHA1
2246            "SHA" => Ok(Expression::Function(Box::new(Function::new(
2247                "SHA1".to_string(),
2248                f.args,
2249            )))),
2250
2251            // APPROX_COUNT_DISTINCT is native
2252            "APPROX_DISTINCT" => Ok(Expression::Function(Box::new(Function::new(
2253                "APPROX_COUNT_DISTINCT".to_string(),
2254                f.args,
2255            )))),
2256
2257            // GEN_RANDOM_UUID/UUID -> Uuid AST node
2258            "GEN_RANDOM_UUID" | "UUID" => {
2259                Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2260                    this: None,
2261                    name: None,
2262                    is_string: None,
2263                })))
2264            }
2265
2266            // NEWID -> Uuid AST node
2267            "NEWID" => Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2268                this: None,
2269                name: None,
2270                is_string: None,
2271            }))),
2272
2273            // UUID_STRING -> Uuid AST node (without args only; with args keep as Function for identity)
2274            "UUID_STRING" => {
2275                if f.args.is_empty() {
2276                    Ok(Expression::Uuid(Box::new(crate::expressions::Uuid {
2277                        this: None,
2278                        name: None,
2279                        is_string: None,
2280                    })))
2281                } else {
2282                    Ok(Expression::Function(Box::new(Function::new(
2283                        "UUID_STRING".to_string(),
2284                        f.args,
2285                    ))))
2286                }
2287            }
2288
2289            // IF -> IFF (convert to IfFunc AST node)
2290            "IF" if f.args.len() >= 2 => {
2291                let mut args = f.args;
2292                let condition = args.remove(0);
2293                let true_val = args.remove(0);
2294                let false_val = if !args.is_empty() {
2295                    Some(args.remove(0))
2296                } else {
2297                    None
2298                };
2299                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
2300                    condition,
2301                    true_value: true_val,
2302                    false_value: Some(
2303                        false_val.unwrap_or(Expression::Null(crate::expressions::Null)),
2304                    ),
2305                    original_name: Some("IFF".to_string()),
2306                    inferred_type: None,
2307                })))
2308            }
2309
2310            // SQUARE(x) -> POWER(x, 2)
2311            "SQUARE" if f.args.len() == 1 => {
2312                let x = f.args.into_iter().next().unwrap();
2313                Ok(Expression::Power(Box::new(
2314                    crate::expressions::BinaryFunc {
2315                        original_name: None,
2316                        this: x,
2317                        expression: Expression::number(2),
2318                        inferred_type: None,
2319                    },
2320                )))
2321            }
2322
2323            // POW(x, y) -> POWER(x, y)
2324            "POW" if f.args.len() == 2 => {
2325                let mut args = f.args.into_iter();
2326                let x = args.next().unwrap();
2327                let y = args.next().unwrap();
2328                Ok(Expression::Power(Box::new(
2329                    crate::expressions::BinaryFunc {
2330                        original_name: None,
2331                        this: x,
2332                        expression: y,
2333                        inferred_type: None,
2334                    },
2335                )))
2336            }
2337
2338            // MOD(x, y) -> x % y (modulo operator)
2339            "MOD" if f.args.len() == 2 => {
2340                let mut args = f.args.into_iter();
2341                let x = args.next().unwrap();
2342                let y = args.next().unwrap();
2343                Ok(Expression::Mod(Box::new(crate::expressions::BinaryOp {
2344                    left: x,
2345                    right: y,
2346                    left_comments: Vec::new(),
2347                    operator_comments: Vec::new(),
2348                    trailing_comments: Vec::new(),
2349                    inferred_type: None,
2350                })))
2351            }
2352
2353            // APPROXIMATE_JACCARD_INDEX -> APPROXIMATE_SIMILARITY
2354            "APPROXIMATE_JACCARD_INDEX" => Ok(Expression::Function(Box::new(Function::new(
2355                "APPROXIMATE_SIMILARITY".to_string(),
2356                f.args,
2357            )))),
2358
2359            // ARRAY_CONSTRUCT -> Array with bracket notation in Snowflake
2360            "ARRAY_CONSTRUCT" => Ok(Expression::ArrayFunc(Box::new(
2361                crate::expressions::ArrayConstructor {
2362                    expressions: f.args,
2363                    bracket_notation: true,
2364                    use_list_keyword: false,
2365                    inferred_type: None,
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            // Canonicalize TRY_TO_DOUBLE so targets with nullable conversion
3376            // functions can preserve Snowflake's NULL-on-failure behavior.
3377            "TRY_TO_DOUBLE" if f.args.len() == 1 => {
3378                let this = f.args.into_iter().next().unwrap();
3379                Ok(Expression::ToDouble(Box::new(
3380                    crate::expressions::ToDouble {
3381                        this: Box::new(this),
3382                        format: None,
3383                        safe: Some(Box::new(Expression::Boolean(
3384                            crate::expressions::BooleanLiteral { value: true },
3385                        ))),
3386                    },
3387                )))
3388            }
3389            "TRY_TO_DOUBLE"
3390                if f.args.len() == 2
3391                    && matches!(&f.args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))) =>
3392            {
3393                let mut args = f.args;
3394                let this = args.remove(0);
3395                let Literal::String(format) = (match args.remove(0) {
3396                    Expression::Literal(lit) => *lit,
3397                    _ => unreachable!(),
3398                }) else {
3399                    unreachable!()
3400                };
3401                Ok(Expression::ToDouble(Box::new(
3402                    crate::expressions::ToDouble {
3403                        this: Box::new(this),
3404                        format: Some(format),
3405                        safe: Some(Box::new(Expression::Boolean(
3406                            crate::expressions::BooleanLiteral { value: true },
3407                        ))),
3408                    },
3409                )))
3410            }
3411            "TRY_TO_DOUBLE" => Ok(Expression::Function(Box::new(f))),
3412
3413            // REGEXP_REPLACE with 2 args -> add empty string replacement
3414            "REGEXP_REPLACE" if f.args.len() == 2 => {
3415                let mut args = f.args;
3416                args.push(Expression::Literal(Box::new(
3417                    crate::expressions::Literal::String(String::new()),
3418                )));
3419                Ok(Expression::Function(Box::new(Function::new(
3420                    "REGEXP_REPLACE".to_string(),
3421                    args,
3422                ))))
3423            }
3424
3425            // LAST_DAY(x, MONTH) -> LAST_DAY(x) in Snowflake (strip MONTH default)
3426            "LAST_DAY" if f.args.len() == 2 => {
3427                let mut args = f.args;
3428                let date = args.remove(0);
3429                let unit = args.remove(0);
3430                let unit_str = match &unit {
3431                    Expression::Column(c) => c.name.name.to_uppercase(),
3432                    Expression::Identifier(i) => i.name.to_uppercase(),
3433                    _ => String::new(),
3434                };
3435                if unit_str == "MONTH" {
3436                    Ok(Expression::Function(Box::new(Function::new(
3437                        "LAST_DAY".to_string(),
3438                        vec![date],
3439                    ))))
3440                } else {
3441                    Ok(Expression::Function(Box::new(Function::new(
3442                        "LAST_DAY".to_string(),
3443                        vec![date, unit],
3444                    ))))
3445                }
3446            }
3447
3448            // EXTRACT('field', expr) function-call syntax -> DATE_PART('field', expr)
3449            "EXTRACT" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
3450                "DATE_PART".to_string(),
3451                f.args,
3452            )))),
3453
3454            // ENDS_WITH/ENDSWITH -> EndsWith AST node
3455            "ENDS_WITH" | "ENDSWITH" if f.args.len() == 2 => {
3456                let mut args = f.args;
3457                let this = args.remove(0);
3458                let expr = args.remove(0);
3459                Ok(Expression::EndsWith(Box::new(
3460                    crate::expressions::BinaryFunc {
3461                        original_name: None,
3462                        this,
3463                        expression: expr,
3464                        inferred_type: None,
3465                    },
3466                )))
3467            }
3468
3469            // Pass through everything else
3470            _ => Ok(Expression::Function(Box::new(f))),
3471        }
3472    }
3473
3474    /// Check if a string looks like a datetime (contains date separators, not just digits)
3475    fn looks_like_datetime(s: &str) -> bool {
3476        // A datetime string typically contains dashes, colons, or spaces
3477        // A numeric/epoch string is just digits (possibly with a dot)
3478        s.contains('-') || s.contains(':') || s.contains(' ') || s.contains('/')
3479    }
3480
3481    /// Check if a string looks like an epoch number (only digits, possibly with a dot)
3482    fn looks_like_epoch(s: &str) -> bool {
3483        !s.is_empty() && s.chars().all(|c| c.is_ascii_digit() || c == '.')
3484    }
3485
3486    /// Wrap an expression in parentheses if it's a complex expression (binary op, etc.)
3487    fn maybe_paren(expr: Expression) -> Expression {
3488        match &expr {
3489            Expression::Sub(_) | Expression::Add(_) | Expression::Mul(_) | Expression::Div(_) => {
3490                Expression::Paren(Box::new(crate::expressions::Paren {
3491                    this: expr,
3492                    trailing_comments: Vec::new(),
3493                }))
3494            }
3495            _ => expr,
3496        }
3497    }
3498
3499    /// Normalize Snowflake date/time format strings to canonical lowercase form.
3500    /// YYYY -> yyyy, MM -> mm, DD -> DD (stays), HH24 -> hh24, HH12 -> hh12,
3501    /// MI -> mi, SS -> ss, FF -> ff, AM/PM -> pm, quoted "T" -> T
3502    fn normalize_snowflake_format(format: &str) -> String {
3503        let mut result = String::new();
3504        let mut cursor = crate::format_tokens::FormatTokenCursor::new(format);
3505        while let Some(ch) = cursor.peek() {
3506            // Quoted literals are copied without recognizing format tokens.
3507            if cursor.consume_prefix("\"", false) {
3508                while let Some(literal) = cursor.next_char() {
3509                    if literal == '"' {
3510                        break;
3511                    }
3512                    result.push(literal);
3513                }
3514                continue;
3515            }
3516
3517            // Multi-char patterns (check longest first)
3518            if cursor.consume_prefix("YYYY", true) {
3519                result.push_str("yyyy");
3520            } else if cursor.consume_prefix("YY", true) {
3521                result.push_str("yy");
3522            } else if cursor.consume_prefix("MMMM", true) {
3523                result.push_str("mmmm");
3524            } else if cursor.consume_prefix("MON", true) {
3525                result.push_str("mon");
3526            } else if cursor.consume_prefix("MM", true) {
3527                result.push_str("mm");
3528            } else if cursor.consume_prefix("DD", true) {
3529                result.push_str("DD");
3530            } else if cursor.consume_prefix("DY", true) {
3531                result.push_str("dy");
3532            } else if cursor.consume_prefix("HH24", true) {
3533                result.push_str("hh24");
3534            } else if cursor.consume_prefix("HH12", true) {
3535                result.push_str("hh12");
3536            } else if cursor.consume_prefix("HH", true) {
3537                result.push_str("hh");
3538            } else if cursor.consume_prefix("MISS", true) {
3539                // MISS = MI + SS
3540                result.push_str("miss");
3541            } else if cursor.consume_prefix("MI", true) {
3542                result.push_str("mi");
3543            } else if cursor.consume_prefix("SS", true) {
3544                result.push_str("ss");
3545            } else if cursor.consume_prefix("FF", true) {
3546                if let Some(digit) = cursor.peek().filter(|c| c.is_ascii_digit()) {
3547                    result.push_str("ff");
3548                    result.push(digit);
3549                    cursor.next_char();
3550                } else {
3551                    // Plain FF normalizes to nanosecond precision.
3552                    result.push_str("ff9");
3553                }
3554            } else if cursor.consume_prefix("AM", true) || cursor.consume_prefix("PM", true) {
3555                result.push_str("pm");
3556            } else if cursor.consume_prefix("TZH", true) {
3557                result.push_str("tzh");
3558            } else if cursor.consume_prefix("TZM", true) {
3559                result.push_str("tzm");
3560            } else {
3561                // Keep separators and other characters as-is
3562                result.push(ch);
3563                cursor.next_char();
3564            }
3565        }
3566        result
3567    }
3568
3569    fn is_temporal_expr(expr: &Expression) -> bool {
3570        match expr {
3571            Expression::CurrentDate(_)
3572            | Expression::CurrentTime(_)
3573            | Expression::CurrentTimestamp(_)
3574            | Expression::CurrentTimestampLTZ(_)
3575            | Expression::CurrentDatetime(_)
3576            | Expression::Localtime(_)
3577            | Expression::Localtimestamp(_)
3578            | Expression::Systimestamp(_)
3579            | Expression::UtcTime(_)
3580            | Expression::UtcTimestamp(_)
3581            | Expression::Date(_)
3582            | Expression::Time(_)
3583            | Expression::ToDate(_)
3584            | Expression::ToTimestamp(_)
3585            | Expression::DateStrToDate(_)
3586            | Expression::TimeStrToTime(_)
3587            | Expression::StrToTime(_) => true,
3588            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
3589                matches!(
3590                    cast.to,
3591                    DataType::Date | DataType::Time { .. } | DataType::Timestamp { .. }
3592                )
3593            }
3594            Expression::Paren(paren) => Self::is_temporal_expr(&paren.this),
3595            other => matches!(
3596                other.inferred_type(),
3597                Some(DataType::Date | DataType::Time { .. } | DataType::Timestamp { .. })
3598            ),
3599        }
3600    }
3601
3602    /// Normalize format string argument if it's a string literal
3603    fn normalize_format_arg(expr: Expression) -> Expression {
3604        if let Expression::Literal(lit) = &expr {
3605            if let crate::expressions::Literal::String(s) = lit.as_ref() {
3606                let normalized = Self::normalize_snowflake_format(s);
3607                Expression::Literal(Box::new(crate::expressions::Literal::String(normalized)))
3608            } else {
3609                expr.clone()
3610            }
3611        } else {
3612            expr
3613        }
3614    }
3615
3616    /// Transform a lambda with typed parameters for Snowflake
3617    /// For `a int -> a + a + 1`, transforms to `a -> CAST(a AS INT) + CAST(a AS INT) + 1`
3618    fn transform_typed_lambda(&self, lambda: crate::expressions::LambdaExpr) -> Expression {
3619        use crate::expressions::{DataType, LambdaExpr};
3620        use std::collections::HashMap;
3621
3622        // Build mapping of parameter names to their types
3623        let mut param_types: HashMap<String, DataType> = HashMap::new();
3624        for (i, param) in lambda.parameters.iter().enumerate() {
3625            if let Some(Some(dt)) = lambda.parameter_types.get(i) {
3626                param_types.insert(param.name.to_uppercase(), dt.clone());
3627            }
3628        }
3629
3630        // If no typed parameters, return lambda unchanged
3631        if param_types.is_empty() {
3632            return Expression::Lambda(Box::new(lambda));
3633        }
3634
3635        // Transform the body by replacing parameter references with CAST expressions
3636        let transformed_body = self.replace_lambda_params_with_cast(lambda.body, &param_types);
3637
3638        // Return new lambda without type annotations (they're now embedded in CAST)
3639        Expression::Lambda(Box::new(LambdaExpr {
3640            parameters: lambda.parameters,
3641            body: transformed_body,
3642            colon: lambda.colon,
3643            parameter_types: Vec::new(), // Clear type annotations
3644        }))
3645    }
3646
3647    /// Recursively replace column/identifier references to typed lambda parameters with CAST expressions
3648    fn replace_lambda_params_with_cast(
3649        &self,
3650        expr: Expression,
3651        param_types: &std::collections::HashMap<String, crate::expressions::DataType>,
3652    ) -> Expression {
3653        use crate::expressions::{BinaryOp, Cast, Paren};
3654
3655        match expr {
3656            // Column reference - check if it matches a typed parameter
3657            Expression::Column(col) if col.table.is_none() => {
3658                let name_upper = col.name.name.to_uppercase();
3659                if let Some(dt) = param_types.get(&name_upper) {
3660                    // Wrap in CAST
3661                    Expression::Cast(Box::new(Cast {
3662                        this: Expression::Column(col),
3663                        to: dt.clone(),
3664                        double_colon_syntax: false,
3665                        trailing_comments: Vec::new(),
3666                        format: None,
3667                        default: None,
3668                        inferred_type: None,
3669                    }))
3670                } else {
3671                    Expression::Column(col)
3672                }
3673            }
3674
3675            // Identifier reference - check if it matches a typed parameter
3676            Expression::Identifier(id) => {
3677                let name_upper = id.name.to_uppercase();
3678                if let Some(dt) = param_types.get(&name_upper) {
3679                    // Wrap in CAST
3680                    Expression::Cast(Box::new(Cast {
3681                        this: Expression::Identifier(id),
3682                        to: dt.clone(),
3683                        double_colon_syntax: false,
3684                        trailing_comments: Vec::new(),
3685                        format: None,
3686                        default: None,
3687                        inferred_type: None,
3688                    }))
3689                } else {
3690                    Expression::Identifier(id)
3691                }
3692            }
3693
3694            // Binary operations - recursively transform both sides
3695            Expression::Add(op) => Expression::Add(Box::new(BinaryOp::new(
3696                self.replace_lambda_params_with_cast(op.left, param_types),
3697                self.replace_lambda_params_with_cast(op.right, param_types),
3698            ))),
3699            Expression::Sub(op) => Expression::Sub(Box::new(BinaryOp::new(
3700                self.replace_lambda_params_with_cast(op.left, param_types),
3701                self.replace_lambda_params_with_cast(op.right, param_types),
3702            ))),
3703            Expression::Mul(op) => Expression::Mul(Box::new(BinaryOp::new(
3704                self.replace_lambda_params_with_cast(op.left, param_types),
3705                self.replace_lambda_params_with_cast(op.right, param_types),
3706            ))),
3707            Expression::Div(op) => Expression::Div(Box::new(BinaryOp::new(
3708                self.replace_lambda_params_with_cast(op.left, param_types),
3709                self.replace_lambda_params_with_cast(op.right, param_types),
3710            ))),
3711            Expression::Mod(op) => Expression::Mod(Box::new(BinaryOp::new(
3712                self.replace_lambda_params_with_cast(op.left, param_types),
3713                self.replace_lambda_params_with_cast(op.right, param_types),
3714            ))),
3715
3716            // Parenthesized expression
3717            Expression::Paren(p) => Expression::Paren(Box::new(Paren {
3718                this: self.replace_lambda_params_with_cast(p.this, param_types),
3719                trailing_comments: p.trailing_comments,
3720            })),
3721
3722            // Function calls - transform arguments
3723            Expression::Function(mut f) => {
3724                f.args = f
3725                    .args
3726                    .into_iter()
3727                    .map(|arg| self.replace_lambda_params_with_cast(arg, param_types))
3728                    .collect();
3729                Expression::Function(f)
3730            }
3731
3732            // Comparison operators
3733            Expression::Eq(op) => Expression::Eq(Box::new(BinaryOp::new(
3734                self.replace_lambda_params_with_cast(op.left, param_types),
3735                self.replace_lambda_params_with_cast(op.right, param_types),
3736            ))),
3737            Expression::Neq(op) => Expression::Neq(Box::new(BinaryOp::new(
3738                self.replace_lambda_params_with_cast(op.left, param_types),
3739                self.replace_lambda_params_with_cast(op.right, param_types),
3740            ))),
3741            Expression::Lt(op) => Expression::Lt(Box::new(BinaryOp::new(
3742                self.replace_lambda_params_with_cast(op.left, param_types),
3743                self.replace_lambda_params_with_cast(op.right, param_types),
3744            ))),
3745            Expression::Lte(op) => Expression::Lte(Box::new(BinaryOp::new(
3746                self.replace_lambda_params_with_cast(op.left, param_types),
3747                self.replace_lambda_params_with_cast(op.right, param_types),
3748            ))),
3749            Expression::Gt(op) => Expression::Gt(Box::new(BinaryOp::new(
3750                self.replace_lambda_params_with_cast(op.left, param_types),
3751                self.replace_lambda_params_with_cast(op.right, param_types),
3752            ))),
3753            Expression::Gte(op) => Expression::Gte(Box::new(BinaryOp::new(
3754                self.replace_lambda_params_with_cast(op.left, param_types),
3755                self.replace_lambda_params_with_cast(op.right, param_types),
3756            ))),
3757
3758            // And/Or
3759            Expression::And(op) => Expression::And(Box::new(BinaryOp::new(
3760                self.replace_lambda_params_with_cast(op.left, param_types),
3761                self.replace_lambda_params_with_cast(op.right, param_types),
3762            ))),
3763            Expression::Or(op) => Expression::Or(Box::new(BinaryOp::new(
3764                self.replace_lambda_params_with_cast(op.left, param_types),
3765                self.replace_lambda_params_with_cast(op.right, param_types),
3766            ))),
3767
3768            // Other expressions - return unchanged
3769            other => other,
3770        }
3771    }
3772
3773    fn transform_aggregate_function(
3774        &self,
3775        f: Box<crate::expressions::AggregateFunction>,
3776    ) -> Result<Expression> {
3777        let name_upper = f.name.to_uppercase();
3778        match name_upper.as_str() {
3779            // GROUP_CONCAT -> LISTAGG
3780            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3781                Function::new("LISTAGG".to_string(), f.args),
3782            ))),
3783
3784            // STRING_AGG -> LISTAGG
3785            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3786                Function::new("LISTAGG".to_string(), f.args),
3787            ))),
3788
3789            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT
3790            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3791                Function::new("APPROX_COUNT_DISTINCT".to_string(), f.args),
3792            ))),
3793
3794            // BIT_AND -> BITAND_AGG
3795            "BIT_AND" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3796                "BITAND_AGG".to_string(),
3797                f.args,
3798            )))),
3799
3800            // BIT_OR -> BITOR_AGG
3801            "BIT_OR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3802                "BITOR_AGG".to_string(),
3803                f.args,
3804            )))),
3805
3806            // BIT_XOR -> BITXOR_AGG
3807            "BIT_XOR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3808                "BITXOR_AGG".to_string(),
3809                f.args,
3810            )))),
3811
3812            // BOOL_AND/BOOLAND_AGG/LOGICAL_AND -> LogicalAnd AST node
3813            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" if !f.args.is_empty() => {
3814                let arg = f.args.into_iter().next().unwrap();
3815                Ok(Expression::LogicalAnd(Box::new(AggFunc {
3816                    this: arg,
3817                    distinct: f.distinct,
3818                    filter: f.filter,
3819                    order_by: Vec::new(),
3820                    name: Some("BOOLAND_AGG".to_string()),
3821                    ignore_nulls: None,
3822                    having_max: None,
3823                    limit: None,
3824                    inferred_type: None,
3825                })))
3826            }
3827
3828            // BOOL_OR/BOOLOR_AGG/LOGICAL_OR -> LogicalOr AST node
3829            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if !f.args.is_empty() => {
3830                let arg = f.args.into_iter().next().unwrap();
3831                Ok(Expression::LogicalOr(Box::new(AggFunc {
3832                    this: arg,
3833                    distinct: f.distinct,
3834                    filter: f.filter,
3835                    order_by: Vec::new(),
3836                    name: Some("BOOLOR_AGG".to_string()),
3837                    ignore_nulls: None,
3838                    having_max: None,
3839                    limit: None,
3840                    inferred_type: None,
3841                })))
3842            }
3843
3844            // APPROX_TOP_K - add default k=1 if only one argument
3845            "APPROX_TOP_K" if f.args.len() == 1 => {
3846                let mut args = f.args;
3847                args.push(Expression::number(1));
3848                Ok(Expression::AggregateFunction(Box::new(
3849                    crate::expressions::AggregateFunction {
3850                        name: "APPROX_TOP_K".to_string(),
3851                        args,
3852                        distinct: f.distinct,
3853                        filter: f.filter,
3854                        order_by: Vec::new(),
3855                        limit: None,
3856                        ignore_nulls: None,
3857                        inferred_type: None,
3858                    },
3859                )))
3860            }
3861
3862            // SKEW/SKEWNESS -> Skewness AST node
3863            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
3864                let arg = f.args.into_iter().next().unwrap();
3865                Ok(Expression::Skewness(Box::new(AggFunc {
3866                    this: arg,
3867                    distinct: f.distinct,
3868                    filter: f.filter,
3869                    order_by: Vec::new(),
3870                    name: Some("SKEW".to_string()),
3871                    ignore_nulls: None,
3872                    having_max: None,
3873                    limit: None,
3874                    inferred_type: None,
3875                })))
3876            }
3877
3878            // Pass through everything else
3879            _ => Ok(Expression::AggregateFunction(f)),
3880        }
3881    }
3882}
3883
3884/// Convert strftime format specifiers to Snowflake format specifiers
3885#[cfg(feature = "transpile")]
3886fn strftime_to_snowflake_format(fmt: &str) -> String {
3887    let mut result = String::new();
3888    let chars: Vec<char> = fmt.chars().collect();
3889    let mut i = 0;
3890    while i < chars.len() {
3891        if chars[i] == '%' && i + 1 < chars.len() {
3892            match chars[i + 1] {
3893                'Y' => {
3894                    result.push_str("yyyy");
3895                    i += 2;
3896                }
3897                'y' => {
3898                    result.push_str("yy");
3899                    i += 2;
3900                }
3901                'm' => {
3902                    result.push_str("mm");
3903                    i += 2;
3904                }
3905                'd' => {
3906                    result.push_str("DD");
3907                    i += 2;
3908                }
3909                'H' => {
3910                    result.push_str("hh24");
3911                    i += 2;
3912                }
3913                'M' => {
3914                    result.push_str("mmmm");
3915                    i += 2;
3916                } // %M = full month name
3917                'i' => {
3918                    result.push_str("mi");
3919                    i += 2;
3920                }
3921                'S' | 's' => {
3922                    result.push_str("ss");
3923                    i += 2;
3924                }
3925                'f' => {
3926                    result.push_str("ff");
3927                    i += 2;
3928                }
3929                'w' => {
3930                    result.push_str("dy");
3931                    i += 2;
3932                } // day of week number
3933                'a' => {
3934                    result.push_str("DY");
3935                    i += 2;
3936                } // abbreviated day name
3937                'b' => {
3938                    result.push_str("mon");
3939                    i += 2;
3940                } // abbreviated month name
3941                'T' => {
3942                    result.push_str("hh24:mi:ss");
3943                    i += 2;
3944                } // time shorthand
3945                _ => {
3946                    result.push(chars[i]);
3947                    result.push(chars[i + 1]);
3948                    i += 2;
3949                }
3950            }
3951        } else {
3952            result.push(chars[i]);
3953            i += 1;
3954        }
3955    }
3956    result
3957}
3958
3959#[cfg(test)]
3960mod tests {
3961    use super::*;
3962    use crate::dialects::Dialect;
3963
3964    #[test]
3965    fn test_date_time_format_normalization_preserves_unicode() {
3966        for literal in ["é", "年", "🦀", "e\u{301}", "ß", "ſ"] {
3967            assert_eq!(
3968                SnowflakeDialect::normalize_snowflake_format(literal),
3969                literal
3970            );
3971            assert_eq!(
3972                SnowflakeDialect::normalize_snowflake_format(&format!(
3973                    "{literal}YYYY{literal}mM{literal}"
3974                )),
3975                format!("{literal}yyyy{literal}mm{literal}")
3976            );
3977        }
3978        for (input, expected) in [
3979            ("", ""),
3980            (
3981                "YYYY YY MMMM MON MM DD DY HH24 HH12 HH MISS MI SS AM PM TZH TZM",
3982                "yyyy yy mmmm mon mm DD dy hh24 hh12 hh miss mi ss pm pm tzh tzm",
3983            ),
3984            (
3985                "FF FF0 FF1 FF6 FF7 FF9 FF69",
3986                "ff9 ff0 ff1 ff6 ff7 ff9 ff69",
3987            ),
3988            ("éFF9年YYYY", "éff9年yyyy"),
3989            ("\"éYYYY🦀\"YYYY", "éYYYY🦀yyyy"),
3990            ("YYYY\"年", "yyyy年"),
3991            ("\"\"YYYY\"T\"HH24", "yyyyThh24"),
3992            ("q %-q %", "q %-q %"),
3993        ] {
3994            assert_eq!(
3995                SnowflakeDialect::normalize_snowflake_format(input),
3996                expected,
3997                "{input}"
3998            );
3999        }
4000    }
4001
4002    fn transpile_to_snowflake(sql: &str) -> String {
4003        let dialect = Dialect::get(DialectType::Generic);
4004        let result = dialect
4005            .transpile(sql, DialectType::Snowflake)
4006            .expect("Transpile failed");
4007        result[0].clone()
4008    }
4009
4010    #[test]
4011    fn test_ifnull_to_coalesce() {
4012        let result = transpile_to_snowflake("SELECT IFNULL(a, b)");
4013        assert!(
4014            result.contains("COALESCE"),
4015            "Expected COALESCE, got: {}",
4016            result
4017        );
4018    }
4019
4020    #[test]
4021    fn test_basic_select() {
4022        let result = transpile_to_snowflake("SELECT a, b FROM users WHERE id = 1");
4023        assert!(result.contains("SELECT"));
4024        assert!(result.contains("FROM users"));
4025    }
4026
4027    #[test]
4028    fn test_extract_string_comma_syntax_remains_date_part() {
4029        assert_eq!(
4030            transpile_to_snowflake("SELECT EXTRACT('month', a)"),
4031            "SELECT DATE_PART('month', a)"
4032        );
4033    }
4034
4035    #[test]
4036    fn test_snowflake_scripting_cursor_declare_block_roundtrip() {
4037        let sql = "DECLARE
4038  emp CURSOR FOR SELECT salary FROM employees;
4039BEGIN
4040  RETURN 1;
4041END";
4042
4043        let dialect = Dialect::get(DialectType::Snowflake);
4044        let ast = dialect.parse(sql).expect("Parse failed");
4045        let output = dialect.generate(&ast[0]).expect("Generate failed");
4046
4047        assert_eq!(output, sql);
4048    }
4049
4050    #[test]
4051    fn test_snowflake_scripting_cursor_return_table_roundtrip() {
4052        let sql = "DECLARE
4053  c1 CURSOR FOR SELECT * FROM invoices;
4054BEGIN
4055  OPEN c1;
4056  RETURN TABLE(RESULTSET_FROM_CURSOR(c1));
4057END";
4058
4059        let dialect = Dialect::get(DialectType::Snowflake);
4060        let ast = dialect.parse(sql).expect("Parse failed");
4061        let output = dialect.generate(&ast[0]).expect("Generate failed");
4062
4063        assert_eq!(output, sql);
4064    }
4065
4066    #[test]
4067    fn test_group_concat_to_listagg() {
4068        let result = transpile_to_snowflake("SELECT GROUP_CONCAT(name)");
4069        assert!(
4070            result.contains("LISTAGG"),
4071            "Expected LISTAGG, got: {}",
4072            result
4073        );
4074    }
4075
4076    #[test]
4077    fn test_string_agg_to_listagg() {
4078        let result = transpile_to_snowflake("SELECT STRING_AGG(name)");
4079        assert!(
4080            result.contains("LISTAGG"),
4081            "Expected LISTAGG, got: {}",
4082            result
4083        );
4084    }
4085
4086    #[test]
4087    fn test_array_to_array_construct() {
4088        let result = transpile_to_snowflake("SELECT ARRAY(1, 2, 3)");
4089        // ARRAY(1, 2, 3) from Generic -> Snowflake uses [] bracket notation
4090        assert!(
4091            result.contains("[1, 2, 3]"),
4092            "Expected [1, 2, 3], got: {}",
4093            result
4094        );
4095    }
4096
4097    #[test]
4098    fn test_double_quote_identifiers() {
4099        // Snowflake uses double quotes for identifiers
4100        let dialect = SnowflakeDialect;
4101        let config = dialect.generator_config();
4102        assert_eq!(config.identifier_quote, '"');
4103    }
4104}