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 is native to Snowflake
2711            "REGEXP_SUBSTR_ALL" => Ok(Expression::Function(Box::new(f))),
2712
2713            // GET_PATH - transform path argument:
2714            // - Convert colon notation to dot notation (y[0]:z -> y[0].z)
2715            // - Wrap unsafe keys in brackets ($id -> ["$id"])
2716            "GET_PATH" if f.args.len() >= 2 => {
2717                let mut args = f.args;
2718                // Transform the path argument (second argument)
2719                if let Expression::Literal(crate::expressions::Literal::String(path)) = &args[1] {
2720                    let transformed = Self::transform_json_path(path);
2721                    args[1] = Expression::Literal(crate::expressions::Literal::String(transformed));
2722                }
2723                Ok(Expression::Function(Box::new(Function::new(
2724                    "GET_PATH".to_string(),
2725                    args,
2726                ))))
2727            }
2728            "GET_PATH" => Ok(Expression::Function(Box::new(f))),
2729
2730            // FLATTEN is native to Snowflake
2731            "FLATTEN" => Ok(Expression::Function(Box::new(f))),
2732
2733            // DATE_TRUNC - transform unit to quoted string
2734            // DATE_TRUNC(yr, x) -> DATE_TRUNC('YEAR', x)
2735            "DATE_TRUNC" if f.args.len() >= 1 => {
2736                let mut args = f.args;
2737                // Transform the unit to canonical form and convert to string literal
2738                let unit_name = match &args[0] {
2739                    Expression::Identifier(id) => Some(id.name.as_str()),
2740                    Expression::Column(col) if col.table.is_none() => Some(col.name.name.as_str()),
2741                    _ => None,
2742                };
2743                if let Some(name) = unit_name {
2744                    let canonical = Self::map_date_part(name).unwrap_or(name);
2745                    args[0] = Expression::Literal(crate::expressions::Literal::String(
2746                        canonical.to_uppercase(),
2747                    ));
2748                }
2749                Ok(Expression::Function(Box::new(Function::new(
2750                    "DATE_TRUNC".to_string(),
2751                    args,
2752                ))))
2753            }
2754
2755            // DATE_PART - transform unit argument
2756            // DATE_PART(yyy, x) -> DATE_PART(YEAR, x)
2757            // Only convert string literals to identifiers when the second arg is a typed literal
2758            // (e.g., TIMESTAMP '...', DATE '...'), indicating the function came from another dialect.
2759            // For native Snowflake DATE_PART('month', CAST(...)), preserve the string as-is.
2760            "DATE_PART" if f.args.len() >= 1 => {
2761                let mut args = f.args;
2762                let from_typed_literal = args.len() >= 2
2763                    && matches!(
2764                        &args[1],
2765                        Expression::Literal(crate::expressions::Literal::Timestamp(_))
2766                            | Expression::Literal(crate::expressions::Literal::Date(_))
2767                            | Expression::Literal(crate::expressions::Literal::Time(_))
2768                            | Expression::Literal(crate::expressions::Literal::Datetime(_))
2769                    );
2770                if from_typed_literal {
2771                    args[0] = self.transform_date_part_arg(args[0].clone());
2772                } else {
2773                    // For non-typed-literal cases, only normalize identifiers/columns
2774                    // (don't convert string literals to identifiers)
2775                    args[0] = self.transform_date_part_arg_identifiers_only(args[0].clone());
2776                }
2777                Ok(Expression::Function(Box::new(Function::new(
2778                    "DATE_PART".to_string(),
2779                    args,
2780                ))))
2781            }
2782
2783            // OBJECT_CONSTRUCT is native to Snowflake
2784            "OBJECT_CONSTRUCT" => Ok(Expression::Function(Box::new(f))),
2785
2786            // OBJECT_CONSTRUCT_KEEP_NULL is native to Snowflake
2787            "OBJECT_CONSTRUCT_KEEP_NULL" => Ok(Expression::Function(Box::new(f))),
2788
2789            // DESC -> DESCRIBE
2790            "DESC" => Ok(Expression::Function(Box::new(Function::new(
2791                "DESCRIBE".to_string(),
2792                f.args,
2793            )))),
2794
2795            // RLIKE -> REGEXP_LIKE
2796            "RLIKE" if f.args.len() >= 2 => Ok(Expression::Function(Box::new(Function::new(
2797                "REGEXP_LIKE".to_string(),
2798                f.args,
2799            )))),
2800
2801            // TRANSFORM function - handle typed lambda parameters
2802            // For typed lambdas like `a int -> a + 1`, we need to:
2803            // 1. Remove the type annotation from the parameter
2804            // 2. Wrap all references to the parameter in the body with CAST(param AS type)
2805            "TRANSFORM" => {
2806                let transformed_args: Vec<Expression> = f
2807                    .args
2808                    .into_iter()
2809                    .map(|arg| {
2810                        if let Expression::Lambda(lambda) = arg {
2811                            self.transform_typed_lambda(*lambda)
2812                        } else {
2813                            arg
2814                        }
2815                    })
2816                    .collect();
2817                Ok(Expression::Function(Box::new(Function::new(
2818                    "TRANSFORM".to_string(),
2819                    transformed_args,
2820                ))))
2821            }
2822
2823            // SEARCH function - convert to Search expression with canonical parameter ordering
2824            "SEARCH" if f.args.len() >= 2 => {
2825                let mut args = f.args.into_iter();
2826                let this = Box::new(args.next().unwrap());
2827                let expression = Box::new(args.next().unwrap());
2828
2829                let mut analyzer: Option<Box<Expression>> = None;
2830                let mut search_mode: Option<Box<Expression>> = None;
2831
2832                // Parse remaining named arguments
2833                for arg in args {
2834                    if let Expression::NamedArgument(na) = &arg {
2835                        let name_upper = na.name.name.to_uppercase();
2836                        match name_upper.as_str() {
2837                            "ANALYZER" => analyzer = Some(Box::new(arg)),
2838                            "SEARCH_MODE" => search_mode = Some(Box::new(arg)),
2839                            _ => {}
2840                        }
2841                    }
2842                }
2843
2844                Ok(Expression::Search(Box::new(crate::expressions::Search {
2845                    this,
2846                    expression,
2847                    json_scope: None,
2848                    analyzer,
2849                    analyzer_options: None,
2850                    search_mode,
2851                })))
2852            }
2853
2854            // ODBC CONVERT function: CONVERT(value, SQL_TYPE) -> CAST(value AS TYPE)
2855            // This handles the { fn CONVERT(...) } ODBC escape sequence syntax
2856            "CONVERT" if f.args.len() == 2 => {
2857                let value = f.args.get(0).cloned().unwrap();
2858                let type_arg = f.args.get(1).cloned().unwrap();
2859
2860                // Check if second argument is a SQL_ type identifier
2861                if let Expression::Column(col) = &type_arg {
2862                    let type_name = col.name.name.to_uppercase();
2863                    let data_type = match type_name.as_str() {
2864                        "SQL_DOUBLE" => Some(DataType::Double {
2865                            precision: None,
2866                            scale: None,
2867                        }),
2868                        "SQL_VARCHAR" => Some(DataType::VarChar {
2869                            length: None,
2870                            parenthesized_length: false,
2871                        }),
2872                        "SQL_INTEGER" | "SQL_INT" => Some(DataType::Int {
2873                            length: None,
2874                            integer_spelling: false,
2875                        }),
2876                        "SQL_BIGINT" => Some(DataType::BigInt { length: None }),
2877                        "SQL_SMALLINT" => Some(DataType::SmallInt { length: None }),
2878                        "SQL_FLOAT" => Some(DataType::Float {
2879                            precision: None,
2880                            scale: None,
2881                            real_spelling: false,
2882                        }),
2883                        "SQL_REAL" => Some(DataType::Float {
2884                            precision: None,
2885                            scale: None,
2886                            real_spelling: true,
2887                        }),
2888                        "SQL_DECIMAL" => Some(DataType::Decimal {
2889                            precision: None,
2890                            scale: None,
2891                        }),
2892                        "SQL_DATE" => Some(DataType::Date),
2893                        "SQL_TIME" => Some(DataType::Time {
2894                            precision: None,
2895                            timezone: false,
2896                        }),
2897                        "SQL_TIMESTAMP" => Some(DataType::Timestamp {
2898                            precision: None,
2899                            timezone: false,
2900                        }),
2901                        _ => None,
2902                    };
2903
2904                    if let Some(dt) = data_type {
2905                        return Ok(Expression::Cast(Box::new(Cast {
2906                            this: value,
2907                            to: dt,
2908                            double_colon_syntax: false,
2909                            trailing_comments: vec![],
2910                            format: None,
2911                            default: None,
2912                            inferred_type: None,
2913                        })));
2914                    }
2915                }
2916                // If not a SQL_ type, keep as regular CONVERT function
2917                Ok(Expression::Function(Box::new(f)))
2918            }
2919
2920            // TO_TIMESTAMP_TZ: single string arg -> CAST(... AS TIMESTAMPTZ), otherwise keep as function
2921            // Per Python sqlglot: _build_datetime converts TO_TIMESTAMP_TZ('string') to CAST('string' AS TIMESTAMPTZ)
2922            "TO_TIMESTAMP_TZ" => {
2923                if f.args.len() == 1 {
2924                    if let Expression::Literal(crate::expressions::Literal::String(_)) = &f.args[0]
2925                    {
2926                        return Ok(Expression::Cast(Box::new(Cast {
2927                            this: f.args.into_iter().next().unwrap(),
2928                            to: DataType::Custom {
2929                                name: "TIMESTAMPTZ".to_string(),
2930                            },
2931                            double_colon_syntax: false,
2932                            trailing_comments: vec![],
2933                            format: None,
2934                            default: None,
2935                            inferred_type: None,
2936                        })));
2937                    }
2938                }
2939                Ok(Expression::Function(Box::new(f)))
2940            }
2941
2942            // TO_TIMESTAMP_NTZ: single string arg -> CAST(... AS TIMESTAMPNTZ), otherwise keep as function
2943            "TO_TIMESTAMP_NTZ" => {
2944                if f.args.len() == 1 {
2945                    if let Expression::Literal(crate::expressions::Literal::String(_)) = &f.args[0]
2946                    {
2947                        return Ok(Expression::Cast(Box::new(Cast {
2948                            this: f.args.into_iter().next().unwrap(),
2949                            to: DataType::Custom {
2950                                name: "TIMESTAMPNTZ".to_string(),
2951                            },
2952                            double_colon_syntax: false,
2953                            trailing_comments: vec![],
2954                            format: None,
2955                            default: None,
2956                            inferred_type: None,
2957                        })));
2958                    }
2959                }
2960                Ok(Expression::Function(Box::new(f)))
2961            }
2962
2963            // TO_TIMESTAMP_LTZ: single string arg -> CAST(... AS TIMESTAMPLTZ), otherwise keep as function
2964            "TO_TIMESTAMP_LTZ" => {
2965                if f.args.len() == 1 {
2966                    if let Expression::Literal(crate::expressions::Literal::String(_)) = &f.args[0]
2967                    {
2968                        return Ok(Expression::Cast(Box::new(Cast {
2969                            this: f.args.into_iter().next().unwrap(),
2970                            to: DataType::Custom {
2971                                name: "TIMESTAMPLTZ".to_string(),
2972                            },
2973                            double_colon_syntax: false,
2974                            trailing_comments: vec![],
2975                            format: None,
2976                            default: None,
2977                            inferred_type: None,
2978                        })));
2979                    }
2980                }
2981                Ok(Expression::Function(Box::new(f)))
2982            }
2983
2984            // UNIFORM -> keep as-is (Snowflake-specific)
2985            "UNIFORM" => Ok(Expression::Function(Box::new(f))),
2986
2987            // REPLACE with 2 args -> add empty string 3rd arg
2988            "REPLACE" if f.args.len() == 2 => {
2989                let mut args = f.args;
2990                args.push(Expression::Literal(crate::expressions::Literal::String(
2991                    String::new(),
2992                )));
2993                Ok(Expression::Function(Box::new(Function::new(
2994                    "REPLACE".to_string(),
2995                    args,
2996                ))))
2997            }
2998
2999            // ARBITRARY -> ANY_VALUE in Snowflake
3000            "ARBITRARY" => Ok(Expression::Function(Box::new(Function::new(
3001                "ANY_VALUE".to_string(),
3002                f.args,
3003            )))),
3004
3005            // SAFE_DIVIDE(x, y) -> IFF(y <> 0, x / y, NULL)
3006            "SAFE_DIVIDE" if f.args.len() == 2 => {
3007                let mut args = f.args;
3008                let x = args.remove(0);
3009                let y = args.remove(0);
3010                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3011                    condition: Expression::Neq(Box::new(BinaryOp {
3012                        left: y.clone(),
3013                        right: Expression::number(0),
3014                        left_comments: Vec::new(),
3015                        operator_comments: Vec::new(),
3016                        trailing_comments: Vec::new(),
3017                        inferred_type: None,
3018                    })),
3019                    true_value: Expression::Div(Box::new(BinaryOp {
3020                        left: x,
3021                        right: y,
3022                        left_comments: Vec::new(),
3023                        operator_comments: Vec::new(),
3024                        trailing_comments: Vec::new(),
3025                        inferred_type: None,
3026                    })),
3027                    false_value: Some(Expression::Null(crate::expressions::Null)),
3028                    original_name: Some("IFF".to_string()),
3029                    inferred_type: None,
3030                })))
3031            }
3032
3033            // TIMESTAMP(x) -> CAST(x AS TIMESTAMPTZ) in Snowflake
3034            "TIMESTAMP" if f.args.len() == 1 => {
3035                let arg = f.args.into_iter().next().unwrap();
3036                Ok(Expression::Cast(Box::new(Cast {
3037                    this: arg,
3038                    to: DataType::Custom {
3039                        name: "TIMESTAMPTZ".to_string(),
3040                    },
3041                    trailing_comments: Vec::new(),
3042                    double_colon_syntax: false,
3043                    format: None,
3044                    default: None,
3045                    inferred_type: None,
3046                })))
3047            }
3048
3049            // TIMESTAMP(x, tz) -> CONVERT_TIMEZONE(tz, CAST(x AS TIMESTAMP)) in Snowflake
3050            "TIMESTAMP" if f.args.len() == 2 => {
3051                let mut args = f.args;
3052                let value = args.remove(0);
3053                let tz = args.remove(0);
3054                Ok(Expression::Function(Box::new(Function::new(
3055                    "CONVERT_TIMEZONE".to_string(),
3056                    vec![
3057                        tz,
3058                        Expression::Cast(Box::new(Cast {
3059                            this: value,
3060                            to: DataType::Timestamp {
3061                                precision: None,
3062                                timezone: false,
3063                            },
3064                            trailing_comments: Vec::new(),
3065                            double_colon_syntax: false,
3066                            format: None,
3067                            default: None,
3068                            inferred_type: None,
3069                        })),
3070                    ],
3071                ))))
3072            }
3073
3074            // TIME(h, m, s) -> TIME_FROM_PARTS(h, m, s) in Snowflake
3075            "TIME" if f.args.len() == 3 => Ok(Expression::Function(Box::new(Function::new(
3076                "TIME_FROM_PARTS".to_string(),
3077                f.args,
3078            )))),
3079
3080            // DIV0(x, y) -> IFF(y = 0 AND NOT x IS NULL, 0, x / y)
3081            "DIV0" if f.args.len() == 2 => {
3082                let mut args = f.args;
3083                let x = args.remove(0);
3084                let y = args.remove(0);
3085                // Need parens around complex expressions
3086                let x_expr = Self::maybe_paren(x.clone());
3087                let y_expr = Self::maybe_paren(y.clone());
3088                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3089                    condition: Expression::And(Box::new(BinaryOp::new(
3090                        Expression::Eq(Box::new(BinaryOp::new(
3091                            y_expr.clone(),
3092                            Expression::number(0),
3093                        ))),
3094                        Expression::Not(Box::new(crate::expressions::UnaryOp {
3095                            this: Expression::IsNull(Box::new(crate::expressions::IsNull {
3096                                this: x_expr.clone(),
3097                                not: false,
3098                                postfix_form: false,
3099                            })),
3100                            inferred_type: None,
3101                        })),
3102                    ))),
3103                    true_value: Expression::number(0),
3104                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3105                    original_name: Some("IFF".to_string()),
3106                    inferred_type: None,
3107                })))
3108            }
3109
3110            // DIV0NULL(x, y) -> IFF(y = 0 OR y IS NULL, 0, x / y)
3111            "DIV0NULL" if f.args.len() == 2 => {
3112                let mut args = f.args;
3113                let x = args.remove(0);
3114                let y = args.remove(0);
3115                let x_expr = Self::maybe_paren(x.clone());
3116                let y_expr = Self::maybe_paren(y.clone());
3117                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3118                    condition: Expression::Or(Box::new(BinaryOp::new(
3119                        Expression::Eq(Box::new(BinaryOp::new(
3120                            y_expr.clone(),
3121                            Expression::number(0),
3122                        ))),
3123                        Expression::IsNull(Box::new(crate::expressions::IsNull {
3124                            this: y_expr.clone(),
3125                            not: false,
3126                            postfix_form: false,
3127                        })),
3128                    ))),
3129                    true_value: Expression::number(0),
3130                    false_value: Some(Expression::Div(Box::new(BinaryOp::new(x_expr, y_expr)))),
3131                    original_name: Some("IFF".to_string()),
3132                    inferred_type: None,
3133                })))
3134            }
3135
3136            // ZEROIFNULL(x) -> IFF(x IS NULL, 0, x)
3137            "ZEROIFNULL" if f.args.len() == 1 => {
3138                let x = f.args.into_iter().next().unwrap();
3139                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3140                    condition: Expression::IsNull(Box::new(crate::expressions::IsNull {
3141                        this: x.clone(),
3142                        not: false,
3143                        postfix_form: false,
3144                    })),
3145                    true_value: Expression::number(0),
3146                    false_value: Some(x),
3147                    original_name: Some("IFF".to_string()),
3148                    inferred_type: None,
3149                })))
3150            }
3151
3152            // NULLIFZERO(x) -> IFF(x = 0, NULL, x)
3153            "NULLIFZERO" if f.args.len() == 1 => {
3154                let x = f.args.into_iter().next().unwrap();
3155                Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
3156                    condition: Expression::Eq(Box::new(BinaryOp::new(
3157                        x.clone(),
3158                        Expression::number(0),
3159                    ))),
3160                    true_value: Expression::Null(crate::expressions::Null),
3161                    false_value: Some(x),
3162                    original_name: Some("IFF".to_string()),
3163                    inferred_type: None,
3164                })))
3165            }
3166
3167            // TRY_TO_TIME('string') -> TRY_CAST('string' AS TIME) when single string arg
3168            "TRY_TO_TIME" => {
3169                if f.args.len() == 1 {
3170                    if let Expression::Literal(crate::expressions::Literal::String(_)) = &f.args[0]
3171                    {
3172                        return Ok(Expression::TryCast(Box::new(Cast {
3173                            this: f.args.into_iter().next().unwrap(),
3174                            to: crate::expressions::DataType::Time {
3175                                precision: None,
3176                                timezone: false,
3177                            },
3178                            double_colon_syntax: false,
3179                            trailing_comments: Vec::new(),
3180                            format: None,
3181                            default: None,
3182                            inferred_type: None,
3183                        })));
3184                    }
3185                }
3186                // Normalize format string (2nd arg) if present
3187                let mut args = f.args;
3188                if args.len() >= 2 {
3189                    args[1] = Self::normalize_format_arg(args[1].clone());
3190                }
3191                Ok(Expression::Function(Box::new(Function::new(
3192                    "TRY_TO_TIME".to_string(),
3193                    args,
3194                ))))
3195            }
3196
3197            // TRY_TO_TIMESTAMP('string') -> TRY_CAST('string' AS TIMESTAMP) when single string arg
3198            // Convert if the string is NOT a pure numeric/epoch value
3199            "TRY_TO_TIMESTAMP" => {
3200                if f.args.len() == 1 {
3201                    if let Expression::Literal(crate::expressions::Literal::String(s)) = &f.args[0]
3202                    {
3203                        if !Self::looks_like_epoch(s) {
3204                            return Ok(Expression::TryCast(Box::new(Cast {
3205                                this: f.args.into_iter().next().unwrap(),
3206                                to: DataType::Timestamp {
3207                                    precision: None,
3208                                    timezone: false,
3209                                },
3210                                double_colon_syntax: false,
3211                                trailing_comments: Vec::new(),
3212                                format: None,
3213                                default: None,
3214                                inferred_type: None,
3215                            })));
3216                        }
3217                    }
3218                }
3219                // Normalize format string (2nd arg) if present
3220                let mut args = f.args;
3221                if args.len() >= 2 {
3222                    args[1] = Self::normalize_format_arg(args[1].clone());
3223                }
3224                Ok(Expression::Function(Box::new(Function::new(
3225                    "TRY_TO_TIMESTAMP".to_string(),
3226                    args,
3227                ))))
3228            }
3229
3230            // TRY_TO_DATE('string') -> TRY_CAST('string' AS DATE) when single string arg
3231            "TRY_TO_DATE" => {
3232                if f.args.len() == 1 {
3233                    if let Expression::Literal(crate::expressions::Literal::String(s)) = &f.args[0]
3234                    {
3235                        // Only convert if the string looks like a date
3236                        if s.contains('-') && s.len() >= 8 && s.len() <= 12 {
3237                            return Ok(Expression::TryCast(Box::new(Cast {
3238                                this: f.args.into_iter().next().unwrap(),
3239                                to: crate::expressions::DataType::Date,
3240                                double_colon_syntax: false,
3241                                trailing_comments: Vec::new(),
3242                                format: None,
3243                                default: None,
3244                                inferred_type: None,
3245                            })));
3246                        }
3247                    }
3248                }
3249                // Normalize format string (2nd arg) if present
3250                let mut args = f.args;
3251                if args.len() >= 2 {
3252                    args[1] = Self::normalize_format_arg(args[1].clone());
3253                }
3254                Ok(Expression::Function(Box::new(Function::new(
3255                    "TRY_TO_DATE".to_string(),
3256                    args,
3257                ))))
3258            }
3259
3260            // TRY_TO_DOUBLE -> keep as TRY_TO_DOUBLE in Snowflake (native function)
3261            "TRY_TO_DOUBLE" => Ok(Expression::Function(Box::new(f))),
3262
3263            // REGEXP_REPLACE with 2 args -> add empty string replacement
3264            "REGEXP_REPLACE" if f.args.len() == 2 => {
3265                let mut args = f.args;
3266                args.push(Expression::Literal(crate::expressions::Literal::String(
3267                    String::new(),
3268                )));
3269                Ok(Expression::Function(Box::new(Function::new(
3270                    "REGEXP_REPLACE".to_string(),
3271                    args,
3272                ))))
3273            }
3274
3275            // LAST_DAY(x, MONTH) -> LAST_DAY(x) in Snowflake (strip MONTH default)
3276            "LAST_DAY" if f.args.len() == 2 => {
3277                let mut args = f.args;
3278                let date = args.remove(0);
3279                let unit = args.remove(0);
3280                let unit_str = match &unit {
3281                    Expression::Column(c) => c.name.name.to_uppercase(),
3282                    Expression::Identifier(i) => i.name.to_uppercase(),
3283                    _ => String::new(),
3284                };
3285                if unit_str == "MONTH" {
3286                    Ok(Expression::Function(Box::new(Function::new(
3287                        "LAST_DAY".to_string(),
3288                        vec![date],
3289                    ))))
3290                } else {
3291                    Ok(Expression::Function(Box::new(Function::new(
3292                        "LAST_DAY".to_string(),
3293                        vec![date, unit],
3294                    ))))
3295                }
3296            }
3297
3298            // EXTRACT('field', expr) function-call syntax -> DATE_PART('field', expr)
3299            "EXTRACT" if f.args.len() == 2 => Ok(Expression::Function(Box::new(Function::new(
3300                "DATE_PART".to_string(),
3301                f.args,
3302            )))),
3303
3304            // ENDS_WITH/ENDSWITH -> EndsWith AST node
3305            "ENDS_WITH" | "ENDSWITH" if f.args.len() == 2 => {
3306                let mut args = f.args;
3307                let this = args.remove(0);
3308                let expr = args.remove(0);
3309                Ok(Expression::EndsWith(Box::new(
3310                    crate::expressions::BinaryFunc {
3311                        original_name: None,
3312                        this,
3313                        expression: expr,
3314                        inferred_type: None,
3315                    },
3316                )))
3317            }
3318
3319            // Pass through everything else
3320            _ => Ok(Expression::Function(Box::new(f))),
3321        }
3322    }
3323
3324    /// Check if a string looks like a datetime (contains date separators, not just digits)
3325    fn looks_like_datetime(s: &str) -> bool {
3326        // A datetime string typically contains dashes, colons, or spaces
3327        // A numeric/epoch string is just digits (possibly with a dot)
3328        s.contains('-') || s.contains(':') || s.contains(' ') || s.contains('/')
3329    }
3330
3331    /// Check if a string looks like an epoch number (only digits, possibly with a dot)
3332    fn looks_like_epoch(s: &str) -> bool {
3333        !s.is_empty() && s.chars().all(|c| c.is_ascii_digit() || c == '.')
3334    }
3335
3336    /// Wrap an expression in parentheses if it's a complex expression (binary op, etc.)
3337    fn maybe_paren(expr: Expression) -> Expression {
3338        match &expr {
3339            Expression::Sub(_) | Expression::Add(_) | Expression::Mul(_) | Expression::Div(_) => {
3340                Expression::Paren(Box::new(crate::expressions::Paren {
3341                    this: expr,
3342                    trailing_comments: Vec::new(),
3343                }))
3344            }
3345            _ => expr,
3346        }
3347    }
3348
3349    /// Normalize Snowflake date/time format strings to canonical lowercase form.
3350    /// YYYY -> yyyy, MM -> mm, DD -> DD (stays), HH24 -> hh24, HH12 -> hh12,
3351    /// MI -> mi, SS -> ss, FF -> ff, AM/PM -> pm, quoted "T" -> T
3352    fn normalize_snowflake_format(format: &str) -> String {
3353        let mut result = String::new();
3354        let chars: Vec<char> = format.chars().collect();
3355        let mut i = 0;
3356        while i < chars.len() {
3357            // Handle quoted strings like "T" -> T
3358            if chars[i] == '"' {
3359                i += 1;
3360                while i < chars.len() && chars[i] != '"' {
3361                    result.push(chars[i]);
3362                    i += 1;
3363                }
3364                if i < chars.len() {
3365                    i += 1; // skip closing quote
3366                }
3367                continue;
3368            }
3369
3370            let remaining = &format[i..];
3371            let remaining_upper = remaining.to_uppercase();
3372
3373            // Multi-char patterns (check longest first)
3374            if remaining_upper.starts_with("YYYY") {
3375                result.push_str("yyyy");
3376                i += 4;
3377            } else if remaining_upper.starts_with("YY") {
3378                result.push_str("yy");
3379                i += 2;
3380            } else if remaining_upper.starts_with("MMMM") {
3381                result.push_str("mmmm");
3382                i += 4;
3383            } else if remaining_upper.starts_with("MON") {
3384                result.push_str("mon");
3385                i += 3;
3386            } else if remaining_upper.starts_with("MM") {
3387                result.push_str("mm");
3388                i += 2;
3389            } else if remaining_upper.starts_with("DD") {
3390                result.push_str("DD");
3391                i += 2;
3392            } else if remaining_upper.starts_with("DY") {
3393                result.push_str("dy");
3394                i += 2;
3395            } else if remaining_upper.starts_with("HH24") {
3396                result.push_str("hh24");
3397                i += 4;
3398            } else if remaining_upper.starts_with("HH12") {
3399                result.push_str("hh12");
3400                i += 4;
3401            } else if remaining_upper.starts_with("HH") {
3402                result.push_str("hh");
3403                i += 2;
3404            } else if remaining_upper.starts_with("MISS") {
3405                // MISS = MI + SS
3406                result.push_str("miss");
3407                i += 4;
3408            } else if remaining_upper.starts_with("MI") {
3409                result.push_str("mi");
3410                i += 2;
3411            } else if remaining_upper.starts_with("SS") {
3412                result.push_str("ss");
3413                i += 2;
3414            } else if remaining_upper.starts_with("FF") {
3415                // FF followed by a digit (FF1-FF9) keeps the digit
3416                let ff_len = 2;
3417                let digit = if i + ff_len < chars.len() && chars[i + ff_len].is_ascii_digit() {
3418                    let d = chars[i + ff_len];
3419                    Some(d)
3420                } else {
3421                    None
3422                };
3423                if let Some(d) = digit {
3424                    result.push_str("ff");
3425                    result.push(d);
3426                    i += 3;
3427                } else {
3428                    // Plain FF -> ff9
3429                    result.push_str("ff9");
3430                    i += 2;
3431                }
3432            } else if remaining_upper.starts_with("AM") || remaining_upper.starts_with("PM") {
3433                result.push_str("pm");
3434                i += 2;
3435            } else if remaining_upper.starts_with("TZH") {
3436                result.push_str("tzh");
3437                i += 3;
3438            } else if remaining_upper.starts_with("TZM") {
3439                result.push_str("tzm");
3440                i += 3;
3441            } else {
3442                // Keep separators and other characters as-is
3443                result.push(chars[i]);
3444                i += 1;
3445            }
3446        }
3447        result
3448    }
3449
3450    /// Normalize format string argument if it's a string literal
3451    fn normalize_format_arg(expr: Expression) -> Expression {
3452        if let Expression::Literal(crate::expressions::Literal::String(s)) = &expr {
3453            let normalized = Self::normalize_snowflake_format(s);
3454            Expression::Literal(crate::expressions::Literal::String(normalized))
3455        } else {
3456            expr
3457        }
3458    }
3459
3460    /// Transform a lambda with typed parameters for Snowflake
3461    /// For `a int -> a + a + 1`, transforms to `a -> CAST(a AS INT) + CAST(a AS INT) + 1`
3462    fn transform_typed_lambda(&self, lambda: crate::expressions::LambdaExpr) -> Expression {
3463        use crate::expressions::{DataType, LambdaExpr};
3464        use std::collections::HashMap;
3465
3466        // Build mapping of parameter names to their types
3467        let mut param_types: HashMap<String, DataType> = HashMap::new();
3468        for (i, param) in lambda.parameters.iter().enumerate() {
3469            if let Some(Some(dt)) = lambda.parameter_types.get(i) {
3470                param_types.insert(param.name.to_uppercase(), dt.clone());
3471            }
3472        }
3473
3474        // If no typed parameters, return lambda unchanged
3475        if param_types.is_empty() {
3476            return Expression::Lambda(Box::new(lambda));
3477        }
3478
3479        // Transform the body by replacing parameter references with CAST expressions
3480        let transformed_body = self.replace_lambda_params_with_cast(lambda.body, &param_types);
3481
3482        // Return new lambda without type annotations (they're now embedded in CAST)
3483        Expression::Lambda(Box::new(LambdaExpr {
3484            parameters: lambda.parameters,
3485            body: transformed_body,
3486            colon: lambda.colon,
3487            parameter_types: Vec::new(), // Clear type annotations
3488        }))
3489    }
3490
3491    /// Recursively replace column/identifier references to typed lambda parameters with CAST expressions
3492    fn replace_lambda_params_with_cast(
3493        &self,
3494        expr: Expression,
3495        param_types: &std::collections::HashMap<String, crate::expressions::DataType>,
3496    ) -> Expression {
3497        use crate::expressions::{BinaryOp, Cast, Paren};
3498
3499        match expr {
3500            // Column reference - check if it matches a typed parameter
3501            Expression::Column(col) if col.table.is_none() => {
3502                let name_upper = col.name.name.to_uppercase();
3503                if let Some(dt) = param_types.get(&name_upper) {
3504                    // Wrap in CAST
3505                    Expression::Cast(Box::new(Cast {
3506                        this: Expression::Column(col),
3507                        to: dt.clone(),
3508                        double_colon_syntax: false,
3509                        trailing_comments: Vec::new(),
3510                        format: None,
3511                        default: None,
3512                        inferred_type: None,
3513                    }))
3514                } else {
3515                    Expression::Column(col)
3516                }
3517            }
3518
3519            // Identifier reference - check if it matches a typed parameter
3520            Expression::Identifier(id) => {
3521                let name_upper = id.name.to_uppercase();
3522                if let Some(dt) = param_types.get(&name_upper) {
3523                    // Wrap in CAST
3524                    Expression::Cast(Box::new(Cast {
3525                        this: Expression::Identifier(id),
3526                        to: dt.clone(),
3527                        double_colon_syntax: false,
3528                        trailing_comments: Vec::new(),
3529                        format: None,
3530                        default: None,
3531                        inferred_type: None,
3532                    }))
3533                } else {
3534                    Expression::Identifier(id)
3535                }
3536            }
3537
3538            // Binary operations - recursively transform both sides
3539            Expression::Add(op) => Expression::Add(Box::new(BinaryOp::new(
3540                self.replace_lambda_params_with_cast(op.left, param_types),
3541                self.replace_lambda_params_with_cast(op.right, param_types),
3542            ))),
3543            Expression::Sub(op) => Expression::Sub(Box::new(BinaryOp::new(
3544                self.replace_lambda_params_with_cast(op.left, param_types),
3545                self.replace_lambda_params_with_cast(op.right, param_types),
3546            ))),
3547            Expression::Mul(op) => Expression::Mul(Box::new(BinaryOp::new(
3548                self.replace_lambda_params_with_cast(op.left, param_types),
3549                self.replace_lambda_params_with_cast(op.right, param_types),
3550            ))),
3551            Expression::Div(op) => Expression::Div(Box::new(BinaryOp::new(
3552                self.replace_lambda_params_with_cast(op.left, param_types),
3553                self.replace_lambda_params_with_cast(op.right, param_types),
3554            ))),
3555            Expression::Mod(op) => Expression::Mod(Box::new(BinaryOp::new(
3556                self.replace_lambda_params_with_cast(op.left, param_types),
3557                self.replace_lambda_params_with_cast(op.right, param_types),
3558            ))),
3559
3560            // Parenthesized expression
3561            Expression::Paren(p) => Expression::Paren(Box::new(Paren {
3562                this: self.replace_lambda_params_with_cast(p.this, param_types),
3563                trailing_comments: p.trailing_comments,
3564            })),
3565
3566            // Function calls - transform arguments
3567            Expression::Function(mut f) => {
3568                f.args = f
3569                    .args
3570                    .into_iter()
3571                    .map(|arg| self.replace_lambda_params_with_cast(arg, param_types))
3572                    .collect();
3573                Expression::Function(f)
3574            }
3575
3576            // Comparison operators
3577            Expression::Eq(op) => Expression::Eq(Box::new(BinaryOp::new(
3578                self.replace_lambda_params_with_cast(op.left, param_types),
3579                self.replace_lambda_params_with_cast(op.right, param_types),
3580            ))),
3581            Expression::Neq(op) => Expression::Neq(Box::new(BinaryOp::new(
3582                self.replace_lambda_params_with_cast(op.left, param_types),
3583                self.replace_lambda_params_with_cast(op.right, param_types),
3584            ))),
3585            Expression::Lt(op) => Expression::Lt(Box::new(BinaryOp::new(
3586                self.replace_lambda_params_with_cast(op.left, param_types),
3587                self.replace_lambda_params_with_cast(op.right, param_types),
3588            ))),
3589            Expression::Lte(op) => Expression::Lte(Box::new(BinaryOp::new(
3590                self.replace_lambda_params_with_cast(op.left, param_types),
3591                self.replace_lambda_params_with_cast(op.right, param_types),
3592            ))),
3593            Expression::Gt(op) => Expression::Gt(Box::new(BinaryOp::new(
3594                self.replace_lambda_params_with_cast(op.left, param_types),
3595                self.replace_lambda_params_with_cast(op.right, param_types),
3596            ))),
3597            Expression::Gte(op) => Expression::Gte(Box::new(BinaryOp::new(
3598                self.replace_lambda_params_with_cast(op.left, param_types),
3599                self.replace_lambda_params_with_cast(op.right, param_types),
3600            ))),
3601
3602            // And/Or
3603            Expression::And(op) => Expression::And(Box::new(BinaryOp::new(
3604                self.replace_lambda_params_with_cast(op.left, param_types),
3605                self.replace_lambda_params_with_cast(op.right, param_types),
3606            ))),
3607            Expression::Or(op) => Expression::Or(Box::new(BinaryOp::new(
3608                self.replace_lambda_params_with_cast(op.left, param_types),
3609                self.replace_lambda_params_with_cast(op.right, param_types),
3610            ))),
3611
3612            // Other expressions - return unchanged
3613            other => other,
3614        }
3615    }
3616
3617    fn transform_aggregate_function(
3618        &self,
3619        f: Box<crate::expressions::AggregateFunction>,
3620    ) -> Result<Expression> {
3621        let name_upper = f.name.to_uppercase();
3622        match name_upper.as_str() {
3623            // GROUP_CONCAT -> LISTAGG
3624            "GROUP_CONCAT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3625                Function::new("LISTAGG".to_string(), f.args),
3626            ))),
3627
3628            // STRING_AGG -> LISTAGG
3629            "STRING_AGG" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3630                Function::new("LISTAGG".to_string(), f.args),
3631            ))),
3632
3633            // APPROX_DISTINCT -> APPROX_COUNT_DISTINCT
3634            "APPROX_DISTINCT" if !f.args.is_empty() => Ok(Expression::Function(Box::new(
3635                Function::new("APPROX_COUNT_DISTINCT".to_string(), f.args),
3636            ))),
3637
3638            // BIT_AND -> BITAND_AGG
3639            "BIT_AND" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3640                "BITAND_AGG".to_string(),
3641                f.args,
3642            )))),
3643
3644            // BIT_OR -> BITOR_AGG
3645            "BIT_OR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3646                "BITOR_AGG".to_string(),
3647                f.args,
3648            )))),
3649
3650            // BIT_XOR -> BITXOR_AGG
3651            "BIT_XOR" if !f.args.is_empty() => Ok(Expression::Function(Box::new(Function::new(
3652                "BITXOR_AGG".to_string(),
3653                f.args,
3654            )))),
3655
3656            // BOOL_AND/BOOLAND_AGG/LOGICAL_AND -> LogicalAnd AST node
3657            "BOOL_AND" | "LOGICAL_AND" | "BOOLAND_AGG" if !f.args.is_empty() => {
3658                let arg = f.args.into_iter().next().unwrap();
3659                Ok(Expression::LogicalAnd(Box::new(AggFunc {
3660                    this: arg,
3661                    distinct: f.distinct,
3662                    filter: f.filter,
3663                    order_by: Vec::new(),
3664                    name: Some("BOOLAND_AGG".to_string()),
3665                    ignore_nulls: None,
3666                    having_max: None,
3667                    limit: None,
3668                    inferred_type: None,
3669                })))
3670            }
3671
3672            // BOOL_OR/BOOLOR_AGG/LOGICAL_OR -> LogicalOr AST node
3673            "BOOL_OR" | "LOGICAL_OR" | "BOOLOR_AGG" if !f.args.is_empty() => {
3674                let arg = f.args.into_iter().next().unwrap();
3675                Ok(Expression::LogicalOr(Box::new(AggFunc {
3676                    this: arg,
3677                    distinct: f.distinct,
3678                    filter: f.filter,
3679                    order_by: Vec::new(),
3680                    name: Some("BOOLOR_AGG".to_string()),
3681                    ignore_nulls: None,
3682                    having_max: None,
3683                    limit: None,
3684                    inferred_type: None,
3685                })))
3686            }
3687
3688            // APPROX_TOP_K - add default k=1 if only one argument
3689            "APPROX_TOP_K" if f.args.len() == 1 => {
3690                let mut args = f.args;
3691                args.push(Expression::number(1));
3692                Ok(Expression::AggregateFunction(Box::new(
3693                    crate::expressions::AggregateFunction {
3694                        name: "APPROX_TOP_K".to_string(),
3695                        args,
3696                        distinct: f.distinct,
3697                        filter: f.filter,
3698                        order_by: Vec::new(),
3699                        limit: None,
3700                        ignore_nulls: None,
3701                        inferred_type: None,
3702                    },
3703                )))
3704            }
3705
3706            // SKEW/SKEWNESS -> Skewness AST node
3707            "SKEW" | "SKEWNESS" if !f.args.is_empty() => {
3708                let arg = f.args.into_iter().next().unwrap();
3709                Ok(Expression::Skewness(Box::new(AggFunc {
3710                    this: arg,
3711                    distinct: f.distinct,
3712                    filter: f.filter,
3713                    order_by: Vec::new(),
3714                    name: Some("SKEW".to_string()),
3715                    ignore_nulls: None,
3716                    having_max: None,
3717                    limit: None,
3718                    inferred_type: None,
3719                })))
3720            }
3721
3722            // Pass through everything else
3723            _ => Ok(Expression::AggregateFunction(f)),
3724        }
3725    }
3726}
3727
3728/// Convert strftime format specifiers to Snowflake format specifiers
3729fn strftime_to_snowflake_format(fmt: &str) -> String {
3730    let mut result = String::new();
3731    let chars: Vec<char> = fmt.chars().collect();
3732    let mut i = 0;
3733    while i < chars.len() {
3734        if chars[i] == '%' && i + 1 < chars.len() {
3735            match chars[i + 1] {
3736                'Y' => {
3737                    result.push_str("yyyy");
3738                    i += 2;
3739                }
3740                'y' => {
3741                    result.push_str("yy");
3742                    i += 2;
3743                }
3744                'm' => {
3745                    result.push_str("mm");
3746                    i += 2;
3747                }
3748                'd' => {
3749                    result.push_str("DD");
3750                    i += 2;
3751                }
3752                'H' => {
3753                    result.push_str("hh24");
3754                    i += 2;
3755                }
3756                'M' => {
3757                    result.push_str("mmmm");
3758                    i += 2;
3759                } // %M = full month name
3760                'i' => {
3761                    result.push_str("mi");
3762                    i += 2;
3763                }
3764                'S' | 's' => {
3765                    result.push_str("ss");
3766                    i += 2;
3767                }
3768                'f' => {
3769                    result.push_str("ff");
3770                    i += 2;
3771                }
3772                'w' => {
3773                    result.push_str("dy");
3774                    i += 2;
3775                } // day of week number
3776                'a' => {
3777                    result.push_str("DY");
3778                    i += 2;
3779                } // abbreviated day name
3780                'b' => {
3781                    result.push_str("mon");
3782                    i += 2;
3783                } // abbreviated month name
3784                'T' => {
3785                    result.push_str("hh24:mi:ss");
3786                    i += 2;
3787                } // time shorthand
3788                _ => {
3789                    result.push(chars[i]);
3790                    result.push(chars[i + 1]);
3791                    i += 2;
3792                }
3793            }
3794        } else {
3795            result.push(chars[i]);
3796            i += 1;
3797        }
3798    }
3799    result
3800}
3801
3802#[cfg(test)]
3803mod tests {
3804    use super::*;
3805    use crate::dialects::Dialect;
3806
3807    fn transpile_to_snowflake(sql: &str) -> String {
3808        let dialect = Dialect::get(DialectType::Generic);
3809        let result = dialect
3810            .transpile_to(sql, DialectType::Snowflake)
3811            .expect("Transpile failed");
3812        result[0].clone()
3813    }
3814
3815    #[test]
3816    fn test_ifnull_to_coalesce() {
3817        let result = transpile_to_snowflake("SELECT IFNULL(a, b)");
3818        assert!(
3819            result.contains("COALESCE"),
3820            "Expected COALESCE, got: {}",
3821            result
3822        );
3823    }
3824
3825    #[test]
3826    fn test_basic_select() {
3827        let result = transpile_to_snowflake("SELECT a, b FROM users WHERE id = 1");
3828        assert!(result.contains("SELECT"));
3829        assert!(result.contains("FROM users"));
3830    }
3831
3832    #[test]
3833    fn test_group_concat_to_listagg() {
3834        let result = transpile_to_snowflake("SELECT GROUP_CONCAT(name)");
3835        assert!(
3836            result.contains("LISTAGG"),
3837            "Expected LISTAGG, got: {}",
3838            result
3839        );
3840    }
3841
3842    #[test]
3843    fn test_string_agg_to_listagg() {
3844        let result = transpile_to_snowflake("SELECT STRING_AGG(name)");
3845        assert!(
3846            result.contains("LISTAGG"),
3847            "Expected LISTAGG, got: {}",
3848            result
3849        );
3850    }
3851
3852    #[test]
3853    fn test_array_to_array_construct() {
3854        let result = transpile_to_snowflake("SELECT ARRAY(1, 2, 3)");
3855        // ARRAY(1, 2, 3) from Generic -> Snowflake uses [] bracket notation
3856        assert!(
3857            result.contains("[1, 2, 3]"),
3858            "Expected [1, 2, 3], got: {}",
3859            result
3860        );
3861    }
3862
3863    #[test]
3864    fn test_double_quote_identifiers() {
3865        // Snowflake uses double quotes for identifiers
3866        let dialect = SnowflakeDialect;
3867        let config = dialect.generator_config();
3868        assert_eq!(config.identifier_quote, '"');
3869    }
3870}