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