Skip to main content

polyglot_sql/dialects/
snowflake.rs

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