Skip to main content

polyglot_sql/dialects/
snowflake.rs

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