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