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