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