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