Skip to main content

polyglot_sql/dialects/
mod.rs

1//! SQL Dialect System
2//!
3//! This module implements the dialect abstraction layer that enables SQL transpilation
4//! between 30+ database engines. Each dialect encapsulates three concerns:
5//!
6//! - **Tokenization**: Dialect-specific lexing rules (e.g., BigQuery uses backtick quoting,
7//!   MySQL uses backtick for identifiers, TSQL uses square brackets).
8//! - **Generation**: How AST nodes are rendered back to SQL text, including identifier quoting
9//!   style, function name casing, and syntax variations.
10//! - **Transformation**: AST-level rewrites that convert dialect-specific constructs to/from
11//!   a normalized form (e.g., Snowflake `SQUARE(x)` becomes `POWER(x, 2)`).
12//!
13//! The primary entry point is [`Dialect::get`], which returns a configured [`Dialect`] instance
14//! for a given [`DialectType`]. From there, callers can [`parse`](Dialect::parse),
15//! [`generate`](Dialect::generate), [`transform`](Dialect::transform), or
16//! [`transpile`](Dialect::transpile) to another dialect in a single call.
17//!
18//! Each concrete dialect (e.g., `PostgresDialect`, `BigQueryDialect`) implements the
19//! [`DialectImpl`] trait, which provides configuration hooks and expression-level transforms.
20//! Dialect modules live in submodules of this module and are re-exported here.
21
22mod generic; // Always compiled
23
24#[cfg(feature = "dialect-athena")]
25mod athena;
26#[cfg(feature = "dialect-bigquery")]
27mod bigquery;
28#[cfg(feature = "dialect-clickhouse")]
29mod clickhouse;
30#[cfg(feature = "dialect-cockroachdb")]
31mod cockroachdb;
32#[cfg(feature = "dialect-databricks")]
33mod databricks;
34#[cfg(feature = "dialect-datafusion")]
35mod datafusion;
36#[cfg(feature = "dialect-doris")]
37mod doris;
38#[cfg(feature = "dialect-dremio")]
39mod dremio;
40#[cfg(feature = "dialect-drill")]
41mod drill;
42#[cfg(feature = "dialect-druid")]
43mod druid;
44#[cfg(feature = "dialect-duckdb")]
45mod duckdb;
46#[cfg(feature = "dialect-dune")]
47mod dune;
48#[cfg(feature = "dialect-exasol")]
49mod exasol;
50#[cfg(feature = "dialect-fabric")]
51mod fabric;
52#[cfg(feature = "dialect-hive")]
53mod hive;
54#[cfg(feature = "dialect-materialize")]
55mod materialize;
56#[cfg(feature = "dialect-mysql")]
57mod mysql;
58#[cfg(feature = "dialect-oracle")]
59mod oracle;
60#[cfg(feature = "dialect-postgresql")]
61mod postgres;
62#[cfg(feature = "dialect-presto")]
63mod presto;
64#[cfg(feature = "dialect-redshift")]
65mod redshift;
66#[cfg(feature = "dialect-risingwave")]
67mod risingwave;
68#[cfg(feature = "dialect-singlestore")]
69mod singlestore;
70#[cfg(feature = "dialect-snowflake")]
71mod snowflake;
72#[cfg(feature = "dialect-solr")]
73mod solr;
74#[cfg(feature = "dialect-spark")]
75mod spark;
76#[cfg(feature = "dialect-sqlite")]
77mod sqlite;
78#[cfg(feature = "dialect-starrocks")]
79mod starrocks;
80#[cfg(feature = "dialect-tableau")]
81mod tableau;
82#[cfg(feature = "dialect-teradata")]
83mod teradata;
84#[cfg(feature = "dialect-tidb")]
85mod tidb;
86#[cfg(feature = "dialect-trino")]
87mod trino;
88#[cfg(feature = "dialect-tsql")]
89mod tsql;
90
91pub use generic::GenericDialect; // Always available
92
93#[cfg(feature = "dialect-athena")]
94pub use athena::AthenaDialect;
95#[cfg(feature = "dialect-bigquery")]
96pub use bigquery::BigQueryDialect;
97#[cfg(feature = "dialect-clickhouse")]
98pub use clickhouse::ClickHouseDialect;
99#[cfg(feature = "dialect-cockroachdb")]
100pub use cockroachdb::CockroachDBDialect;
101#[cfg(feature = "dialect-databricks")]
102pub use databricks::DatabricksDialect;
103#[cfg(feature = "dialect-datafusion")]
104pub use datafusion::DataFusionDialect;
105#[cfg(feature = "dialect-doris")]
106pub use doris::DorisDialect;
107#[cfg(feature = "dialect-dremio")]
108pub use dremio::DremioDialect;
109#[cfg(feature = "dialect-drill")]
110pub use drill::DrillDialect;
111#[cfg(feature = "dialect-druid")]
112pub use druid::DruidDialect;
113#[cfg(feature = "dialect-duckdb")]
114pub use duckdb::DuckDBDialect;
115#[cfg(feature = "dialect-dune")]
116pub use dune::DuneDialect;
117#[cfg(feature = "dialect-exasol")]
118pub use exasol::ExasolDialect;
119#[cfg(feature = "dialect-fabric")]
120pub use fabric::FabricDialect;
121#[cfg(feature = "dialect-hive")]
122pub use hive::HiveDialect;
123#[cfg(feature = "dialect-materialize")]
124pub use materialize::MaterializeDialect;
125#[cfg(feature = "dialect-mysql")]
126pub use mysql::MySQLDialect;
127#[cfg(feature = "dialect-oracle")]
128pub use oracle::OracleDialect;
129#[cfg(feature = "dialect-postgresql")]
130pub use postgres::PostgresDialect;
131#[cfg(feature = "dialect-presto")]
132pub use presto::PrestoDialect;
133#[cfg(feature = "dialect-redshift")]
134pub use redshift::RedshiftDialect;
135#[cfg(feature = "dialect-risingwave")]
136pub use risingwave::RisingWaveDialect;
137#[cfg(feature = "dialect-singlestore")]
138pub use singlestore::SingleStoreDialect;
139#[cfg(feature = "dialect-snowflake")]
140pub use snowflake::SnowflakeDialect;
141#[cfg(feature = "dialect-solr")]
142pub use solr::SolrDialect;
143#[cfg(feature = "dialect-spark")]
144pub use spark::SparkDialect;
145#[cfg(feature = "dialect-sqlite")]
146pub use sqlite::SQLiteDialect;
147#[cfg(feature = "dialect-starrocks")]
148pub use starrocks::StarRocksDialect;
149#[cfg(feature = "dialect-tableau")]
150pub use tableau::TableauDialect;
151#[cfg(feature = "dialect-teradata")]
152pub use teradata::TeradataDialect;
153#[cfg(feature = "dialect-tidb")]
154pub use tidb::TiDBDialect;
155#[cfg(feature = "dialect-trino")]
156pub use trino::TrinoDialect;
157#[cfg(feature = "dialect-tsql")]
158pub use tsql::TSQLDialect;
159
160use crate::error::Result;
161#[cfg(feature = "transpile")]
162use crate::expressions::{Cast, ColumnConstraint, Function, Identifier, Literal};
163use crate::expressions::{DataType, Expression};
164#[cfg(any(
165    feature = "transpile",
166    feature = "ast-tools",
167    feature = "generate",
168    feature = "semantic"
169))]
170use crate::expressions::{From, FunctionBody, Join, Null, OrderBy, OutputClause, TableRef, With};
171#[cfg(feature = "transpile")]
172use crate::generator::UnsupportedLevel;
173#[cfg(feature = "generate")]
174use crate::generator::{Generator, GeneratorConfig};
175#[cfg(feature = "transpile")]
176use crate::guard::enforce_generate_ast;
177use crate::guard::{enforce_input, ComplexityGuardOptions};
178use crate::parser::Parser;
179#[cfg(feature = "transpile")]
180use crate::tokens::TokenType;
181use crate::tokens::{Token, Tokenizer, TokenizerConfig};
182#[cfg(feature = "transpile")]
183use crate::traversal::ExpressionWalk;
184use serde::{Deserialize, Serialize};
185use std::collections::HashMap;
186use std::sync::{Arc, LazyLock, RwLock};
187
188/// Enumeration of all supported SQL dialects.
189///
190/// Each variant corresponds to a specific SQL database engine or query language.
191/// The `Generic` variant represents standard SQL with no dialect-specific behavior,
192/// and is used as the default when no dialect is specified.
193///
194/// Dialect names are case-insensitive when parsed from strings via [`FromStr`].
195/// Some dialects accept aliases (e.g., "mssql" and "sqlserver" both resolve to [`TSQL`](DialectType::TSQL)).
196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
197#[serde(rename_all = "lowercase")]
198pub enum DialectType {
199    /// Standard SQL with no dialect-specific behavior (default).
200    Generic,
201    /// PostgreSQL -- advanced open-source relational database.
202    PostgreSQL,
203    /// MySQL -- widely-used open-source relational database (also accepts "mysql").
204    MySQL,
205    /// Google BigQuery -- serverless cloud data warehouse with unique syntax (backtick quoting, STRUCT types, QUALIFY).
206    BigQuery,
207    /// Snowflake -- cloud data platform with QUALIFY clause, FLATTEN, and variant types.
208    Snowflake,
209    /// DuckDB -- in-process analytical database with modern SQL extensions.
210    DuckDB,
211    /// SQLite -- lightweight embedded relational database.
212    SQLite,
213    /// Apache Hive -- data warehouse on Hadoop with HiveQL syntax.
214    Hive,
215    /// Apache Spark SQL -- distributed query engine (also accepts "spark2").
216    Spark,
217    /// Trino -- distributed SQL query engine (formerly PrestoSQL).
218    Trino,
219    /// PrestoDB -- distributed SQL query engine for big data.
220    Presto,
221    /// Amazon Redshift -- cloud data warehouse based on PostgreSQL.
222    Redshift,
223    /// Transact-SQL (T-SQL) -- Microsoft SQL Server and Azure SQL (also accepts "mssql", "sqlserver").
224    TSQL,
225    /// Oracle Database -- commercial relational database with PL/SQL extensions.
226    Oracle,
227    /// ClickHouse -- column-oriented OLAP database for real-time analytics.
228    ClickHouse,
229    /// Databricks SQL -- Spark-based lakehouse platform with QUALIFY support.
230    Databricks,
231    /// Amazon Athena -- serverless query service (hybrid Trino/Hive engine).
232    Athena,
233    /// Teradata -- enterprise data warehouse with proprietary SQL extensions.
234    Teradata,
235    /// Apache Doris -- real-time analytical database (MySQL-compatible).
236    Doris,
237    /// StarRocks -- sub-second OLAP database (MySQL-compatible).
238    StarRocks,
239    /// Materialize -- streaming SQL database built on differential dataflow.
240    Materialize,
241    /// RisingWave -- distributed streaming database with PostgreSQL compatibility.
242    RisingWave,
243    /// SingleStore (formerly MemSQL) -- distributed SQL database (also accepts "memsql").
244    SingleStore,
245    /// CockroachDB -- distributed SQL database with PostgreSQL compatibility (also accepts "cockroach").
246    CockroachDB,
247    /// TiDB -- distributed HTAP database with MySQL compatibility.
248    TiDB,
249    /// Apache Druid -- real-time analytics database.
250    Druid,
251    /// Apache Solr -- search platform with SQL interface.
252    Solr,
253    /// Tableau -- data visualization platform with its own SQL dialect.
254    Tableau,
255    /// Dune Analytics -- blockchain analytics SQL engine.
256    Dune,
257    /// Microsoft Fabric -- unified analytics platform (T-SQL based).
258    Fabric,
259    /// Apache Drill -- schema-free SQL query engine for big data.
260    Drill,
261    /// Dremio -- data lakehouse platform with Arrow-based query engine.
262    Dremio,
263    /// Exasol -- in-memory analytic database.
264    Exasol,
265    /// Apache DataFusion -- Arrow-based query engine with modern SQL extensions.
266    DataFusion,
267}
268
269impl Default for DialectType {
270    fn default() -> Self {
271        DialectType::Generic
272    }
273}
274
275impl std::fmt::Display for DialectType {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        match self {
278            DialectType::Generic => write!(f, "generic"),
279            DialectType::PostgreSQL => write!(f, "postgresql"),
280            DialectType::MySQL => write!(f, "mysql"),
281            DialectType::BigQuery => write!(f, "bigquery"),
282            DialectType::Snowflake => write!(f, "snowflake"),
283            DialectType::DuckDB => write!(f, "duckdb"),
284            DialectType::SQLite => write!(f, "sqlite"),
285            DialectType::Hive => write!(f, "hive"),
286            DialectType::Spark => write!(f, "spark"),
287            DialectType::Trino => write!(f, "trino"),
288            DialectType::Presto => write!(f, "presto"),
289            DialectType::Redshift => write!(f, "redshift"),
290            DialectType::TSQL => write!(f, "tsql"),
291            DialectType::Oracle => write!(f, "oracle"),
292            DialectType::ClickHouse => write!(f, "clickhouse"),
293            DialectType::Databricks => write!(f, "databricks"),
294            DialectType::Athena => write!(f, "athena"),
295            DialectType::Teradata => write!(f, "teradata"),
296            DialectType::Doris => write!(f, "doris"),
297            DialectType::StarRocks => write!(f, "starrocks"),
298            DialectType::Materialize => write!(f, "materialize"),
299            DialectType::RisingWave => write!(f, "risingwave"),
300            DialectType::SingleStore => write!(f, "singlestore"),
301            DialectType::CockroachDB => write!(f, "cockroachdb"),
302            DialectType::TiDB => write!(f, "tidb"),
303            DialectType::Druid => write!(f, "druid"),
304            DialectType::Solr => write!(f, "solr"),
305            DialectType::Tableau => write!(f, "tableau"),
306            DialectType::Dune => write!(f, "dune"),
307            DialectType::Fabric => write!(f, "fabric"),
308            DialectType::Drill => write!(f, "drill"),
309            DialectType::Dremio => write!(f, "dremio"),
310            DialectType::Exasol => write!(f, "exasol"),
311            DialectType::DataFusion => write!(f, "datafusion"),
312        }
313    }
314}
315
316impl std::str::FromStr for DialectType {
317    type Err = crate::error::Error;
318
319    fn from_str(s: &str) -> Result<Self> {
320        match s.to_ascii_lowercase().as_str() {
321            "generic" | "" => Ok(DialectType::Generic),
322            "postgres" | "postgresql" => Ok(DialectType::PostgreSQL),
323            "mysql" => Ok(DialectType::MySQL),
324            "bigquery" => Ok(DialectType::BigQuery),
325            "snowflake" => Ok(DialectType::Snowflake),
326            "duckdb" => Ok(DialectType::DuckDB),
327            "sqlite" => Ok(DialectType::SQLite),
328            "hive" => Ok(DialectType::Hive),
329            "spark" | "spark2" => Ok(DialectType::Spark),
330            "trino" => Ok(DialectType::Trino),
331            "presto" => Ok(DialectType::Presto),
332            "redshift" => Ok(DialectType::Redshift),
333            "tsql" | "mssql" | "sqlserver" => Ok(DialectType::TSQL),
334            "oracle" => Ok(DialectType::Oracle),
335            "clickhouse" => Ok(DialectType::ClickHouse),
336            "databricks" => Ok(DialectType::Databricks),
337            "athena" => Ok(DialectType::Athena),
338            "teradata" => Ok(DialectType::Teradata),
339            "doris" => Ok(DialectType::Doris),
340            "starrocks" => Ok(DialectType::StarRocks),
341            "materialize" => Ok(DialectType::Materialize),
342            "risingwave" => Ok(DialectType::RisingWave),
343            "singlestore" | "memsql" => Ok(DialectType::SingleStore),
344            "cockroachdb" | "cockroach" => Ok(DialectType::CockroachDB),
345            "tidb" => Ok(DialectType::TiDB),
346            "druid" => Ok(DialectType::Druid),
347            "solr" => Ok(DialectType::Solr),
348            "tableau" => Ok(DialectType::Tableau),
349            "dune" => Ok(DialectType::Dune),
350            "fabric" => Ok(DialectType::Fabric),
351            "drill" => Ok(DialectType::Drill),
352            "dremio" => Ok(DialectType::Dremio),
353            "exasol" => Ok(DialectType::Exasol),
354            "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Ok(DialectType::DataFusion),
355            _ => Err(crate::error::Error::parse(
356                format!("Unknown dialect: {}", s),
357                0,
358                0,
359                0,
360                0,
361            )),
362        }
363    }
364}
365
366/// Trait that each concrete SQL dialect must implement.
367///
368/// `DialectImpl` provides the configuration hooks and per-expression transform logic
369/// that distinguish one dialect from another. Implementors supply:
370///
371/// - A [`DialectType`] identifier.
372/// - Optional overrides for tokenizer and generator configuration (defaults to generic SQL).
373/// - An expression-level transform function ([`transform_expr`](DialectImpl::transform_expr))
374///   that rewrites individual AST nodes for this dialect (e.g., converting `NVL` to `COALESCE`).
375/// - An optional preprocessing step ([`preprocess`](DialectImpl::preprocess)) for whole-tree
376///   rewrites that must run before the recursive per-node transform (e.g., eliminating QUALIFY).
377///
378/// The default implementations are no-ops, so a minimal dialect only needs to provide
379/// [`dialect_type`](DialectImpl::dialect_type) and override the methods that differ from
380/// standard SQL.
381pub trait DialectImpl {
382    /// Returns the [`DialectType`] that identifies this dialect.
383    fn dialect_type(&self) -> DialectType;
384
385    /// Returns the tokenizer configuration for this dialect.
386    ///
387    /// Override to customize identifier quoting characters, string escape rules,
388    /// comment styles, and other lexing behavior.
389    fn tokenizer_config(&self) -> TokenizerConfig {
390        TokenizerConfig::default()
391    }
392
393    /// Returns the generator configuration for this dialect.
394    ///
395    /// Override to customize identifier quoting style, function name casing,
396    /// keyword casing, and other SQL generation behavior.
397    #[cfg(feature = "generate")]
398    fn generator_config(&self) -> GeneratorConfig {
399        GeneratorConfig::default()
400    }
401
402    /// Returns a generator configuration tailored to a specific expression.
403    ///
404    /// Override this for hybrid dialects like Athena that route to different SQL engines
405    /// based on expression type (e.g., Hive-style generation for DDL, Trino-style for DML).
406    /// The default delegates to [`generator_config`](DialectImpl::generator_config).
407    #[cfg(feature = "generate")]
408    fn generator_config_for_expr(&self, _expr: &Expression) -> GeneratorConfig {
409        self.generator_config()
410    }
411
412    /// Transforms a single expression node for this dialect, without recursing into children.
413    ///
414    /// This is the per-node rewrite hook invoked by [`transform_recursive`]. Return the
415    /// expression unchanged if no dialect-specific rewrite is needed. Transformations
416    /// typically include function renaming, operator substitution, and type mapping.
417    #[cfg(feature = "transpile")]
418    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
419        Ok(expr)
420    }
421
422    /// Applies whole-tree preprocessing transforms before the recursive per-node pass.
423    ///
424    /// Override this to apply structural rewrites that must see the entire tree at once,
425    /// such as `eliminate_qualify`, `eliminate_distinct_on`, `ensure_bools`, or
426    /// `explode_projection_to_unnest`. The default is a no-op pass-through.
427    #[cfg(feature = "transpile")]
428    fn preprocess(&self, expr: Expression) -> Result<Expression> {
429        Ok(expr)
430    }
431}
432
433/// Recursively transforms a [`DataType`](crate::expressions::DataType), handling nested
434/// parametric types such as `ARRAY<INT>`, `STRUCT<a INT, b TEXT>`, and `MAP<STRING, INT>`.
435///
436/// The outer type is first passed through `transform_fn` as an `Expression::DataType`,
437/// and then nested element/field types are recursed into. This ensures that dialect-level
438/// type mappings (e.g., `INT` to `INTEGER`) propagate into complex nested types.
439#[cfg(any(
440    feature = "transpile",
441    feature = "ast-tools",
442    feature = "generate",
443    feature = "semantic"
444))]
445fn transform_data_type_recursive<F>(
446    dt: crate::expressions::DataType,
447    transform_fn: &F,
448) -> Result<crate::expressions::DataType>
449where
450    F: Fn(Expression) -> Result<Expression>,
451{
452    use crate::expressions::DataType;
453    // First, transform the outermost type through the expression system
454    let dt_expr = transform_fn(Expression::DataType(dt))?;
455    let dt = match dt_expr {
456        Expression::DataType(d) => d,
457        _ => {
458            return Ok(match dt_expr {
459                _ => DataType::Custom {
460                    name: "UNKNOWN".to_string(),
461                },
462            })
463        }
464    };
465    // Then recurse into nested types
466    match dt {
467        DataType::Array {
468            element_type,
469            dimension,
470        } => {
471            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
472            Ok(DataType::Array {
473                element_type: Box::new(inner),
474                dimension,
475            })
476        }
477        DataType::List { element_type } => {
478            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
479            Ok(DataType::List {
480                element_type: Box::new(inner),
481            })
482        }
483        DataType::Struct { fields, nested } => {
484            let mut new_fields = Vec::new();
485            for mut field in fields {
486                field.data_type = transform_data_type_recursive(field.data_type, transform_fn)?;
487                new_fields.push(field);
488            }
489            Ok(DataType::Struct {
490                fields: new_fields,
491                nested,
492            })
493        }
494        DataType::Map {
495            key_type,
496            value_type,
497        } => {
498            let k = transform_data_type_recursive(*key_type, transform_fn)?;
499            let v = transform_data_type_recursive(*value_type, transform_fn)?;
500            Ok(DataType::Map {
501                key_type: Box::new(k),
502                value_type: Box::new(v),
503            })
504        }
505        other => Ok(other),
506    }
507}
508
509/// Convert DuckDB C-style format strings to Presto C-style format strings.
510/// DuckDB and Presto both use C-style % directives but with different specifiers for some cases.
511#[cfg(feature = "transpile")]
512fn duckdb_to_presto_format(fmt: &str) -> String {
513    // Order matters: handle longer patterns first to avoid partial replacements
514    let mut result = fmt.to_string();
515    // First pass: mark multi-char patterns with placeholders
516    result = result.replace("%-m", "\x01NOPADM\x01");
517    result = result.replace("%-d", "\x01NOPADD\x01");
518    result = result.replace("%-I", "\x01NOPADI\x01");
519    result = result.replace("%-H", "\x01NOPADH\x01");
520    result = result.replace("%H:%M:%S", "\x01HMS\x01");
521    result = result.replace("%Y-%m-%d", "\x01YMD\x01");
522    // Now convert individual specifiers
523    result = result.replace("%M", "%i");
524    result = result.replace("%S", "%s");
525    // Restore multi-char patterns with Presto equivalents
526    result = result.replace("\x01NOPADM\x01", "%c");
527    result = result.replace("\x01NOPADD\x01", "%e");
528    result = result.replace("\x01NOPADI\x01", "%l");
529    result = result.replace("\x01NOPADH\x01", "%k");
530    result = result.replace("\x01HMS\x01", "%T");
531    result = result.replace("\x01YMD\x01", "%Y-%m-%d");
532    result
533}
534
535/// Convert DuckDB C-style format strings to BigQuery format strings.
536/// BigQuery uses a mix of strftime-like directives.
537#[cfg(feature = "transpile")]
538fn duckdb_to_bigquery_format(fmt: &str) -> String {
539    let mut result = fmt.to_string();
540    // Handle longer patterns first
541    result = result.replace("%-d", "%e");
542    result = result.replace("%Y-%m-%d %H:%M:%S", "%F %T");
543    result = result.replace("%Y-%m-%d", "%F");
544    result = result.replace("%H:%M:%S", "%T");
545    result
546}
547
548#[cfg(feature = "transpile")]
549fn presto_to_java_format(fmt: &str) -> String {
550    fmt.replace("%Y", "yyyy")
551        .replace("%m", "MM")
552        .replace("%d", "dd")
553        .replace("%H", "HH")
554        .replace("%i", "mm")
555        .replace("%S", "ss")
556        .replace("%s", "ss")
557        .replace("%y", "yy")
558        .replace("%T", "HH:mm:ss")
559        .replace("%F", "yyyy-MM-dd")
560        .replace("%M", "MMMM")
561}
562
563#[cfg(feature = "transpile")]
564fn normalize_presto_format(fmt: &str) -> String {
565    fmt.replace("%H:%i:%S", "%T").replace("%H:%i:%s", "%T")
566}
567
568#[cfg(feature = "transpile")]
569fn presto_to_duckdb_format(fmt: &str) -> String {
570    fmt.replace("%i", "%M")
571        .replace("%s", "%S")
572        .replace("%T", "%H:%M:%S")
573}
574
575#[cfg(feature = "transpile")]
576fn presto_to_bigquery_format(fmt: &str) -> String {
577    fmt.replace("%Y-%m-%d", "%F")
578        .replace("%H:%i:%S", "%T")
579        .replace("%H:%i:%s", "%T")
580        .replace("%i", "%M")
581        .replace("%s", "%S")
582}
583
584#[cfg(feature = "transpile")]
585fn is_default_presto_timestamp_format(fmt: &str) -> bool {
586    let normalized = normalize_presto_format(fmt);
587    normalized == "%Y-%m-%d %T"
588        || normalized == "%Y-%m-%d %H:%i:%S"
589        || fmt == "%Y-%m-%d %H:%i:%S"
590        || fmt == "%Y-%m-%d %T"
591}
592
593#[cfg(feature = "transpile")]
594fn is_default_presto_date_format(fmt: &str) -> bool {
595    fmt == "%Y-%m-%d" || fmt == "%F"
596}
597
598#[cfg(any(
599    feature = "transpile",
600    feature = "ast-tools",
601    feature = "generate",
602    feature = "semantic"
603))]
604#[derive(Debug)]
605enum TransformTask {
606    Visit(Expression),
607    Finish(FinishTask),
608}
609
610#[cfg(any(
611    feature = "transpile",
612    feature = "ast-tools",
613    feature = "generate",
614    feature = "semantic"
615))]
616#[derive(Debug)]
617enum FinishTask {
618    Unary(Expression),
619    Binary(Expression),
620    CastLike(Expression),
621    List(Expression, usize),
622    From(crate::expressions::From, usize),
623    Select(SelectFrame),
624    SetOp(Expression),
625}
626
627#[cfg(any(
628    feature = "transpile",
629    feature = "ast-tools",
630    feature = "generate",
631    feature = "semantic"
632))]
633#[derive(Debug)]
634struct SelectFrame {
635    select: Box<crate::expressions::Select>,
636    expr_count: usize,
637    from_present: bool,
638    where_present: bool,
639    group_by_count: usize,
640    having_present: bool,
641    qualify_present: bool,
642}
643
644#[cfg(any(
645    feature = "transpile",
646    feature = "ast-tools",
647    feature = "generate",
648    feature = "semantic"
649))]
650fn transform_pop_result(results: &mut Vec<Expression>) -> Result<Expression> {
651    results
652        .pop()
653        .ok_or_else(|| crate::error::Error::Internal("transform stack underflow".to_string()))
654}
655
656#[cfg(any(
657    feature = "transpile",
658    feature = "ast-tools",
659    feature = "generate",
660    feature = "semantic"
661))]
662fn transform_pop_results(results: &mut Vec<Expression>, count: usize) -> Result<Vec<Expression>> {
663    if results.len() < count {
664        return Err(crate::error::Error::Internal(
665            "transform result stack underflow".to_string(),
666        ));
667    }
668    Ok(results.split_off(results.len() - count))
669}
670
671/// Applies a transform function bottom-up through an entire expression tree.
672///
673/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
674/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
675/// trees, and common binary/unary expression chains). Less common shapes currently
676/// reuse the reference recursive implementation so semantics stay identical while
677/// the hot path avoids stack growth.
678#[cfg(any(
679    feature = "transpile",
680    feature = "ast-tools",
681    feature = "generate",
682    feature = "semantic"
683))]
684pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
685where
686    F: Fn(Expression) -> Result<Expression>,
687{
688    #[cfg(feature = "stacker")]
689    {
690        let red_zone = if cfg!(debug_assertions) {
691            4 * 1024 * 1024
692        } else {
693            1024 * 1024
694        };
695        stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
696            transform_recursive_inner(expr, transform_fn)
697        })
698    }
699    #[cfg(not(feature = "stacker"))]
700    {
701        transform_recursive_inner(expr, transform_fn)
702    }
703}
704
705#[cfg(any(
706    feature = "transpile",
707    feature = "ast-tools",
708    feature = "generate",
709    feature = "semantic"
710))]
711fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
712where
713    F: Fn(Expression) -> Result<Expression>,
714{
715    let mut tasks = vec![TransformTask::Visit(expr)];
716    let mut results = Vec::new();
717
718    while let Some(task) = tasks.pop() {
719        match task {
720            TransformTask::Visit(expr) => {
721                if matches!(
722                    &expr,
723                    Expression::Literal(_)
724                        | Expression::Boolean(_)
725                        | Expression::Null(_)
726                        | Expression::Identifier(_)
727                        | Expression::Star(_)
728                        | Expression::Parameter(_)
729                        | Expression::Placeholder(_)
730                        | Expression::SessionParameter(_)
731                ) {
732                    results.push(transform_fn(expr)?);
733                    continue;
734                }
735
736                match expr {
737                    Expression::Alias(mut alias) => {
738                        let child = std::mem::replace(&mut alias.this, Expression::Null(Null));
739                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Alias(
740                            alias,
741                        ))));
742                        tasks.push(TransformTask::Visit(child));
743                    }
744                    Expression::Paren(mut paren) => {
745                        let child = std::mem::replace(&mut paren.this, Expression::Null(Null));
746                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Paren(
747                            paren,
748                        ))));
749                        tasks.push(TransformTask::Visit(child));
750                    }
751                    Expression::Not(mut not) => {
752                        let child = std::mem::replace(&mut not.this, Expression::Null(Null));
753                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Not(
754                            not,
755                        ))));
756                        tasks.push(TransformTask::Visit(child));
757                    }
758                    Expression::Neg(mut neg) => {
759                        let child = std::mem::replace(&mut neg.this, Expression::Null(Null));
760                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Neg(
761                            neg,
762                        ))));
763                        tasks.push(TransformTask::Visit(child));
764                    }
765                    Expression::IsNull(mut expr) => {
766                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
767                        tasks.push(TransformTask::Finish(FinishTask::Unary(
768                            Expression::IsNull(expr),
769                        )));
770                        tasks.push(TransformTask::Visit(child));
771                    }
772                    Expression::IsTrue(mut expr) => {
773                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
774                        tasks.push(TransformTask::Finish(FinishTask::Unary(
775                            Expression::IsTrue(expr),
776                        )));
777                        tasks.push(TransformTask::Visit(child));
778                    }
779                    Expression::IsFalse(mut expr) => {
780                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
781                        tasks.push(TransformTask::Finish(FinishTask::Unary(
782                            Expression::IsFalse(expr),
783                        )));
784                        tasks.push(TransformTask::Visit(child));
785                    }
786                    Expression::Subquery(mut subquery) => {
787                        let child = std::mem::replace(&mut subquery.this, Expression::Null(Null));
788                        tasks.push(TransformTask::Finish(FinishTask::Unary(
789                            Expression::Subquery(subquery),
790                        )));
791                        tasks.push(TransformTask::Visit(child));
792                    }
793                    Expression::Exists(mut exists) => {
794                        let child = std::mem::replace(&mut exists.this, Expression::Null(Null));
795                        tasks.push(TransformTask::Finish(FinishTask::Unary(
796                            Expression::Exists(exists),
797                        )));
798                        tasks.push(TransformTask::Visit(child));
799                    }
800                    Expression::TableArgument(mut arg) => {
801                        let child = std::mem::replace(&mut arg.this, Expression::Null(Null));
802                        tasks.push(TransformTask::Finish(FinishTask::Unary(
803                            Expression::TableArgument(arg),
804                        )));
805                        tasks.push(TransformTask::Visit(child));
806                    }
807                    Expression::And(mut op) => {
808                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
809                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
810                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::And(
811                            op,
812                        ))));
813                        tasks.push(TransformTask::Visit(right));
814                        tasks.push(TransformTask::Visit(left));
815                    }
816                    Expression::Or(mut op) => {
817                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
818                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
819                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Or(
820                            op,
821                        ))));
822                        tasks.push(TransformTask::Visit(right));
823                        tasks.push(TransformTask::Visit(left));
824                    }
825                    Expression::Add(mut op) => {
826                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
827                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
828                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Add(
829                            op,
830                        ))));
831                        tasks.push(TransformTask::Visit(right));
832                        tasks.push(TransformTask::Visit(left));
833                    }
834                    Expression::Sub(mut op) => {
835                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
836                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
837                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Sub(
838                            op,
839                        ))));
840                        tasks.push(TransformTask::Visit(right));
841                        tasks.push(TransformTask::Visit(left));
842                    }
843                    Expression::Mul(mut op) => {
844                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
845                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
846                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mul(
847                            op,
848                        ))));
849                        tasks.push(TransformTask::Visit(right));
850                        tasks.push(TransformTask::Visit(left));
851                    }
852                    Expression::Div(mut op) => {
853                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
854                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
855                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Div(
856                            op,
857                        ))));
858                        tasks.push(TransformTask::Visit(right));
859                        tasks.push(TransformTask::Visit(left));
860                    }
861                    Expression::Eq(mut op) => {
862                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
863                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
864                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Eq(
865                            op,
866                        ))));
867                        tasks.push(TransformTask::Visit(right));
868                        tasks.push(TransformTask::Visit(left));
869                    }
870                    Expression::Lt(mut op) => {
871                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
872                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
873                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lt(
874                            op,
875                        ))));
876                        tasks.push(TransformTask::Visit(right));
877                        tasks.push(TransformTask::Visit(left));
878                    }
879                    Expression::Gt(mut op) => {
880                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
881                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
882                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gt(
883                            op,
884                        ))));
885                        tasks.push(TransformTask::Visit(right));
886                        tasks.push(TransformTask::Visit(left));
887                    }
888                    Expression::Neq(mut op) => {
889                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
890                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
891                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Neq(
892                            op,
893                        ))));
894                        tasks.push(TransformTask::Visit(right));
895                        tasks.push(TransformTask::Visit(left));
896                    }
897                    Expression::Lte(mut op) => {
898                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
899                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
900                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lte(
901                            op,
902                        ))));
903                        tasks.push(TransformTask::Visit(right));
904                        tasks.push(TransformTask::Visit(left));
905                    }
906                    Expression::Gte(mut op) => {
907                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
908                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
909                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gte(
910                            op,
911                        ))));
912                        tasks.push(TransformTask::Visit(right));
913                        tasks.push(TransformTask::Visit(left));
914                    }
915                    Expression::Mod(mut op) => {
916                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
917                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
918                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mod(
919                            op,
920                        ))));
921                        tasks.push(TransformTask::Visit(right));
922                        tasks.push(TransformTask::Visit(left));
923                    }
924                    Expression::Concat(mut op) => {
925                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
926                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
927                        tasks.push(TransformTask::Finish(FinishTask::Binary(
928                            Expression::Concat(op),
929                        )));
930                        tasks.push(TransformTask::Visit(right));
931                        tasks.push(TransformTask::Visit(left));
932                    }
933                    Expression::BitwiseAnd(mut op) => {
934                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
935                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
936                        tasks.push(TransformTask::Finish(FinishTask::Binary(
937                            Expression::BitwiseAnd(op),
938                        )));
939                        tasks.push(TransformTask::Visit(right));
940                        tasks.push(TransformTask::Visit(left));
941                    }
942                    Expression::BitwiseOr(mut op) => {
943                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
944                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
945                        tasks.push(TransformTask::Finish(FinishTask::Binary(
946                            Expression::BitwiseOr(op),
947                        )));
948                        tasks.push(TransformTask::Visit(right));
949                        tasks.push(TransformTask::Visit(left));
950                    }
951                    Expression::BitwiseXor(mut op) => {
952                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
953                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
954                        tasks.push(TransformTask::Finish(FinishTask::Binary(
955                            Expression::BitwiseXor(op),
956                        )));
957                        tasks.push(TransformTask::Visit(right));
958                        tasks.push(TransformTask::Visit(left));
959                    }
960                    Expression::Is(mut op) => {
961                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
962                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
963                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Is(
964                            op,
965                        ))));
966                        tasks.push(TransformTask::Visit(right));
967                        tasks.push(TransformTask::Visit(left));
968                    }
969                    Expression::MemberOf(mut op) => {
970                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
971                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
972                        tasks.push(TransformTask::Finish(FinishTask::Binary(
973                            Expression::MemberOf(op),
974                        )));
975                        tasks.push(TransformTask::Visit(right));
976                        tasks.push(TransformTask::Visit(left));
977                    }
978                    Expression::ArrayContainsAll(mut op) => {
979                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
980                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
981                        tasks.push(TransformTask::Finish(FinishTask::Binary(
982                            Expression::ArrayContainsAll(op),
983                        )));
984                        tasks.push(TransformTask::Visit(right));
985                        tasks.push(TransformTask::Visit(left));
986                    }
987                    Expression::ArrayContainedBy(mut op) => {
988                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
989                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
990                        tasks.push(TransformTask::Finish(FinishTask::Binary(
991                            Expression::ArrayContainedBy(op),
992                        )));
993                        tasks.push(TransformTask::Visit(right));
994                        tasks.push(TransformTask::Visit(left));
995                    }
996                    Expression::ArrayOverlaps(mut op) => {
997                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
998                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
999                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1000                            Expression::ArrayOverlaps(op),
1001                        )));
1002                        tasks.push(TransformTask::Visit(right));
1003                        tasks.push(TransformTask::Visit(left));
1004                    }
1005                    Expression::TsMatch(mut op) => {
1006                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1007                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1008                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1009                            Expression::TsMatch(op),
1010                        )));
1011                        tasks.push(TransformTask::Visit(right));
1012                        tasks.push(TransformTask::Visit(left));
1013                    }
1014                    Expression::Adjacent(mut op) => {
1015                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1016                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1017                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1018                            Expression::Adjacent(op),
1019                        )));
1020                        tasks.push(TransformTask::Visit(right));
1021                        tasks.push(TransformTask::Visit(left));
1022                    }
1023                    Expression::Like(mut like) => {
1024                        let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1025                        let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1026                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Like(
1027                            like,
1028                        ))));
1029                        tasks.push(TransformTask::Visit(right));
1030                        tasks.push(TransformTask::Visit(left));
1031                    }
1032                    Expression::ILike(mut like) => {
1033                        let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1034                        let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1035                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1036                            Expression::ILike(like),
1037                        )));
1038                        tasks.push(TransformTask::Visit(right));
1039                        tasks.push(TransformTask::Visit(left));
1040                    }
1041                    Expression::Cast(mut cast) => {
1042                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1043                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1044                            Expression::Cast(cast),
1045                        )));
1046                        tasks.push(TransformTask::Visit(child));
1047                    }
1048                    Expression::TryCast(mut cast) => {
1049                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1050                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1051                            Expression::TryCast(cast),
1052                        )));
1053                        tasks.push(TransformTask::Visit(child));
1054                    }
1055                    Expression::SafeCast(mut cast) => {
1056                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1057                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1058                            Expression::SafeCast(cast),
1059                        )));
1060                        tasks.push(TransformTask::Visit(child));
1061                    }
1062                    Expression::Function(mut function) => {
1063                        let args = std::mem::take(&mut function.args);
1064                        let count = args.len();
1065                        tasks.push(TransformTask::Finish(FinishTask::List(
1066                            Expression::Function(function),
1067                            count,
1068                        )));
1069                        for child in args.into_iter().rev() {
1070                            tasks.push(TransformTask::Visit(child));
1071                        }
1072                    }
1073                    Expression::Array(mut array) => {
1074                        let expressions = std::mem::take(&mut array.expressions);
1075                        let count = expressions.len();
1076                        tasks.push(TransformTask::Finish(FinishTask::List(
1077                            Expression::Array(array),
1078                            count,
1079                        )));
1080                        for child in expressions.into_iter().rev() {
1081                            tasks.push(TransformTask::Visit(child));
1082                        }
1083                    }
1084                    Expression::Tuple(mut tuple) => {
1085                        let expressions = std::mem::take(&mut tuple.expressions);
1086                        let count = expressions.len();
1087                        tasks.push(TransformTask::Finish(FinishTask::List(
1088                            Expression::Tuple(tuple),
1089                            count,
1090                        )));
1091                        for child in expressions.into_iter().rev() {
1092                            tasks.push(TransformTask::Visit(child));
1093                        }
1094                    }
1095                    Expression::ArrayFunc(mut array) => {
1096                        let expressions = std::mem::take(&mut array.expressions);
1097                        let count = expressions.len();
1098                        tasks.push(TransformTask::Finish(FinishTask::List(
1099                            Expression::ArrayFunc(array),
1100                            count,
1101                        )));
1102                        for child in expressions.into_iter().rev() {
1103                            tasks.push(TransformTask::Visit(child));
1104                        }
1105                    }
1106                    Expression::Coalesce(mut func) => {
1107                        let expressions = std::mem::take(&mut func.expressions);
1108                        let count = expressions.len();
1109                        tasks.push(TransformTask::Finish(FinishTask::List(
1110                            Expression::Coalesce(func),
1111                            count,
1112                        )));
1113                        for child in expressions.into_iter().rev() {
1114                            tasks.push(TransformTask::Visit(child));
1115                        }
1116                    }
1117                    Expression::Greatest(mut func) => {
1118                        let expressions = std::mem::take(&mut func.expressions);
1119                        let count = expressions.len();
1120                        tasks.push(TransformTask::Finish(FinishTask::List(
1121                            Expression::Greatest(func),
1122                            count,
1123                        )));
1124                        for child in expressions.into_iter().rev() {
1125                            tasks.push(TransformTask::Visit(child));
1126                        }
1127                    }
1128                    Expression::Least(mut func) => {
1129                        let expressions = std::mem::take(&mut func.expressions);
1130                        let count = expressions.len();
1131                        tasks.push(TransformTask::Finish(FinishTask::List(
1132                            Expression::Least(func),
1133                            count,
1134                        )));
1135                        for child in expressions.into_iter().rev() {
1136                            tasks.push(TransformTask::Visit(child));
1137                        }
1138                    }
1139                    Expression::ArrayConcat(mut func) => {
1140                        let expressions = std::mem::take(&mut func.expressions);
1141                        let count = expressions.len();
1142                        tasks.push(TransformTask::Finish(FinishTask::List(
1143                            Expression::ArrayConcat(func),
1144                            count,
1145                        )));
1146                        for child in expressions.into_iter().rev() {
1147                            tasks.push(TransformTask::Visit(child));
1148                        }
1149                    }
1150                    Expression::ArrayIntersect(mut func) => {
1151                        let expressions = std::mem::take(&mut func.expressions);
1152                        let count = expressions.len();
1153                        tasks.push(TransformTask::Finish(FinishTask::List(
1154                            Expression::ArrayIntersect(func),
1155                            count,
1156                        )));
1157                        for child in expressions.into_iter().rev() {
1158                            tasks.push(TransformTask::Visit(child));
1159                        }
1160                    }
1161                    Expression::ArrayZip(mut func) => {
1162                        let expressions = std::mem::take(&mut func.expressions);
1163                        let count = expressions.len();
1164                        tasks.push(TransformTask::Finish(FinishTask::List(
1165                            Expression::ArrayZip(func),
1166                            count,
1167                        )));
1168                        for child in expressions.into_iter().rev() {
1169                            tasks.push(TransformTask::Visit(child));
1170                        }
1171                    }
1172                    Expression::MapConcat(mut func) => {
1173                        let expressions = std::mem::take(&mut func.expressions);
1174                        let count = expressions.len();
1175                        tasks.push(TransformTask::Finish(FinishTask::List(
1176                            Expression::MapConcat(func),
1177                            count,
1178                        )));
1179                        for child in expressions.into_iter().rev() {
1180                            tasks.push(TransformTask::Visit(child));
1181                        }
1182                    }
1183                    Expression::JsonArray(mut func) => {
1184                        let expressions = std::mem::take(&mut func.expressions);
1185                        let count = expressions.len();
1186                        tasks.push(TransformTask::Finish(FinishTask::List(
1187                            Expression::JsonArray(func),
1188                            count,
1189                        )));
1190                        for child in expressions.into_iter().rev() {
1191                            tasks.push(TransformTask::Visit(child));
1192                        }
1193                    }
1194                    Expression::From(mut from) => {
1195                        let expressions = std::mem::take(&mut from.expressions);
1196                        let count = expressions.len();
1197                        tasks.push(TransformTask::Finish(FinishTask::From(*from, count)));
1198                        for child in expressions.into_iter().rev() {
1199                            tasks.push(TransformTask::Visit(child));
1200                        }
1201                    }
1202                    Expression::Select(mut select) => {
1203                        let expressions = std::mem::take(&mut select.expressions);
1204                        let expr_count = expressions.len();
1205
1206                        let from_info = select.from.take().map(|mut from| {
1207                            let children = std::mem::take(&mut from.expressions);
1208                            (from, children)
1209                        });
1210                        let from_present = from_info.is_some();
1211
1212                        let where_child = select.where_clause.as_mut().map(|where_clause| {
1213                            std::mem::replace(&mut where_clause.this, Expression::Null(Null))
1214                        });
1215                        let where_present = where_child.is_some();
1216
1217                        let group_expressions = select
1218                            .group_by
1219                            .as_mut()
1220                            .map(|group_by| std::mem::take(&mut group_by.expressions))
1221                            .unwrap_or_default();
1222                        let group_by_count = group_expressions.len();
1223
1224                        let having_child = select.having.as_mut().map(|having| {
1225                            std::mem::replace(&mut having.this, Expression::Null(Null))
1226                        });
1227                        let having_present = having_child.is_some();
1228
1229                        let qualify_child = select.qualify.as_mut().map(|qualify| {
1230                            std::mem::replace(&mut qualify.this, Expression::Null(Null))
1231                        });
1232                        let qualify_present = qualify_child.is_some();
1233
1234                        tasks.push(TransformTask::Finish(FinishTask::Select(SelectFrame {
1235                            select,
1236                            expr_count,
1237                            from_present,
1238                            where_present,
1239                            group_by_count,
1240                            having_present,
1241                            qualify_present,
1242                        })));
1243
1244                        if let Some(child) = qualify_child {
1245                            tasks.push(TransformTask::Visit(child));
1246                        }
1247                        if let Some(child) = having_child {
1248                            tasks.push(TransformTask::Visit(child));
1249                        }
1250                        for child in group_expressions.into_iter().rev() {
1251                            tasks.push(TransformTask::Visit(child));
1252                        }
1253                        if let Some(child) = where_child {
1254                            tasks.push(TransformTask::Visit(child));
1255                        }
1256                        if let Some((from, children)) = from_info {
1257                            tasks.push(TransformTask::Finish(FinishTask::From(
1258                                from,
1259                                children.len(),
1260                            )));
1261                            for child in children.into_iter().rev() {
1262                                tasks.push(TransformTask::Visit(child));
1263                            }
1264                        }
1265                        for child in expressions.into_iter().rev() {
1266                            tasks.push(TransformTask::Visit(child));
1267                        }
1268                    }
1269                    Expression::Union(mut union) => {
1270                        let right = std::mem::replace(&mut union.right, Expression::Null(Null));
1271                        let left = std::mem::replace(&mut union.left, Expression::Null(Null));
1272                        tasks.push(TransformTask::Finish(FinishTask::SetOp(Expression::Union(
1273                            union,
1274                        ))));
1275                        tasks.push(TransformTask::Visit(right));
1276                        tasks.push(TransformTask::Visit(left));
1277                    }
1278                    Expression::Intersect(mut intersect) => {
1279                        let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
1280                        let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
1281                        tasks.push(TransformTask::Finish(FinishTask::SetOp(
1282                            Expression::Intersect(intersect),
1283                        )));
1284                        tasks.push(TransformTask::Visit(right));
1285                        tasks.push(TransformTask::Visit(left));
1286                    }
1287                    Expression::Except(mut except) => {
1288                        let right = std::mem::replace(&mut except.right, Expression::Null(Null));
1289                        let left = std::mem::replace(&mut except.left, Expression::Null(Null));
1290                        tasks.push(TransformTask::Finish(FinishTask::SetOp(
1291                            Expression::Except(except),
1292                        )));
1293                        tasks.push(TransformTask::Visit(right));
1294                        tasks.push(TransformTask::Visit(left));
1295                    }
1296                    other => {
1297                        results.push(transform_recursive_reference(other, transform_fn)?);
1298                    }
1299                }
1300            }
1301            TransformTask::Finish(finish) => match finish {
1302                FinishTask::Unary(expr) => {
1303                    let child = transform_pop_result(&mut results)?;
1304                    let rebuilt = match expr {
1305                        Expression::Alias(mut alias) => {
1306                            alias.this = child;
1307                            Expression::Alias(alias)
1308                        }
1309                        Expression::Paren(mut paren) => {
1310                            paren.this = child;
1311                            Expression::Paren(paren)
1312                        }
1313                        Expression::Not(mut not) => {
1314                            not.this = child;
1315                            Expression::Not(not)
1316                        }
1317                        Expression::Neg(mut neg) => {
1318                            neg.this = child;
1319                            Expression::Neg(neg)
1320                        }
1321                        Expression::IsNull(mut expr) => {
1322                            expr.this = child;
1323                            Expression::IsNull(expr)
1324                        }
1325                        Expression::IsTrue(mut expr) => {
1326                            expr.this = child;
1327                            Expression::IsTrue(expr)
1328                        }
1329                        Expression::IsFalse(mut expr) => {
1330                            expr.this = child;
1331                            Expression::IsFalse(expr)
1332                        }
1333                        Expression::Subquery(mut subquery) => {
1334                            subquery.this = child;
1335                            Expression::Subquery(subquery)
1336                        }
1337                        Expression::Exists(mut exists) => {
1338                            exists.this = child;
1339                            Expression::Exists(exists)
1340                        }
1341                        Expression::TableArgument(mut arg) => {
1342                            arg.this = child;
1343                            Expression::TableArgument(arg)
1344                        }
1345                        _ => {
1346                            return Err(crate::error::Error::Internal(
1347                                "unexpected unary transform task".to_string(),
1348                            ));
1349                        }
1350                    };
1351                    results.push(transform_fn(rebuilt)?);
1352                }
1353                FinishTask::Binary(expr) => {
1354                    let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1355                    let left = children.next().expect("left child");
1356                    let right = children.next().expect("right child");
1357                    let rebuilt = match expr {
1358                        Expression::And(mut op) => {
1359                            op.left = left;
1360                            op.right = right;
1361                            Expression::And(op)
1362                        }
1363                        Expression::Or(mut op) => {
1364                            op.left = left;
1365                            op.right = right;
1366                            Expression::Or(op)
1367                        }
1368                        Expression::Add(mut op) => {
1369                            op.left = left;
1370                            op.right = right;
1371                            Expression::Add(op)
1372                        }
1373                        Expression::Sub(mut op) => {
1374                            op.left = left;
1375                            op.right = right;
1376                            Expression::Sub(op)
1377                        }
1378                        Expression::Mul(mut op) => {
1379                            op.left = left;
1380                            op.right = right;
1381                            Expression::Mul(op)
1382                        }
1383                        Expression::Div(mut op) => {
1384                            op.left = left;
1385                            op.right = right;
1386                            Expression::Div(op)
1387                        }
1388                        Expression::Eq(mut op) => {
1389                            op.left = left;
1390                            op.right = right;
1391                            Expression::Eq(op)
1392                        }
1393                        Expression::Lt(mut op) => {
1394                            op.left = left;
1395                            op.right = right;
1396                            Expression::Lt(op)
1397                        }
1398                        Expression::Gt(mut op) => {
1399                            op.left = left;
1400                            op.right = right;
1401                            Expression::Gt(op)
1402                        }
1403                        Expression::Neq(mut op) => {
1404                            op.left = left;
1405                            op.right = right;
1406                            Expression::Neq(op)
1407                        }
1408                        Expression::Lte(mut op) => {
1409                            op.left = left;
1410                            op.right = right;
1411                            Expression::Lte(op)
1412                        }
1413                        Expression::Gte(mut op) => {
1414                            op.left = left;
1415                            op.right = right;
1416                            Expression::Gte(op)
1417                        }
1418                        Expression::Mod(mut op) => {
1419                            op.left = left;
1420                            op.right = right;
1421                            Expression::Mod(op)
1422                        }
1423                        Expression::Concat(mut op) => {
1424                            op.left = left;
1425                            op.right = right;
1426                            Expression::Concat(op)
1427                        }
1428                        Expression::BitwiseAnd(mut op) => {
1429                            op.left = left;
1430                            op.right = right;
1431                            Expression::BitwiseAnd(op)
1432                        }
1433                        Expression::BitwiseOr(mut op) => {
1434                            op.left = left;
1435                            op.right = right;
1436                            Expression::BitwiseOr(op)
1437                        }
1438                        Expression::BitwiseXor(mut op) => {
1439                            op.left = left;
1440                            op.right = right;
1441                            Expression::BitwiseXor(op)
1442                        }
1443                        Expression::Is(mut op) => {
1444                            op.left = left;
1445                            op.right = right;
1446                            Expression::Is(op)
1447                        }
1448                        Expression::MemberOf(mut op) => {
1449                            op.left = left;
1450                            op.right = right;
1451                            Expression::MemberOf(op)
1452                        }
1453                        Expression::ArrayContainsAll(mut op) => {
1454                            op.left = left;
1455                            op.right = right;
1456                            Expression::ArrayContainsAll(op)
1457                        }
1458                        Expression::ArrayContainedBy(mut op) => {
1459                            op.left = left;
1460                            op.right = right;
1461                            Expression::ArrayContainedBy(op)
1462                        }
1463                        Expression::ArrayOverlaps(mut op) => {
1464                            op.left = left;
1465                            op.right = right;
1466                            Expression::ArrayOverlaps(op)
1467                        }
1468                        Expression::TsMatch(mut op) => {
1469                            op.left = left;
1470                            op.right = right;
1471                            Expression::TsMatch(op)
1472                        }
1473                        Expression::Adjacent(mut op) => {
1474                            op.left = left;
1475                            op.right = right;
1476                            Expression::Adjacent(op)
1477                        }
1478                        Expression::Like(mut like) => {
1479                            like.left = left;
1480                            like.right = right;
1481                            Expression::Like(like)
1482                        }
1483                        Expression::ILike(mut like) => {
1484                            like.left = left;
1485                            like.right = right;
1486                            Expression::ILike(like)
1487                        }
1488                        _ => {
1489                            return Err(crate::error::Error::Internal(
1490                                "unexpected binary transform task".to_string(),
1491                            ));
1492                        }
1493                    };
1494                    results.push(transform_fn(rebuilt)?);
1495                }
1496                FinishTask::CastLike(expr) => {
1497                    let child = transform_pop_result(&mut results)?;
1498                    let rebuilt = match expr {
1499                        Expression::Cast(mut cast) => {
1500                            cast.this = child;
1501                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1502                            Expression::Cast(cast)
1503                        }
1504                        Expression::TryCast(mut cast) => {
1505                            cast.this = child;
1506                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1507                            Expression::TryCast(cast)
1508                        }
1509                        Expression::SafeCast(mut cast) => {
1510                            cast.this = child;
1511                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1512                            Expression::SafeCast(cast)
1513                        }
1514                        _ => {
1515                            return Err(crate::error::Error::Internal(
1516                                "unexpected cast transform task".to_string(),
1517                            ));
1518                        }
1519                    };
1520                    results.push(transform_fn(rebuilt)?);
1521                }
1522                FinishTask::List(expr, count) => {
1523                    let children = transform_pop_results(&mut results, count)?;
1524                    let rebuilt = match expr {
1525                        Expression::Function(mut function) => {
1526                            function.args = children;
1527                            Expression::Function(function)
1528                        }
1529                        Expression::Array(mut array) => {
1530                            array.expressions = children;
1531                            Expression::Array(array)
1532                        }
1533                        Expression::Tuple(mut tuple) => {
1534                            tuple.expressions = children;
1535                            Expression::Tuple(tuple)
1536                        }
1537                        Expression::ArrayFunc(mut array) => {
1538                            array.expressions = children;
1539                            Expression::ArrayFunc(array)
1540                        }
1541                        Expression::Coalesce(mut func) => {
1542                            func.expressions = children;
1543                            Expression::Coalesce(func)
1544                        }
1545                        Expression::Greatest(mut func) => {
1546                            func.expressions = children;
1547                            Expression::Greatest(func)
1548                        }
1549                        Expression::Least(mut func) => {
1550                            func.expressions = children;
1551                            Expression::Least(func)
1552                        }
1553                        Expression::ArrayConcat(mut func) => {
1554                            func.expressions = children;
1555                            Expression::ArrayConcat(func)
1556                        }
1557                        Expression::ArrayIntersect(mut func) => {
1558                            func.expressions = children;
1559                            Expression::ArrayIntersect(func)
1560                        }
1561                        Expression::ArrayZip(mut func) => {
1562                            func.expressions = children;
1563                            Expression::ArrayZip(func)
1564                        }
1565                        Expression::MapConcat(mut func) => {
1566                            func.expressions = children;
1567                            Expression::MapConcat(func)
1568                        }
1569                        Expression::JsonArray(mut func) => {
1570                            func.expressions = children;
1571                            Expression::JsonArray(func)
1572                        }
1573                        _ => {
1574                            return Err(crate::error::Error::Internal(
1575                                "unexpected list transform task".to_string(),
1576                            ));
1577                        }
1578                    };
1579                    results.push(transform_fn(rebuilt)?);
1580                }
1581                FinishTask::From(mut from, count) => {
1582                    from.expressions = transform_pop_results(&mut results, count)?;
1583                    results.push(transform_fn(Expression::From(Box::new(from)))?);
1584                }
1585                FinishTask::Select(frame) => {
1586                    let mut select = *frame.select;
1587
1588                    if frame.qualify_present {
1589                        if let Some(ref mut qualify) = select.qualify {
1590                            qualify.this = transform_pop_result(&mut results)?;
1591                        }
1592                    }
1593                    if frame.having_present {
1594                        if let Some(ref mut having) = select.having {
1595                            having.this = transform_pop_result(&mut results)?;
1596                        }
1597                    }
1598                    if frame.group_by_count > 0 {
1599                        if let Some(ref mut group_by) = select.group_by {
1600                            group_by.expressions =
1601                                transform_pop_results(&mut results, frame.group_by_count)?;
1602                        }
1603                    }
1604                    if frame.where_present {
1605                        if let Some(ref mut where_clause) = select.where_clause {
1606                            where_clause.this = transform_pop_result(&mut results)?;
1607                        }
1608                    }
1609                    if frame.from_present {
1610                        match transform_pop_result(&mut results)? {
1611                            Expression::From(from) => {
1612                                select.from = Some(*from);
1613                            }
1614                            _ => {
1615                                return Err(crate::error::Error::Internal(
1616                                    "expected FROM expression result".to_string(),
1617                                ));
1618                            }
1619                        }
1620                    }
1621                    select.expressions = transform_pop_results(&mut results, frame.expr_count)?;
1622
1623                    select.joins = select
1624                        .joins
1625                        .into_iter()
1626                        .map(|mut join| {
1627                            join.this = transform_recursive(join.this, transform_fn)?;
1628                            if let Some(on) = join.on.take() {
1629                                join.on = Some(transform_recursive(on, transform_fn)?);
1630                            }
1631                            match transform_fn(Expression::Join(Box::new(join)))? {
1632                                Expression::Join(j) => Ok(*j),
1633                                _ => Err(crate::error::Error::parse(
1634                                    "Join transformation returned non-join expression",
1635                                    0,
1636                                    0,
1637                                    0,
1638                                    0,
1639                                )),
1640                            }
1641                        })
1642                        .collect::<Result<Vec<_>>>()?;
1643
1644                    select.lateral_views = select
1645                        .lateral_views
1646                        .into_iter()
1647                        .map(|mut lv| {
1648                            lv.this = transform_recursive(lv.this, transform_fn)?;
1649                            Ok(lv)
1650                        })
1651                        .collect::<Result<Vec<_>>>()?;
1652
1653                    if let Some(mut with) = select.with.take() {
1654                        with.ctes = with
1655                            .ctes
1656                            .into_iter()
1657                            .map(|mut cte| {
1658                                let original = cte.this.clone();
1659                                cte.this =
1660                                    transform_recursive(cte.this, transform_fn).unwrap_or(original);
1661                                cte
1662                            })
1663                            .collect();
1664                        select.with = Some(with);
1665                    }
1666
1667                    if let Some(mut order) = select.order_by.take() {
1668                        order.expressions = order
1669                            .expressions
1670                            .into_iter()
1671                            .map(|o| {
1672                                let mut o = o;
1673                                let original = o.this.clone();
1674                                o.this =
1675                                    transform_recursive(o.this, transform_fn).unwrap_or(original);
1676                                match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1677                                    Ok(Expression::Ordered(transformed)) => *transformed,
1678                                    Ok(_) | Err(_) => o,
1679                                }
1680                            })
1681                            .collect();
1682                        select.order_by = Some(order);
1683                    }
1684
1685                    if let Some(ref mut windows) = select.windows {
1686                        for nw in windows.iter_mut() {
1687                            nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1688                                .into_iter()
1689                                .map(|o| {
1690                                    let mut o = o;
1691                                    let original = o.this.clone();
1692                                    o.this = transform_recursive(o.this, transform_fn)
1693                                        .unwrap_or(original);
1694                                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1695                                        Ok(Expression::Ordered(transformed)) => *transformed,
1696                                        Ok(_) | Err(_) => o,
1697                                    }
1698                                })
1699                                .collect();
1700                        }
1701                    }
1702
1703                    results.push(transform_fn(Expression::Select(Box::new(select)))?);
1704                }
1705                FinishTask::SetOp(expr) => {
1706                    let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1707                    let left = children.next().expect("left child");
1708                    let right = children.next().expect("right child");
1709
1710                    let rebuilt = match expr {
1711                        Expression::Union(mut union) => {
1712                            union.left = left;
1713                            union.right = right;
1714                            if let Some(mut order) = union.order_by.take() {
1715                                order.expressions = order
1716                                    .expressions
1717                                    .into_iter()
1718                                    .map(|o| {
1719                                        let mut o = o;
1720                                        let original = o.this.clone();
1721                                        o.this = transform_recursive(o.this, transform_fn)
1722                                            .unwrap_or(original);
1723                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1724                                        {
1725                                            Ok(Expression::Ordered(transformed)) => *transformed,
1726                                            Ok(_) | Err(_) => o,
1727                                        }
1728                                    })
1729                                    .collect();
1730                                union.order_by = Some(order);
1731                            }
1732                            if let Some(mut with) = union.with.take() {
1733                                with.ctes = with
1734                                    .ctes
1735                                    .into_iter()
1736                                    .map(|mut cte| {
1737                                        let original = cte.this.clone();
1738                                        cte.this = transform_recursive(cte.this, transform_fn)
1739                                            .unwrap_or(original);
1740                                        cte
1741                                    })
1742                                    .collect();
1743                                union.with = Some(with);
1744                            }
1745                            Expression::Union(union)
1746                        }
1747                        Expression::Intersect(mut intersect) => {
1748                            intersect.left = left;
1749                            intersect.right = right;
1750                            if let Some(mut order) = intersect.order_by.take() {
1751                                order.expressions = order
1752                                    .expressions
1753                                    .into_iter()
1754                                    .map(|o| {
1755                                        let mut o = o;
1756                                        let original = o.this.clone();
1757                                        o.this = transform_recursive(o.this, transform_fn)
1758                                            .unwrap_or(original);
1759                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1760                                        {
1761                                            Ok(Expression::Ordered(transformed)) => *transformed,
1762                                            Ok(_) | Err(_) => o,
1763                                        }
1764                                    })
1765                                    .collect();
1766                                intersect.order_by = Some(order);
1767                            }
1768                            if let Some(mut with) = intersect.with.take() {
1769                                with.ctes = with
1770                                    .ctes
1771                                    .into_iter()
1772                                    .map(|mut cte| {
1773                                        let original = cte.this.clone();
1774                                        cte.this = transform_recursive(cte.this, transform_fn)
1775                                            .unwrap_or(original);
1776                                        cte
1777                                    })
1778                                    .collect();
1779                                intersect.with = Some(with);
1780                            }
1781                            Expression::Intersect(intersect)
1782                        }
1783                        Expression::Except(mut except) => {
1784                            except.left = left;
1785                            except.right = right;
1786                            if let Some(mut order) = except.order_by.take() {
1787                                order.expressions = order
1788                                    .expressions
1789                                    .into_iter()
1790                                    .map(|o| {
1791                                        let mut o = o;
1792                                        let original = o.this.clone();
1793                                        o.this = transform_recursive(o.this, transform_fn)
1794                                            .unwrap_or(original);
1795                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1796                                        {
1797                                            Ok(Expression::Ordered(transformed)) => *transformed,
1798                                            Ok(_) | Err(_) => o,
1799                                        }
1800                                    })
1801                                    .collect();
1802                                except.order_by = Some(order);
1803                            }
1804                            if let Some(mut with) = except.with.take() {
1805                                with.ctes = with
1806                                    .ctes
1807                                    .into_iter()
1808                                    .map(|mut cte| {
1809                                        let original = cte.this.clone();
1810                                        cte.this = transform_recursive(cte.this, transform_fn)
1811                                            .unwrap_or(original);
1812                                        cte
1813                                    })
1814                                    .collect();
1815                                except.with = Some(with);
1816                            }
1817                            Expression::Except(except)
1818                        }
1819                        _ => {
1820                            return Err(crate::error::Error::Internal(
1821                                "unexpected set-op transform task".to_string(),
1822                            ));
1823                        }
1824                    };
1825                    results.push(transform_fn(rebuilt)?);
1826                }
1827            },
1828        }
1829    }
1830
1831    match results.len() {
1832        1 => Ok(results.pop().expect("single transform result")),
1833        _ => Err(crate::error::Error::Internal(
1834            "unexpected transform result stack size".to_string(),
1835        )),
1836    }
1837}
1838
1839#[cfg(any(
1840    feature = "transpile",
1841    feature = "ast-tools",
1842    feature = "generate",
1843    feature = "semantic"
1844))]
1845fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1846where
1847    F: Fn(Expression) -> Result<Expression>,
1848{
1849    match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1850        Expression::Table(table) => Ok(*table),
1851        _ => Err(crate::error::Error::parse(
1852            "TableRef transformation returned non-table expression",
1853            0,
1854            0,
1855            0,
1856            0,
1857        )),
1858    }
1859}
1860
1861#[cfg(any(
1862    feature = "transpile",
1863    feature = "ast-tools",
1864    feature = "generate",
1865    feature = "semantic"
1866))]
1867fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1868where
1869    F: Fn(Expression) -> Result<Expression>,
1870{
1871    match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1872        Expression::From(from) => Ok(*from),
1873        _ => Err(crate::error::Error::parse(
1874            "FROM transformation returned non-FROM expression",
1875            0,
1876            0,
1877            0,
1878            0,
1879        )),
1880    }
1881}
1882
1883#[cfg(any(
1884    feature = "transpile",
1885    feature = "ast-tools",
1886    feature = "generate",
1887    feature = "semantic"
1888))]
1889fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1890where
1891    F: Fn(Expression) -> Result<Expression>,
1892{
1893    join.this = transform_recursive(join.this, transform_fn)?;
1894    if let Some(on) = join.on.take() {
1895        join.on = Some(transform_recursive(on, transform_fn)?);
1896    }
1897    if let Some(match_condition) = join.match_condition.take() {
1898        join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1899    }
1900    join.pivots = join
1901        .pivots
1902        .into_iter()
1903        .map(|pivot| transform_recursive(pivot, transform_fn))
1904        .collect::<Result<Vec<_>>>()?;
1905
1906    match transform_fn(Expression::Join(Box::new(join)))? {
1907        Expression::Join(join) => Ok(*join),
1908        _ => Err(crate::error::Error::parse(
1909            "Join transformation returned non-join expression",
1910            0,
1911            0,
1912            0,
1913            0,
1914        )),
1915    }
1916}
1917
1918#[cfg(any(
1919    feature = "transpile",
1920    feature = "ast-tools",
1921    feature = "generate",
1922    feature = "semantic"
1923))]
1924fn transform_output_clause_recursive<F>(
1925    mut output: OutputClause,
1926    transform_fn: &F,
1927) -> Result<OutputClause>
1928where
1929    F: Fn(Expression) -> Result<Expression>,
1930{
1931    output.columns = output
1932        .columns
1933        .into_iter()
1934        .map(|column| transform_recursive(column, transform_fn))
1935        .collect::<Result<Vec<_>>>()?;
1936    if let Some(into_table) = output.into_table.take() {
1937        output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1938    }
1939    Ok(output)
1940}
1941
1942#[cfg(any(
1943    feature = "transpile",
1944    feature = "ast-tools",
1945    feature = "generate",
1946    feature = "semantic"
1947))]
1948fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1949where
1950    F: Fn(Expression) -> Result<Expression>,
1951{
1952    with.ctes = with
1953        .ctes
1954        .into_iter()
1955        .map(|mut cte| {
1956            cte.this = transform_recursive(cte.this, transform_fn)?;
1957            Ok(cte)
1958        })
1959        .collect::<Result<Vec<_>>>()?;
1960    if let Some(search) = with.search.take() {
1961        with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1962    }
1963    Ok(with)
1964}
1965
1966#[cfg(any(
1967    feature = "transpile",
1968    feature = "ast-tools",
1969    feature = "generate",
1970    feature = "semantic"
1971))]
1972fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1973where
1974    F: Fn(Expression) -> Result<Expression>,
1975{
1976    order.expressions = order
1977        .expressions
1978        .into_iter()
1979        .map(|mut ordered| {
1980            let original = ordered.this.clone();
1981            ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1982            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1983                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1984                Ok(_) | Err(_) => Ok(ordered),
1985            }
1986        })
1987        .collect::<Result<Vec<_>>>()?;
1988    Ok(order)
1989}
1990
1991#[cfg(any(
1992    feature = "transpile",
1993    feature = "ast-tools",
1994    feature = "generate",
1995    feature = "semantic"
1996))]
1997fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
1998where
1999    F: Fn(Expression) -> Result<Expression>,
2000{
2001    use crate::expressions::BinaryOp;
2002
2003    // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
2004    macro_rules! recurse_agg {
2005        ($variant:ident, $f:expr) => {{
2006            let mut f = $f;
2007            f.this = transform_recursive(f.this, transform_fn)?;
2008            if let Some(filter) = f.filter.take() {
2009                f.filter = Some(transform_recursive(filter, transform_fn)?);
2010            }
2011            for ord in &mut f.order_by {
2012                ord.this = transform_recursive(
2013                    std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
2014                    transform_fn,
2015                )?;
2016            }
2017            if let Some((ref mut expr, _)) = f.having_max {
2018                *expr = Box::new(transform_recursive(
2019                    std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
2020                    transform_fn,
2021                )?);
2022            }
2023            if let Some(limit) = f.limit.take() {
2024                f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
2025            }
2026            Expression::$variant(f)
2027        }};
2028    }
2029
2030    // Helper macro to transform binary ops with Box<BinaryOp>
2031    macro_rules! transform_binary {
2032        ($variant:ident, $op:expr) => {{
2033            let left = transform_recursive($op.left, transform_fn)?;
2034            let right = transform_recursive($op.right, transform_fn)?;
2035            Expression::$variant(Box::new(BinaryOp {
2036                left,
2037                right,
2038                left_comments: $op.left_comments,
2039                operator_comments: $op.operator_comments,
2040                trailing_comments: $op.trailing_comments,
2041                inferred_type: $op.inferred_type,
2042            }))
2043        }};
2044    }
2045
2046    // Fast path: leaf nodes never need child traversal, apply transform directly
2047    if matches!(
2048        &expr,
2049        Expression::Literal(_)
2050            | Expression::Boolean(_)
2051            | Expression::Null(_)
2052            | Expression::Identifier(_)
2053            | Expression::Star(_)
2054            | Expression::Parameter(_)
2055            | Expression::Placeholder(_)
2056            | Expression::SessionParameter(_)
2057    ) {
2058        return transform_fn(expr);
2059    }
2060
2061    // First recursively transform children, then apply the transform function
2062    let expr = match expr {
2063        Expression::Select(mut select) => {
2064            select.expressions = select
2065                .expressions
2066                .into_iter()
2067                .map(|e| transform_recursive(e, transform_fn))
2068                .collect::<Result<Vec<_>>>()?;
2069
2070            // Transform FROM clause
2071            if let Some(mut from) = select.from.take() {
2072                from.expressions = from
2073                    .expressions
2074                    .into_iter()
2075                    .map(|e| transform_recursive(e, transform_fn))
2076                    .collect::<Result<Vec<_>>>()?;
2077                select.from = Some(from);
2078            }
2079
2080            // Transform JOINs - important for CROSS APPLY / LATERAL transformations
2081            select.joins = select
2082                .joins
2083                .into_iter()
2084                .map(|mut join| {
2085                    join.this = transform_recursive(join.this, transform_fn)?;
2086                    if let Some(on) = join.on.take() {
2087                        join.on = Some(transform_recursive(on, transform_fn)?);
2088                    }
2089                    // Wrap join in Expression::Join to allow transform_fn to transform it
2090                    match transform_fn(Expression::Join(Box::new(join)))? {
2091                        Expression::Join(j) => Ok(*j),
2092                        _ => Err(crate::error::Error::parse(
2093                            "Join transformation returned non-join expression",
2094                            0,
2095                            0,
2096                            0,
2097                            0,
2098                        )),
2099                    }
2100                })
2101                .collect::<Result<Vec<_>>>()?;
2102
2103            // Transform LATERAL VIEW expressions (Hive/Spark)
2104            select.lateral_views = select
2105                .lateral_views
2106                .into_iter()
2107                .map(|mut lv| {
2108                    lv.this = transform_recursive(lv.this, transform_fn)?;
2109                    Ok(lv)
2110                })
2111                .collect::<Result<Vec<_>>>()?;
2112
2113            // Transform WHERE clause
2114            if let Some(mut where_clause) = select.where_clause.take() {
2115                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2116                select.where_clause = Some(where_clause);
2117            }
2118
2119            // Transform GROUP BY
2120            if let Some(mut group_by) = select.group_by.take() {
2121                group_by.expressions = group_by
2122                    .expressions
2123                    .into_iter()
2124                    .map(|e| transform_recursive(e, transform_fn))
2125                    .collect::<Result<Vec<_>>>()?;
2126                select.group_by = Some(group_by);
2127            }
2128
2129            // Transform HAVING
2130            if let Some(mut having) = select.having.take() {
2131                having.this = transform_recursive(having.this, transform_fn)?;
2132                select.having = Some(having);
2133            }
2134
2135            // Transform WITH (CTEs)
2136            if let Some(mut with) = select.with.take() {
2137                with.ctes = with
2138                    .ctes
2139                    .into_iter()
2140                    .map(|mut cte| {
2141                        let original = cte.this.clone();
2142                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2143                        cte
2144                    })
2145                    .collect();
2146                select.with = Some(with);
2147            }
2148
2149            // Transform ORDER BY
2150            if let Some(mut order) = select.order_by.take() {
2151                order.expressions = order
2152                    .expressions
2153                    .into_iter()
2154                    .map(|o| {
2155                        let mut o = o;
2156                        let original = o.this.clone();
2157                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2158                        // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
2159                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2160                            Ok(Expression::Ordered(transformed)) => *transformed,
2161                            Ok(_) | Err(_) => o,
2162                        }
2163                    })
2164                    .collect();
2165                select.order_by = Some(order);
2166            }
2167
2168            // Transform WINDOW clause order_by
2169            if let Some(ref mut windows) = select.windows {
2170                for nw in windows.iter_mut() {
2171                    nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
2172                        .into_iter()
2173                        .map(|o| {
2174                            let mut o = o;
2175                            let original = o.this.clone();
2176                            o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2177                            match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2178                                Ok(Expression::Ordered(transformed)) => *transformed,
2179                                Ok(_) | Err(_) => o,
2180                            }
2181                        })
2182                        .collect();
2183                }
2184            }
2185
2186            // Transform QUALIFY
2187            if let Some(mut qual) = select.qualify.take() {
2188                qual.this = transform_recursive(qual.this, transform_fn)?;
2189                select.qualify = Some(qual);
2190            }
2191
2192            Expression::Select(select)
2193        }
2194        Expression::Function(mut f) => {
2195            f.args = f
2196                .args
2197                .into_iter()
2198                .map(|e| transform_recursive(e, transform_fn))
2199                .collect::<Result<Vec<_>>>()?;
2200            Expression::Function(f)
2201        }
2202        Expression::AggregateFunction(mut f) => {
2203            f.args = f
2204                .args
2205                .into_iter()
2206                .map(|e| transform_recursive(e, transform_fn))
2207                .collect::<Result<Vec<_>>>()?;
2208            if let Some(filter) = f.filter {
2209                f.filter = Some(transform_recursive(filter, transform_fn)?);
2210            }
2211            Expression::AggregateFunction(f)
2212        }
2213        Expression::WindowFunction(mut wf) => {
2214            wf.this = transform_recursive(wf.this, transform_fn)?;
2215            wf.over.partition_by = wf
2216                .over
2217                .partition_by
2218                .into_iter()
2219                .map(|e| transform_recursive(e, transform_fn))
2220                .collect::<Result<Vec<_>>>()?;
2221            // Transform order_by items through Expression::Ordered wrapper
2222            wf.over.order_by = wf
2223                .over
2224                .order_by
2225                .into_iter()
2226                .map(|o| {
2227                    let mut o = o;
2228                    o.this = transform_recursive(o.this, transform_fn)?;
2229                    match transform_fn(Expression::Ordered(Box::new(o)))? {
2230                        Expression::Ordered(transformed) => Ok(*transformed),
2231                        _ => Err(crate::error::Error::parse(
2232                            "Ordered transformation returned non-Ordered expression",
2233                            0,
2234                            0,
2235                            0,
2236                            0,
2237                        )),
2238                    }
2239                })
2240                .collect::<Result<Vec<_>>>()?;
2241            Expression::WindowFunction(wf)
2242        }
2243        Expression::Alias(mut a) => {
2244            a.this = transform_recursive(a.this, transform_fn)?;
2245            Expression::Alias(a)
2246        }
2247        Expression::Cast(mut c) => {
2248            c.this = transform_recursive(c.this, transform_fn)?;
2249            // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
2250            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2251            Expression::Cast(c)
2252        }
2253        Expression::And(op) => transform_binary!(And, *op),
2254        Expression::Or(op) => transform_binary!(Or, *op),
2255        Expression::Add(op) => transform_binary!(Add, *op),
2256        Expression::Sub(op) => transform_binary!(Sub, *op),
2257        Expression::Mul(op) => transform_binary!(Mul, *op),
2258        Expression::Div(op) => transform_binary!(Div, *op),
2259        Expression::Eq(op) => transform_binary!(Eq, *op),
2260        Expression::Lt(op) => transform_binary!(Lt, *op),
2261        Expression::Gt(op) => transform_binary!(Gt, *op),
2262        Expression::Paren(mut p) => {
2263            p.this = transform_recursive(p.this, transform_fn)?;
2264            Expression::Paren(p)
2265        }
2266        Expression::Coalesce(mut f) => {
2267            f.expressions = f
2268                .expressions
2269                .into_iter()
2270                .map(|e| transform_recursive(e, transform_fn))
2271                .collect::<Result<Vec<_>>>()?;
2272            Expression::Coalesce(f)
2273        }
2274        Expression::IfNull(mut f) => {
2275            f.this = transform_recursive(f.this, transform_fn)?;
2276            f.expression = transform_recursive(f.expression, transform_fn)?;
2277            Expression::IfNull(f)
2278        }
2279        Expression::Nvl(mut f) => {
2280            f.this = transform_recursive(f.this, transform_fn)?;
2281            f.expression = transform_recursive(f.expression, transform_fn)?;
2282            Expression::Nvl(f)
2283        }
2284        Expression::In(mut i) => {
2285            i.this = transform_recursive(i.this, transform_fn)?;
2286            i.expressions = i
2287                .expressions
2288                .into_iter()
2289                .map(|e| transform_recursive(e, transform_fn))
2290                .collect::<Result<Vec<_>>>()?;
2291            if let Some(query) = i.query {
2292                i.query = Some(transform_recursive(query, transform_fn)?);
2293            }
2294            Expression::In(i)
2295        }
2296        Expression::Not(mut n) => {
2297            n.this = transform_recursive(n.this, transform_fn)?;
2298            Expression::Not(n)
2299        }
2300        Expression::ArraySlice(mut s) => {
2301            s.this = transform_recursive(s.this, transform_fn)?;
2302            if let Some(start) = s.start {
2303                s.start = Some(transform_recursive(start, transform_fn)?);
2304            }
2305            if let Some(end) = s.end {
2306                s.end = Some(transform_recursive(end, transform_fn)?);
2307            }
2308            Expression::ArraySlice(s)
2309        }
2310        Expression::Subscript(mut s) => {
2311            s.this = transform_recursive(s.this, transform_fn)?;
2312            s.index = transform_recursive(s.index, transform_fn)?;
2313            Expression::Subscript(s)
2314        }
2315        Expression::Array(mut a) => {
2316            a.expressions = a
2317                .expressions
2318                .into_iter()
2319                .map(|e| transform_recursive(e, transform_fn))
2320                .collect::<Result<Vec<_>>>()?;
2321            Expression::Array(a)
2322        }
2323        Expression::Struct(mut s) => {
2324            let mut new_fields = Vec::new();
2325            for (name, expr) in s.fields {
2326                let transformed = transform_recursive(expr, transform_fn)?;
2327                new_fields.push((name, transformed));
2328            }
2329            s.fields = new_fields;
2330            Expression::Struct(s)
2331        }
2332        Expression::NamedArgument(mut na) => {
2333            na.value = transform_recursive(na.value, transform_fn)?;
2334            Expression::NamedArgument(na)
2335        }
2336        Expression::MapFunc(mut m) => {
2337            m.keys = m
2338                .keys
2339                .into_iter()
2340                .map(|e| transform_recursive(e, transform_fn))
2341                .collect::<Result<Vec<_>>>()?;
2342            m.values = m
2343                .values
2344                .into_iter()
2345                .map(|e| transform_recursive(e, transform_fn))
2346                .collect::<Result<Vec<_>>>()?;
2347            Expression::MapFunc(m)
2348        }
2349        Expression::ArrayFunc(mut a) => {
2350            a.expressions = a
2351                .expressions
2352                .into_iter()
2353                .map(|e| transform_recursive(e, transform_fn))
2354                .collect::<Result<Vec<_>>>()?;
2355            Expression::ArrayFunc(a)
2356        }
2357        Expression::Lambda(mut l) => {
2358            l.body = transform_recursive(l.body, transform_fn)?;
2359            Expression::Lambda(l)
2360        }
2361        Expression::JsonExtract(mut f) => {
2362            f.this = transform_recursive(f.this, transform_fn)?;
2363            f.path = transform_recursive(f.path, transform_fn)?;
2364            Expression::JsonExtract(f)
2365        }
2366        Expression::JsonExtractScalar(mut f) => {
2367            f.this = transform_recursive(f.this, transform_fn)?;
2368            f.path = transform_recursive(f.path, transform_fn)?;
2369            Expression::JsonExtractScalar(f)
2370        }
2371
2372        // ===== UnaryFunc-based expressions =====
2373        // These all have a single `this: Expression` child
2374        Expression::Length(mut f) => {
2375            f.this = transform_recursive(f.this, transform_fn)?;
2376            Expression::Length(f)
2377        }
2378        Expression::Upper(mut f) => {
2379            f.this = transform_recursive(f.this, transform_fn)?;
2380            Expression::Upper(f)
2381        }
2382        Expression::Lower(mut f) => {
2383            f.this = transform_recursive(f.this, transform_fn)?;
2384            Expression::Lower(f)
2385        }
2386        Expression::LTrim(mut f) => {
2387            f.this = transform_recursive(f.this, transform_fn)?;
2388            Expression::LTrim(f)
2389        }
2390        Expression::RTrim(mut f) => {
2391            f.this = transform_recursive(f.this, transform_fn)?;
2392            Expression::RTrim(f)
2393        }
2394        Expression::Reverse(mut f) => {
2395            f.this = transform_recursive(f.this, transform_fn)?;
2396            Expression::Reverse(f)
2397        }
2398        Expression::Abs(mut f) => {
2399            f.this = transform_recursive(f.this, transform_fn)?;
2400            Expression::Abs(f)
2401        }
2402        Expression::Ceil(mut f) => {
2403            f.this = transform_recursive(f.this, transform_fn)?;
2404            Expression::Ceil(f)
2405        }
2406        Expression::Floor(mut f) => {
2407            f.this = transform_recursive(f.this, transform_fn)?;
2408            Expression::Floor(f)
2409        }
2410        Expression::Sign(mut f) => {
2411            f.this = transform_recursive(f.this, transform_fn)?;
2412            Expression::Sign(f)
2413        }
2414        Expression::Sqrt(mut f) => {
2415            f.this = transform_recursive(f.this, transform_fn)?;
2416            Expression::Sqrt(f)
2417        }
2418        Expression::Cbrt(mut f) => {
2419            f.this = transform_recursive(f.this, transform_fn)?;
2420            Expression::Cbrt(f)
2421        }
2422        Expression::Ln(mut f) => {
2423            f.this = transform_recursive(f.this, transform_fn)?;
2424            Expression::Ln(f)
2425        }
2426        Expression::Log(mut f) => {
2427            f.this = transform_recursive(f.this, transform_fn)?;
2428            if let Some(base) = f.base {
2429                f.base = Some(transform_recursive(base, transform_fn)?);
2430            }
2431            Expression::Log(f)
2432        }
2433        Expression::Exp(mut f) => {
2434            f.this = transform_recursive(f.this, transform_fn)?;
2435            Expression::Exp(f)
2436        }
2437        Expression::Date(mut f) => {
2438            f.this = transform_recursive(f.this, transform_fn)?;
2439            Expression::Date(f)
2440        }
2441        Expression::Stddev(f) => recurse_agg!(Stddev, f),
2442        Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
2443        Expression::Variance(f) => recurse_agg!(Variance, f),
2444
2445        // ===== BinaryFunc-based expressions =====
2446        Expression::ModFunc(mut f) => {
2447            f.this = transform_recursive(f.this, transform_fn)?;
2448            f.expression = transform_recursive(f.expression, transform_fn)?;
2449            Expression::ModFunc(f)
2450        }
2451        Expression::Power(mut f) => {
2452            f.this = transform_recursive(f.this, transform_fn)?;
2453            f.expression = transform_recursive(f.expression, transform_fn)?;
2454            Expression::Power(f)
2455        }
2456        Expression::MapFromArrays(mut f) => {
2457            f.this = transform_recursive(f.this, transform_fn)?;
2458            f.expression = transform_recursive(f.expression, transform_fn)?;
2459            Expression::MapFromArrays(f)
2460        }
2461        Expression::ElementAt(mut f) => {
2462            f.this = transform_recursive(f.this, transform_fn)?;
2463            f.expression = transform_recursive(f.expression, transform_fn)?;
2464            Expression::ElementAt(f)
2465        }
2466        Expression::MapContainsKey(mut f) => {
2467            f.this = transform_recursive(f.this, transform_fn)?;
2468            f.expression = transform_recursive(f.expression, transform_fn)?;
2469            Expression::MapContainsKey(f)
2470        }
2471        Expression::Left(mut f) => {
2472            f.this = transform_recursive(f.this, transform_fn)?;
2473            f.length = transform_recursive(f.length, transform_fn)?;
2474            Expression::Left(f)
2475        }
2476        Expression::Right(mut f) => {
2477            f.this = transform_recursive(f.this, transform_fn)?;
2478            f.length = transform_recursive(f.length, transform_fn)?;
2479            Expression::Right(f)
2480        }
2481        Expression::Repeat(mut f) => {
2482            f.this = transform_recursive(f.this, transform_fn)?;
2483            f.times = transform_recursive(f.times, transform_fn)?;
2484            Expression::Repeat(f)
2485        }
2486
2487        // ===== Complex function expressions =====
2488        Expression::Substring(mut f) => {
2489            f.this = transform_recursive(f.this, transform_fn)?;
2490            f.start = transform_recursive(f.start, transform_fn)?;
2491            if let Some(len) = f.length {
2492                f.length = Some(transform_recursive(len, transform_fn)?);
2493            }
2494            Expression::Substring(f)
2495        }
2496        Expression::Replace(mut f) => {
2497            f.this = transform_recursive(f.this, transform_fn)?;
2498            f.old = transform_recursive(f.old, transform_fn)?;
2499            f.new = transform_recursive(f.new, transform_fn)?;
2500            Expression::Replace(f)
2501        }
2502        Expression::ConcatWs(mut f) => {
2503            f.separator = transform_recursive(f.separator, transform_fn)?;
2504            f.expressions = f
2505                .expressions
2506                .into_iter()
2507                .map(|e| transform_recursive(e, transform_fn))
2508                .collect::<Result<Vec<_>>>()?;
2509            Expression::ConcatWs(f)
2510        }
2511        Expression::Trim(mut f) => {
2512            f.this = transform_recursive(f.this, transform_fn)?;
2513            if let Some(chars) = f.characters {
2514                f.characters = Some(transform_recursive(chars, transform_fn)?);
2515            }
2516            Expression::Trim(f)
2517        }
2518        Expression::Split(mut f) => {
2519            f.this = transform_recursive(f.this, transform_fn)?;
2520            f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
2521            Expression::Split(f)
2522        }
2523        Expression::Lpad(mut f) => {
2524            f.this = transform_recursive(f.this, transform_fn)?;
2525            f.length = transform_recursive(f.length, transform_fn)?;
2526            if let Some(fill) = f.fill {
2527                f.fill = Some(transform_recursive(fill, transform_fn)?);
2528            }
2529            Expression::Lpad(f)
2530        }
2531        Expression::Rpad(mut f) => {
2532            f.this = transform_recursive(f.this, transform_fn)?;
2533            f.length = transform_recursive(f.length, transform_fn)?;
2534            if let Some(fill) = f.fill {
2535                f.fill = Some(transform_recursive(fill, transform_fn)?);
2536            }
2537            Expression::Rpad(f)
2538        }
2539
2540        // ===== Conditional expressions =====
2541        Expression::Case(mut c) => {
2542            if let Some(operand) = c.operand {
2543                c.operand = Some(transform_recursive(operand, transform_fn)?);
2544            }
2545            c.whens = c
2546                .whens
2547                .into_iter()
2548                .map(|(cond, then)| {
2549                    let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
2550                    let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
2551                    (new_cond, new_then)
2552                })
2553                .collect();
2554            if let Some(else_expr) = c.else_ {
2555                c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
2556            }
2557            Expression::Case(c)
2558        }
2559        Expression::IfFunc(mut f) => {
2560            f.condition = transform_recursive(f.condition, transform_fn)?;
2561            f.true_value = transform_recursive(f.true_value, transform_fn)?;
2562            if let Some(false_val) = f.false_value {
2563                f.false_value = Some(transform_recursive(false_val, transform_fn)?);
2564            }
2565            Expression::IfFunc(f)
2566        }
2567
2568        // ===== Date/Time expressions =====
2569        Expression::DateAdd(mut f) => {
2570            f.this = transform_recursive(f.this, transform_fn)?;
2571            f.interval = transform_recursive(f.interval, transform_fn)?;
2572            Expression::DateAdd(f)
2573        }
2574        Expression::DateSub(mut f) => {
2575            f.this = transform_recursive(f.this, transform_fn)?;
2576            f.interval = transform_recursive(f.interval, transform_fn)?;
2577            Expression::DateSub(f)
2578        }
2579        Expression::DateDiff(mut f) => {
2580            f.this = transform_recursive(f.this, transform_fn)?;
2581            f.expression = transform_recursive(f.expression, transform_fn)?;
2582            Expression::DateDiff(f)
2583        }
2584        Expression::DateTrunc(mut f) => {
2585            f.this = transform_recursive(f.this, transform_fn)?;
2586            Expression::DateTrunc(f)
2587        }
2588        Expression::Extract(mut f) => {
2589            f.this = transform_recursive(f.this, transform_fn)?;
2590            Expression::Extract(f)
2591        }
2592
2593        // ===== JSON expressions =====
2594        Expression::JsonObject(mut f) => {
2595            f.pairs = f
2596                .pairs
2597                .into_iter()
2598                .map(|(k, v)| {
2599                    let new_k = transform_recursive(k, transform_fn)?;
2600                    let new_v = transform_recursive(v, transform_fn)?;
2601                    Ok((new_k, new_v))
2602                })
2603                .collect::<Result<Vec<_>>>()?;
2604            Expression::JsonObject(f)
2605        }
2606
2607        // ===== Subquery expressions =====
2608        Expression::Subquery(mut s) => {
2609            s.this = transform_recursive(s.this, transform_fn)?;
2610            Expression::Subquery(s)
2611        }
2612        Expression::Exists(mut e) => {
2613            e.this = transform_recursive(e.this, transform_fn)?;
2614            Expression::Exists(e)
2615        }
2616        Expression::Describe(mut d) => {
2617            d.target = transform_recursive(d.target, transform_fn)?;
2618            Expression::Describe(d)
2619        }
2620
2621        // ===== Set operations =====
2622        Expression::Union(mut u) => {
2623            let left = std::mem::replace(&mut u.left, Expression::Null(Null));
2624            u.left = transform_recursive(left, transform_fn)?;
2625            let right = std::mem::replace(&mut u.right, Expression::Null(Null));
2626            u.right = transform_recursive(right, transform_fn)?;
2627            if let Some(mut order) = u.order_by.take() {
2628                order.expressions = order
2629                    .expressions
2630                    .into_iter()
2631                    .map(|o| {
2632                        let mut o = o;
2633                        let original = o.this.clone();
2634                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2635                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2636                            Ok(Expression::Ordered(transformed)) => *transformed,
2637                            Ok(_) | Err(_) => o,
2638                        }
2639                    })
2640                    .collect();
2641                u.order_by = Some(order);
2642            }
2643            if let Some(mut with) = u.with.take() {
2644                with.ctes = with
2645                    .ctes
2646                    .into_iter()
2647                    .map(|mut cte| {
2648                        let original = cte.this.clone();
2649                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2650                        cte
2651                    })
2652                    .collect();
2653                u.with = Some(with);
2654            }
2655            Expression::Union(u)
2656        }
2657        Expression::Intersect(mut i) => {
2658            let left = std::mem::replace(&mut i.left, Expression::Null(Null));
2659            i.left = transform_recursive(left, transform_fn)?;
2660            let right = std::mem::replace(&mut i.right, Expression::Null(Null));
2661            i.right = transform_recursive(right, transform_fn)?;
2662            if let Some(mut order) = i.order_by.take() {
2663                order.expressions = order
2664                    .expressions
2665                    .into_iter()
2666                    .map(|o| {
2667                        let mut o = o;
2668                        let original = o.this.clone();
2669                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2670                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2671                            Ok(Expression::Ordered(transformed)) => *transformed,
2672                            Ok(_) | Err(_) => o,
2673                        }
2674                    })
2675                    .collect();
2676                i.order_by = Some(order);
2677            }
2678            if let Some(mut with) = i.with.take() {
2679                with.ctes = with
2680                    .ctes
2681                    .into_iter()
2682                    .map(|mut cte| {
2683                        let original = cte.this.clone();
2684                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2685                        cte
2686                    })
2687                    .collect();
2688                i.with = Some(with);
2689            }
2690            Expression::Intersect(i)
2691        }
2692        Expression::Except(mut e) => {
2693            let left = std::mem::replace(&mut e.left, Expression::Null(Null));
2694            e.left = transform_recursive(left, transform_fn)?;
2695            let right = std::mem::replace(&mut e.right, Expression::Null(Null));
2696            e.right = transform_recursive(right, transform_fn)?;
2697            if let Some(mut order) = e.order_by.take() {
2698                order.expressions = order
2699                    .expressions
2700                    .into_iter()
2701                    .map(|o| {
2702                        let mut o = o;
2703                        let original = o.this.clone();
2704                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2705                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2706                            Ok(Expression::Ordered(transformed)) => *transformed,
2707                            Ok(_) | Err(_) => o,
2708                        }
2709                    })
2710                    .collect();
2711                e.order_by = Some(order);
2712            }
2713            if let Some(mut with) = e.with.take() {
2714                with.ctes = with
2715                    .ctes
2716                    .into_iter()
2717                    .map(|mut cte| {
2718                        let original = cte.this.clone();
2719                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2720                        cte
2721                    })
2722                    .collect();
2723                e.with = Some(with);
2724            }
2725            Expression::Except(e)
2726        }
2727
2728        // ===== DML expressions =====
2729        Expression::Insert(mut ins) => {
2730            // Transform VALUES clause expressions
2731            let mut new_values = Vec::new();
2732            for row in ins.values {
2733                let mut new_row = Vec::new();
2734                for e in row {
2735                    new_row.push(transform_recursive(e, transform_fn)?);
2736                }
2737                new_values.push(new_row);
2738            }
2739            ins.values = new_values;
2740
2741            // Transform query (for INSERT ... SELECT)
2742            if let Some(query) = ins.query {
2743                ins.query = Some(transform_recursive(query, transform_fn)?);
2744            }
2745
2746            // Transform RETURNING clause
2747            let mut new_returning = Vec::new();
2748            for e in ins.returning {
2749                new_returning.push(transform_recursive(e, transform_fn)?);
2750            }
2751            ins.returning = new_returning;
2752
2753            // Transform ON CONFLICT clause
2754            if let Some(on_conflict) = ins.on_conflict {
2755                ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
2756            }
2757
2758            Expression::Insert(ins)
2759        }
2760        Expression::Update(mut upd) => {
2761            upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
2762            upd.extra_tables = upd
2763                .extra_tables
2764                .into_iter()
2765                .map(|table| transform_table_ref_recursive(table, transform_fn))
2766                .collect::<Result<Vec<_>>>()?;
2767            upd.table_joins = upd
2768                .table_joins
2769                .into_iter()
2770                .map(|join| transform_join_recursive(join, transform_fn))
2771                .collect::<Result<Vec<_>>>()?;
2772            upd.set = upd
2773                .set
2774                .into_iter()
2775                .map(|(id, val)| {
2776                    let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
2777                    (id, new_val)
2778                })
2779                .collect();
2780            if let Some(from_clause) = upd.from_clause.take() {
2781                upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
2782            }
2783            upd.from_joins = upd
2784                .from_joins
2785                .into_iter()
2786                .map(|join| transform_join_recursive(join, transform_fn))
2787                .collect::<Result<Vec<_>>>()?;
2788            if let Some(mut where_clause) = upd.where_clause.take() {
2789                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2790                upd.where_clause = Some(where_clause);
2791            }
2792            upd.returning = upd
2793                .returning
2794                .into_iter()
2795                .map(|expr| transform_recursive(expr, transform_fn))
2796                .collect::<Result<Vec<_>>>()?;
2797            if let Some(output) = upd.output.take() {
2798                upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2799            }
2800            if let Some(with) = upd.with.take() {
2801                upd.with = Some(transform_with_recursive(with, transform_fn)?);
2802            }
2803            if let Some(limit) = upd.limit.take() {
2804                upd.limit = Some(transform_recursive(limit, transform_fn)?);
2805            }
2806            if let Some(order_by) = upd.order_by.take() {
2807                upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2808            }
2809            Expression::Update(upd)
2810        }
2811        Expression::Delete(mut del) => {
2812            del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2813            del.using = del
2814                .using
2815                .into_iter()
2816                .map(|table| transform_table_ref_recursive(table, transform_fn))
2817                .collect::<Result<Vec<_>>>()?;
2818            if let Some(mut where_clause) = del.where_clause.take() {
2819                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2820                del.where_clause = Some(where_clause);
2821            }
2822            if let Some(output) = del.output.take() {
2823                del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2824            }
2825            if let Some(with) = del.with.take() {
2826                del.with = Some(transform_with_recursive(with, transform_fn)?);
2827            }
2828            if let Some(limit) = del.limit.take() {
2829                del.limit = Some(transform_recursive(limit, transform_fn)?);
2830            }
2831            if let Some(order_by) = del.order_by.take() {
2832                del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2833            }
2834            del.returning = del
2835                .returning
2836                .into_iter()
2837                .map(|expr| transform_recursive(expr, transform_fn))
2838                .collect::<Result<Vec<_>>>()?;
2839            del.tables = del
2840                .tables
2841                .into_iter()
2842                .map(|table| transform_table_ref_recursive(table, transform_fn))
2843                .collect::<Result<Vec<_>>>()?;
2844            del.joins = del
2845                .joins
2846                .into_iter()
2847                .map(|join| transform_join_recursive(join, transform_fn))
2848                .collect::<Result<Vec<_>>>()?;
2849            Expression::Delete(del)
2850        }
2851
2852        // ===== CTE expressions =====
2853        Expression::With(mut w) => {
2854            w.ctes = w
2855                .ctes
2856                .into_iter()
2857                .map(|mut cte| {
2858                    let original = cte.this.clone();
2859                    cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2860                    cte
2861                })
2862                .collect();
2863            Expression::With(w)
2864        }
2865        Expression::Cte(mut c) => {
2866            c.this = transform_recursive(c.this, transform_fn)?;
2867            Expression::Cte(c)
2868        }
2869
2870        // ===== Order expressions =====
2871        Expression::Ordered(mut o) => {
2872            o.this = transform_recursive(o.this, transform_fn)?;
2873            Expression::Ordered(o)
2874        }
2875
2876        // ===== Negation =====
2877        Expression::Neg(mut n) => {
2878            n.this = transform_recursive(n.this, transform_fn)?;
2879            Expression::Neg(n)
2880        }
2881
2882        // ===== Between =====
2883        Expression::Between(mut b) => {
2884            b.this = transform_recursive(b.this, transform_fn)?;
2885            b.low = transform_recursive(b.low, transform_fn)?;
2886            b.high = transform_recursive(b.high, transform_fn)?;
2887            Expression::Between(b)
2888        }
2889        Expression::IsNull(mut i) => {
2890            i.this = transform_recursive(i.this, transform_fn)?;
2891            Expression::IsNull(i)
2892        }
2893        Expression::IsTrue(mut i) => {
2894            i.this = transform_recursive(i.this, transform_fn)?;
2895            Expression::IsTrue(i)
2896        }
2897        Expression::IsFalse(mut i) => {
2898            i.this = transform_recursive(i.this, transform_fn)?;
2899            Expression::IsFalse(i)
2900        }
2901
2902        // ===== Like expressions =====
2903        Expression::Like(mut l) => {
2904            l.left = transform_recursive(l.left, transform_fn)?;
2905            l.right = transform_recursive(l.right, transform_fn)?;
2906            Expression::Like(l)
2907        }
2908        Expression::ILike(mut l) => {
2909            l.left = transform_recursive(l.left, transform_fn)?;
2910            l.right = transform_recursive(l.right, transform_fn)?;
2911            Expression::ILike(l)
2912        }
2913
2914        // ===== Additional binary ops not covered by macro =====
2915        Expression::Neq(op) => transform_binary!(Neq, *op),
2916        Expression::Lte(op) => transform_binary!(Lte, *op),
2917        Expression::Gte(op) => transform_binary!(Gte, *op),
2918        Expression::Mod(op) => transform_binary!(Mod, *op),
2919        Expression::Concat(op) => transform_binary!(Concat, *op),
2920        Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2921        Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2922        Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2923        Expression::Is(op) => transform_binary!(Is, *op),
2924
2925        // ===== TryCast / SafeCast =====
2926        Expression::TryCast(mut c) => {
2927            c.this = transform_recursive(c.this, transform_fn)?;
2928            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2929            Expression::TryCast(c)
2930        }
2931        Expression::SafeCast(mut c) => {
2932            c.this = transform_recursive(c.this, transform_fn)?;
2933            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2934            Expression::SafeCast(c)
2935        }
2936
2937        // ===== Misc =====
2938        Expression::Unnest(mut f) => {
2939            f.this = transform_recursive(f.this, transform_fn)?;
2940            f.expressions = f
2941                .expressions
2942                .into_iter()
2943                .map(|e| transform_recursive(e, transform_fn))
2944                .collect::<Result<Vec<_>>>()?;
2945            Expression::Unnest(f)
2946        }
2947        Expression::Explode(mut f) => {
2948            f.this = transform_recursive(f.this, transform_fn)?;
2949            Expression::Explode(f)
2950        }
2951        Expression::GroupConcat(mut f) => {
2952            f.this = transform_recursive(f.this, transform_fn)?;
2953            Expression::GroupConcat(f)
2954        }
2955        Expression::StringAgg(mut f) => {
2956            f.this = transform_recursive(f.this, transform_fn)?;
2957            if let Some(order_by) = f.order_by.take() {
2958                f.order_by = Some(
2959                    order_by
2960                        .into_iter()
2961                        .map(|mut ordered| {
2962                            let original = ordered.this.clone();
2963                            ordered.this =
2964                                transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2965                            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2966                                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2967                                Ok(_) | Err(_) => Ok(ordered),
2968                            }
2969                        })
2970                        .collect::<Result<Vec<_>>>()?,
2971                );
2972            }
2973            Expression::StringAgg(f)
2974        }
2975        Expression::ListAgg(mut f) => {
2976            f.this = transform_recursive(f.this, transform_fn)?;
2977            Expression::ListAgg(f)
2978        }
2979        Expression::ArrayAgg(mut f) => {
2980            f.this = transform_recursive(f.this, transform_fn)?;
2981            Expression::ArrayAgg(f)
2982        }
2983        Expression::ParseJson(mut f) => {
2984            f.this = transform_recursive(f.this, transform_fn)?;
2985            Expression::ParseJson(f)
2986        }
2987        Expression::ToJson(mut f) => {
2988            f.this = transform_recursive(f.this, transform_fn)?;
2989            Expression::ToJson(f)
2990        }
2991        Expression::JSONExtract(mut e) => {
2992            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2993            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2994            Expression::JSONExtract(e)
2995        }
2996        Expression::JSONExtractScalar(mut e) => {
2997            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2998            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2999            Expression::JSONExtractScalar(e)
3000        }
3001
3002        // StrToTime: recurse into this
3003        Expression::StrToTime(mut e) => {
3004            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3005            Expression::StrToTime(e)
3006        }
3007
3008        // UnixToTime: recurse into this
3009        Expression::UnixToTime(mut e) => {
3010            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3011            Expression::UnixToTime(e)
3012        }
3013
3014        // CreateTable: recurse into column defaults, on_update expressions, and data types
3015        Expression::CreateTable(mut ct) => {
3016            for col in &mut ct.columns {
3017                if let Some(default_expr) = col.default.take() {
3018                    col.default = Some(transform_recursive(default_expr, transform_fn)?);
3019                }
3020                if let Some(on_update_expr) = col.on_update.take() {
3021                    col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
3022                }
3023                // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
3024                // are NOT applied here because per-dialect transforms are designed for CAST/expression
3025                // contexts and may not produce correct results for DDL column definitions.
3026                // The DDL type mappings would need dedicated handling per source/target pair.
3027            }
3028            if let Some(as_select) = ct.as_select.take() {
3029                ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
3030            }
3031            Expression::CreateTable(ct)
3032        }
3033
3034        // CreateView: recurse into the view body query
3035        Expression::CreateView(mut cv) => {
3036            cv.query = transform_recursive(cv.query, transform_fn)?;
3037            Expression::CreateView(cv)
3038        }
3039
3040        // CreateTask: recurse into the task body
3041        Expression::CreateTask(mut ct) => {
3042            ct.body = transform_recursive(ct.body, transform_fn)?;
3043            Expression::CreateTask(ct)
3044        }
3045
3046        // Prepare: recurse into the prepared statement body
3047        Expression::Prepare(mut prepare) => {
3048            prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
3049            Expression::Prepare(prepare)
3050        }
3051
3052        // Execute: recurse into procedure/prepared name and argument values
3053        Expression::Execute(mut execute) => {
3054            execute.this = transform_recursive(execute.this, transform_fn)?;
3055            execute.arguments = execute
3056                .arguments
3057                .into_iter()
3058                .map(|argument| transform_recursive(argument, transform_fn))
3059                .collect::<Result<Vec<_>>>()?;
3060            execute.parameters = execute
3061                .parameters
3062                .into_iter()
3063                .map(|mut parameter| {
3064                    parameter.value = transform_recursive(parameter.value, transform_fn)?;
3065                    Ok(parameter)
3066                })
3067                .collect::<Result<Vec<_>>>()?;
3068            Expression::Execute(execute)
3069        }
3070
3071        // CreateProcedure: recurse into body expressions
3072        Expression::CreateProcedure(mut cp) => {
3073            if let Some(body) = cp.body.take() {
3074                cp.body = Some(match body {
3075                    FunctionBody::Expression(expr) => {
3076                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3077                    }
3078                    FunctionBody::Return(expr) => {
3079                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3080                    }
3081                    FunctionBody::Statements(stmts) => {
3082                        let transformed_stmts = stmts
3083                            .into_iter()
3084                            .map(|s| transform_recursive(s, transform_fn))
3085                            .collect::<Result<Vec<_>>>()?;
3086                        FunctionBody::Statements(transformed_stmts)
3087                    }
3088                    other => other,
3089                });
3090            }
3091            Expression::CreateProcedure(cp)
3092        }
3093
3094        // CreateFunction: recurse into body expressions
3095        Expression::CreateFunction(mut cf) => {
3096            if let Some(body) = cf.body.take() {
3097                cf.body = Some(match body {
3098                    FunctionBody::Expression(expr) => {
3099                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3100                    }
3101                    FunctionBody::Return(expr) => {
3102                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3103                    }
3104                    FunctionBody::Statements(stmts) => {
3105                        let transformed_stmts = stmts
3106                            .into_iter()
3107                            .map(|s| transform_recursive(s, transform_fn))
3108                            .collect::<Result<Vec<_>>>()?;
3109                        FunctionBody::Statements(transformed_stmts)
3110                    }
3111                    other => other,
3112                });
3113            }
3114            Expression::CreateFunction(cf)
3115        }
3116
3117        // MemberOf: recurse into left and right operands
3118        Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
3119        // ArrayContainsAll (@>): recurse into left and right operands
3120        Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
3121        // ArrayContainedBy (<@): recurse into left and right operands
3122        Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
3123        // ArrayOverlaps (&&): recurse into left and right operands
3124        Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
3125        // TsMatch (@@): recurse into left and right operands
3126        Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
3127        // Adjacent (-|-): recurse into left and right operands
3128        Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
3129
3130        // Table: recurse into when (HistoricalData) and changes fields
3131        Expression::Table(mut t) => {
3132            if let Some(when) = t.when.take() {
3133                let transformed =
3134                    transform_recursive(Expression::HistoricalData(when), transform_fn)?;
3135                if let Expression::HistoricalData(hd) = transformed {
3136                    t.when = Some(hd);
3137                }
3138            }
3139            if let Some(changes) = t.changes.take() {
3140                let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
3141                if let Expression::Changes(c) = transformed {
3142                    t.changes = Some(c);
3143                }
3144            }
3145            Expression::Table(t)
3146        }
3147
3148        // HistoricalData (Snowflake time travel): recurse into expression
3149        Expression::HistoricalData(mut hd) => {
3150            *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
3151            Expression::HistoricalData(hd)
3152        }
3153
3154        // Changes (Snowflake CHANGES clause): recurse into at_before and end
3155        Expression::Changes(mut c) => {
3156            if let Some(at_before) = c.at_before.take() {
3157                c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
3158            }
3159            if let Some(end) = c.end.take() {
3160                c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
3161            }
3162            Expression::Changes(c)
3163        }
3164
3165        // TableArgument: TABLE(expr) or MODEL(expr)
3166        Expression::TableArgument(mut ta) => {
3167            ta.this = transform_recursive(ta.this, transform_fn)?;
3168            Expression::TableArgument(ta)
3169        }
3170
3171        // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
3172        Expression::JoinedTable(mut jt) => {
3173            jt.left = transform_recursive(jt.left, transform_fn)?;
3174            jt.joins = jt
3175                .joins
3176                .into_iter()
3177                .map(|mut join| {
3178                    join.this = transform_recursive(join.this, transform_fn)?;
3179                    if let Some(on) = join.on.take() {
3180                        join.on = Some(transform_recursive(on, transform_fn)?);
3181                    }
3182                    match transform_fn(Expression::Join(Box::new(join)))? {
3183                        Expression::Join(j) => Ok(*j),
3184                        _ => Err(crate::error::Error::parse(
3185                            "Join transformation returned non-join expression",
3186                            0,
3187                            0,
3188                            0,
3189                            0,
3190                        )),
3191                    }
3192                })
3193                .collect::<Result<Vec<_>>>()?;
3194            jt.lateral_views = jt
3195                .lateral_views
3196                .into_iter()
3197                .map(|mut lv| {
3198                    lv.this = transform_recursive(lv.this, transform_fn)?;
3199                    Ok(lv)
3200                })
3201                .collect::<Result<Vec<_>>>()?;
3202            Expression::JoinedTable(jt)
3203        }
3204
3205        // Lateral: LATERAL func() - recurse into the function expression
3206        Expression::Lateral(mut lat) => {
3207            *lat.this = transform_recursive(*lat.this, transform_fn)?;
3208            Expression::Lateral(lat)
3209        }
3210
3211        // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
3212        // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
3213        // as a unit together with the WithinGroup wrapper
3214        Expression::WithinGroup(mut wg) => {
3215            wg.order_by = wg
3216                .order_by
3217                .into_iter()
3218                .map(|mut o| {
3219                    let original = o.this.clone();
3220                    o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
3221                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
3222                        Ok(Expression::Ordered(transformed)) => *transformed,
3223                        Ok(_) | Err(_) => o,
3224                    }
3225                })
3226                .collect();
3227            Expression::WithinGroup(wg)
3228        }
3229
3230        // Filter: recurse into both the aggregate and the filter condition
3231        Expression::Filter(mut f) => {
3232            f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
3233            f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
3234            Expression::Filter(f)
3235        }
3236
3237        // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
3238        // filter, order_by, having_max, and limit.
3239        // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
3240        Expression::Sum(f) => recurse_agg!(Sum, f),
3241        Expression::Avg(f) => recurse_agg!(Avg, f),
3242        Expression::Min(f) => recurse_agg!(Min, f),
3243        Expression::Max(f) => recurse_agg!(Max, f),
3244        Expression::CountIf(f) => recurse_agg!(CountIf, f),
3245        Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
3246        Expression::VarPop(f) => recurse_agg!(VarPop, f),
3247        Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
3248        Expression::Median(f) => recurse_agg!(Median, f),
3249        Expression::Mode(f) => recurse_agg!(Mode, f),
3250        Expression::First(f) => recurse_agg!(First, f),
3251        Expression::Last(f) => recurse_agg!(Last, f),
3252        Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
3253        Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
3254        Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
3255        Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
3256        Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
3257        Expression::Skewness(f) => recurse_agg!(Skewness, f),
3258        Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
3259        Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
3260        Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
3261        Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
3262        Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
3263        Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
3264
3265        // Count has its own struct with an Option<Expression> `this` field
3266        Expression::Count(mut c) => {
3267            if let Some(this) = c.this.take() {
3268                c.this = Some(transform_recursive(this, transform_fn)?);
3269            }
3270            if let Some(filter) = c.filter.take() {
3271                c.filter = Some(transform_recursive(filter, transform_fn)?);
3272            }
3273            Expression::Count(c)
3274        }
3275
3276        Expression::PipeOperator(mut pipe) => {
3277            pipe.this = transform_recursive(pipe.this, transform_fn)?;
3278            pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
3279            Expression::PipeOperator(pipe)
3280        }
3281
3282        // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
3283        Expression::ArrayExcept(mut f) => {
3284            f.this = transform_recursive(f.this, transform_fn)?;
3285            f.expression = transform_recursive(f.expression, transform_fn)?;
3286            Expression::ArrayExcept(f)
3287        }
3288        Expression::ArrayContains(mut f) => {
3289            f.this = transform_recursive(f.this, transform_fn)?;
3290            f.expression = transform_recursive(f.expression, transform_fn)?;
3291            Expression::ArrayContains(f)
3292        }
3293        Expression::ArrayDistinct(mut f) => {
3294            f.this = transform_recursive(f.this, transform_fn)?;
3295            Expression::ArrayDistinct(f)
3296        }
3297        Expression::ArrayPosition(mut f) => {
3298            f.this = transform_recursive(f.this, transform_fn)?;
3299            f.expression = transform_recursive(f.expression, transform_fn)?;
3300            Expression::ArrayPosition(f)
3301        }
3302
3303        // Pass through leaf nodes unchanged
3304        other => other,
3305    };
3306
3307    // Then apply the transform function
3308    transform_fn(expr)
3309}
3310
3311/// Returns the tokenizer config, generator config, and expression transform closure
3312/// for a built-in dialect type. This is the shared implementation used by both
3313/// `Dialect::get()` and custom dialect construction.
3314// ---------------------------------------------------------------------------
3315// Cached dialect configurations
3316// ---------------------------------------------------------------------------
3317
3318/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
3319/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
3320struct CachedDialectConfig {
3321    tokenizer_config: TokenizerConfig,
3322    #[cfg(feature = "generate")]
3323    generator_config: Arc<GeneratorConfig>,
3324}
3325
3326struct DialectConfigs {
3327    tokenizer_config: TokenizerConfig,
3328    #[cfg(feature = "generate")]
3329    generator_config: Arc<GeneratorConfig>,
3330    #[cfg(feature = "transpile")]
3331    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3332}
3333
3334/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
3335macro_rules! cached_dialect {
3336    ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
3337        #[cfg(feature = $feature)]
3338        static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3339            let d = $dialect_struct;
3340            CachedDialectConfig {
3341                tokenizer_config: d.tokenizer_config(),
3342                #[cfg(feature = "generate")]
3343                generator_config: Arc::new(d.generator_config()),
3344            }
3345        });
3346    };
3347}
3348
3349static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3350    let d = GenericDialect;
3351    CachedDialectConfig {
3352        tokenizer_config: d.tokenizer_config(),
3353        #[cfg(feature = "generate")]
3354        generator_config: Arc::new(d.generator_config()),
3355    }
3356});
3357
3358cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
3359cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
3360cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
3361cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
3362cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
3363cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
3364cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
3365cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
3366cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
3367cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
3368cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
3369cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
3370cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
3371cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
3372cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
3373cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
3374cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
3375cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
3376cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
3377cached_dialect!(
3378    CACHED_MATERIALIZE,
3379    MaterializeDialect,
3380    "dialect-materialize"
3381);
3382cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
3383cached_dialect!(
3384    CACHED_SINGLESTORE,
3385    SingleStoreDialect,
3386    "dialect-singlestore"
3387);
3388cached_dialect!(
3389    CACHED_COCKROACHDB,
3390    CockroachDBDialect,
3391    "dialect-cockroachdb"
3392);
3393cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
3394cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
3395cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
3396cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
3397cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
3398cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
3399cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
3400cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
3401cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
3402cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
3403
3404fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
3405    /// Clone configs from a cached static and pair with a fresh transform closure.
3406    macro_rules! from_cache {
3407        ($cache:expr, $dialect_struct:expr) => {{
3408            let c = &*$cache;
3409            DialectConfigs {
3410                tokenizer_config: c.tokenizer_config.clone(),
3411                #[cfg(feature = "generate")]
3412                generator_config: c.generator_config.clone(),
3413                #[cfg(feature = "transpile")]
3414                transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
3415            }
3416        }};
3417    }
3418    match dt {
3419        #[cfg(feature = "dialect-postgresql")]
3420        DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
3421        #[cfg(feature = "dialect-mysql")]
3422        DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
3423        #[cfg(feature = "dialect-bigquery")]
3424        DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
3425        #[cfg(feature = "dialect-snowflake")]
3426        DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
3427        #[cfg(feature = "dialect-duckdb")]
3428        DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
3429        #[cfg(feature = "dialect-tsql")]
3430        DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
3431        #[cfg(feature = "dialect-oracle")]
3432        DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
3433        #[cfg(feature = "dialect-hive")]
3434        DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
3435        #[cfg(feature = "dialect-spark")]
3436        DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
3437        #[cfg(feature = "dialect-sqlite")]
3438        DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
3439        #[cfg(feature = "dialect-presto")]
3440        DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
3441        #[cfg(feature = "dialect-trino")]
3442        DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
3443        #[cfg(feature = "dialect-redshift")]
3444        DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
3445        #[cfg(feature = "dialect-clickhouse")]
3446        DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
3447        #[cfg(feature = "dialect-databricks")]
3448        DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
3449        #[cfg(feature = "dialect-athena")]
3450        DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
3451        #[cfg(feature = "dialect-teradata")]
3452        DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
3453        #[cfg(feature = "dialect-doris")]
3454        DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
3455        #[cfg(feature = "dialect-starrocks")]
3456        DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
3457        #[cfg(feature = "dialect-materialize")]
3458        DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
3459        #[cfg(feature = "dialect-risingwave")]
3460        DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
3461        #[cfg(feature = "dialect-singlestore")]
3462        DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
3463        #[cfg(feature = "dialect-cockroachdb")]
3464        DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
3465        #[cfg(feature = "dialect-tidb")]
3466        DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
3467        #[cfg(feature = "dialect-druid")]
3468        DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
3469        #[cfg(feature = "dialect-solr")]
3470        DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
3471        #[cfg(feature = "dialect-tableau")]
3472        DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
3473        #[cfg(feature = "dialect-dune")]
3474        DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
3475        #[cfg(feature = "dialect-fabric")]
3476        DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
3477        #[cfg(feature = "dialect-drill")]
3478        DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
3479        #[cfg(feature = "dialect-dremio")]
3480        DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
3481        #[cfg(feature = "dialect-exasol")]
3482        DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
3483        #[cfg(feature = "dialect-datafusion")]
3484        DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
3485        _ => from_cache!(CACHED_GENERIC, GenericDialect),
3486    }
3487}
3488
3489// ---------------------------------------------------------------------------
3490// Custom dialect registry
3491// ---------------------------------------------------------------------------
3492
3493static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
3494    LazyLock::new(|| RwLock::new(HashMap::new()));
3495
3496struct CustomDialectConfig {
3497    name: String,
3498    base_dialect: DialectType,
3499    tokenizer_config: TokenizerConfig,
3500    #[cfg(feature = "generate")]
3501    generator_config: GeneratorConfig,
3502    #[cfg(feature = "transpile")]
3503    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3504    #[cfg(feature = "transpile")]
3505    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3506}
3507
3508/// Fluent builder for creating and registering custom SQL dialects.
3509///
3510/// A custom dialect is based on an existing built-in dialect and allows selective
3511/// overrides of tokenizer configuration, generator configuration, and expression
3512/// transforms.
3513///
3514/// # Example
3515///
3516/// ```rust,ignore
3517/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
3518/// use polyglot_sql::generator::NormalizeFunctions;
3519///
3520/// CustomDialectBuilder::new("my_postgres")
3521///     .based_on(DialectType::PostgreSQL)
3522///     .generator_config_modifier(|gc| {
3523///         gc.normalize_functions = NormalizeFunctions::Lower;
3524///     })
3525///     .register()
3526///     .unwrap();
3527///
3528/// let d = Dialect::get_by_name("my_postgres").unwrap();
3529/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
3530/// let sql = d.generate(&exprs[0]).unwrap();
3531/// assert_eq!(sql, "select count(*)");
3532///
3533/// polyglot_sql::unregister_custom_dialect("my_postgres");
3534/// ```
3535pub struct CustomDialectBuilder {
3536    name: String,
3537    base_dialect: DialectType,
3538    tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
3539    #[cfg(feature = "generate")]
3540    generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
3541    #[cfg(feature = "transpile")]
3542    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3543    #[cfg(feature = "transpile")]
3544    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3545}
3546
3547impl CustomDialectBuilder {
3548    /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
3549    pub fn new(name: impl Into<String>) -> Self {
3550        Self {
3551            name: name.into(),
3552            base_dialect: DialectType::Generic,
3553            tokenizer_modifier: None,
3554            #[cfg(feature = "generate")]
3555            generator_modifier: None,
3556            #[cfg(feature = "transpile")]
3557            transform: None,
3558            #[cfg(feature = "transpile")]
3559            preprocess: None,
3560        }
3561    }
3562
3563    /// Set the base built-in dialect to inherit configuration from.
3564    pub fn based_on(mut self, dialect: DialectType) -> Self {
3565        self.base_dialect = dialect;
3566        self
3567    }
3568
3569    /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
3570    pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
3571    where
3572        F: FnOnce(&mut TokenizerConfig) + 'static,
3573    {
3574        self.tokenizer_modifier = Some(Box::new(f));
3575        self
3576    }
3577
3578    /// Provide a closure that modifies the generator configuration inherited from the base dialect.
3579    #[cfg(feature = "generate")]
3580    pub fn generator_config_modifier<F>(mut self, f: F) -> Self
3581    where
3582        F: FnOnce(&mut GeneratorConfig) + 'static,
3583    {
3584        self.generator_modifier = Some(Box::new(f));
3585        self
3586    }
3587
3588    /// Set a custom per-node expression transform function.
3589    ///
3590    /// This replaces the base dialect's transform. It is called on every expression
3591    /// node during the recursive transform pass.
3592    #[cfg(feature = "transpile")]
3593    pub fn transform_fn<F>(mut self, f: F) -> Self
3594    where
3595        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3596    {
3597        self.transform = Some(Arc::new(f));
3598        self
3599    }
3600
3601    /// Set a custom whole-tree preprocessing function.
3602    ///
3603    /// This replaces the base dialect's built-in preprocessing. It is called once
3604    /// on the entire expression tree before the recursive per-node transform.
3605    #[cfg(feature = "transpile")]
3606    pub fn preprocess_fn<F>(mut self, f: F) -> Self
3607    where
3608        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3609    {
3610        self.preprocess = Some(Arc::new(f));
3611        self
3612    }
3613
3614    /// Build the custom dialect configuration and register it in the global registry.
3615    ///
3616    /// Returns an error if:
3617    /// - The name collides with a built-in dialect name
3618    /// - A custom dialect with the same name is already registered
3619    pub fn register(self) -> Result<()> {
3620        // Reject names that collide with built-in dialects
3621        if DialectType::from_str(&self.name).is_ok() {
3622            return Err(crate::error::Error::parse(
3623                format!(
3624                    "Cannot register custom dialect '{}': name collides with built-in dialect",
3625                    self.name
3626                ),
3627                0,
3628                0,
3629                0,
3630                0,
3631            ));
3632        }
3633
3634        // Get base configs
3635        let base_configs = configs_for_dialect_type(self.base_dialect);
3636        let mut tok_config = base_configs.tokenizer_config;
3637        #[cfg(feature = "generate")]
3638        let mut gen_config = (*base_configs.generator_config).clone();
3639
3640        // Apply modifiers
3641        if let Some(tok_mod) = self.tokenizer_modifier {
3642            tok_mod(&mut tok_config);
3643        }
3644        #[cfg(feature = "generate")]
3645        if let Some(gen_mod) = self.generator_modifier {
3646            gen_mod(&mut gen_config);
3647        }
3648
3649        let config = CustomDialectConfig {
3650            name: self.name.clone(),
3651            base_dialect: self.base_dialect,
3652            tokenizer_config: tok_config,
3653            #[cfg(feature = "generate")]
3654            generator_config: gen_config,
3655            #[cfg(feature = "transpile")]
3656            transform: self.transform,
3657            #[cfg(feature = "transpile")]
3658            preprocess: self.preprocess,
3659        };
3660
3661        register_custom_dialect(config)
3662    }
3663}
3664
3665use std::str::FromStr;
3666
3667fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
3668    let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
3669        crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
3670    })?;
3671
3672    if registry.contains_key(&config.name) {
3673        return Err(crate::error::Error::parse(
3674            format!("Custom dialect '{}' is already registered", config.name),
3675            0,
3676            0,
3677            0,
3678            0,
3679        ));
3680    }
3681
3682    registry.insert(config.name.clone(), Arc::new(config));
3683    Ok(())
3684}
3685
3686/// Remove a custom dialect from the global registry.
3687///
3688/// Returns `true` if a dialect with that name was found and removed,
3689/// `false` if no such custom dialect existed.
3690pub fn unregister_custom_dialect(name: &str) -> bool {
3691    if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
3692        registry.remove(name).is_some()
3693    } else {
3694        false
3695    }
3696}
3697
3698fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
3699    CUSTOM_DIALECT_REGISTRY
3700        .read()
3701        .ok()
3702        .and_then(|registry| registry.get(name).cloned())
3703}
3704
3705/// Main entry point for dialect-specific SQL operations.
3706///
3707/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
3708/// transformer for a specific SQL database engine. It is the high-level API through
3709/// which callers parse, generate, transform, and transpile SQL.
3710///
3711/// # Usage
3712///
3713/// ```rust,ignore
3714/// use polyglot_sql::dialects::{Dialect, DialectType};
3715///
3716/// // Parse PostgreSQL SQL into an AST
3717/// let pg = Dialect::get(DialectType::PostgreSQL);
3718/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
3719///
3720/// // Transpile from PostgreSQL to BigQuery
3721/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
3722/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
3723/// ```
3724///
3725/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
3726/// The struct is `Send + Sync` safe so it can be shared across threads.
3727pub struct Dialect {
3728    dialect_type: DialectType,
3729    tokenizer: Tokenizer,
3730    #[cfg(feature = "generate")]
3731    generator_config: Arc<GeneratorConfig>,
3732    #[cfg(feature = "transpile")]
3733    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3734    /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
3735    #[cfg(feature = "generate")]
3736    generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
3737    /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
3738    #[cfg(feature = "transpile")]
3739    custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3740}
3741
3742/// Options for [`Dialect::transpile_with`].
3743///
3744/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
3745/// The struct is marked `#[non_exhaustive]` so new fields can be added without
3746/// breaking the API.
3747///
3748/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
3749/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
3750#[cfg(feature = "transpile")]
3751#[derive(Debug, Clone, Serialize, Deserialize)]
3752#[serde(rename_all = "camelCase", default)]
3753#[non_exhaustive]
3754pub struct TranspileOptions {
3755    /// Whether to pretty-print the output SQL.
3756    pub pretty: bool,
3757    /// How unsupported target-dialect constructs should be handled.
3758    ///
3759    /// The default is [`UnsupportedLevel::Warn`], which preserves the current
3760    /// compatibility behavior and continues transpilation.
3761    pub unsupported_level: UnsupportedLevel,
3762    /// Maximum number of unsupported diagnostics to include in raised errors.
3763    pub max_unsupported: usize,
3764    /// Complexity guard limits used while parsing, transforming, and generating.
3765    pub complexity_guard: ComplexityGuardOptions,
3766}
3767
3768#[cfg(feature = "transpile")]
3769impl Default for TranspileOptions {
3770    fn default() -> Self {
3771        Self {
3772            pretty: false,
3773            unsupported_level: UnsupportedLevel::Warn,
3774            max_unsupported: 3,
3775            complexity_guard: ComplexityGuardOptions::default(),
3776        }
3777    }
3778}
3779
3780#[cfg(feature = "transpile")]
3781impl TranspileOptions {
3782    /// Construct options with pretty-printing enabled.
3783    pub fn pretty() -> Self {
3784        Self {
3785            pretty: true,
3786            ..Default::default()
3787        }
3788    }
3789
3790    /// Construct options that raise when known unsupported constructs remain.
3791    pub fn strict() -> Self {
3792        Self {
3793            unsupported_level: UnsupportedLevel::Raise,
3794            ..Default::default()
3795        }
3796    }
3797
3798    /// Set how unsupported target-dialect constructs should be handled.
3799    pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3800        self.unsupported_level = level;
3801        self
3802    }
3803
3804    /// Set the maximum number of unsupported diagnostics to include in raised errors.
3805    pub fn with_max_unsupported(mut self, max: usize) -> Self {
3806        self.max_unsupported = max;
3807        self
3808    }
3809
3810    /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3811    pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3812        self.complexity_guard = guard;
3813        self
3814    }
3815}
3816
3817/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3818/// [`Dialect::transpile_with`].
3819///
3820/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3821/// dialect handle, including custom ones). End users do not normally need to
3822/// implement this trait themselves.
3823#[cfg(feature = "transpile")]
3824pub trait TranspileTarget {
3825    /// Invoke `f` with a reference to the resolved target dialect.
3826    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3827}
3828
3829#[cfg(feature = "transpile")]
3830impl TranspileTarget for DialectType {
3831    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3832        f(&Dialect::get(self))
3833    }
3834}
3835
3836#[cfg(feature = "transpile")]
3837impl TranspileTarget for &Dialect {
3838    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3839        f(self)
3840    }
3841}
3842
3843impl Dialect {
3844    /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3845    ///
3846    /// This is the primary constructor. It initializes the tokenizer, generator config,
3847    /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3848    /// For hybrid dialects like Athena, it also sets up expression-specific generator
3849    /// config routing.
3850    pub fn get(dialect_type: DialectType) -> Self {
3851        let configs = configs_for_dialect_type(dialect_type);
3852        let tokenizer_config = configs.tokenizer_config;
3853        #[cfg(feature = "generate")]
3854        let generator_config = configs.generator_config;
3855        #[cfg(feature = "transpile")]
3856        let transformer = configs.transformer;
3857
3858        // Set up expression-specific generator config for hybrid dialects
3859        #[cfg(feature = "generate")]
3860        let generator_config_for_expr: Option<
3861            Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3862        > = match dialect_type {
3863            #[cfg(feature = "dialect-athena")]
3864            DialectType::Athena => Some(Box::new(|expr| {
3865                AthenaDialect.generator_config_for_expr(expr)
3866            })),
3867            _ => None,
3868        };
3869
3870        Self {
3871            dialect_type,
3872            tokenizer: Tokenizer::new(tokenizer_config),
3873            #[cfg(feature = "generate")]
3874            generator_config,
3875            #[cfg(feature = "transpile")]
3876            transformer,
3877            #[cfg(feature = "generate")]
3878            generator_config_for_expr,
3879            #[cfg(feature = "transpile")]
3880            custom_preprocess: None,
3881        }
3882    }
3883
3884    /// Look up a dialect by string name.
3885    ///
3886    /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3887    /// falls back to the custom dialect registry. Returns `None` if no dialect
3888    /// with the given name exists.
3889    pub fn get_by_name(name: &str) -> Option<Self> {
3890        // Try built-in first
3891        if let Ok(dt) = DialectType::from_str(name) {
3892            return Some(Self::get(dt));
3893        }
3894
3895        // Try custom registry
3896        let config = get_custom_dialect_config(name)?;
3897        Some(Self::from_custom_config(&config))
3898    }
3899
3900    /// Construct a `Dialect` from a custom dialect configuration.
3901    fn from_custom_config(config: &CustomDialectConfig) -> Self {
3902        // Build the transformer: use custom if provided, else use base dialect's
3903        #[cfg(feature = "transpile")]
3904        let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3905            if let Some(ref custom_transform) = config.transform {
3906                let t = Arc::clone(custom_transform);
3907                Box::new(move |e| t(e))
3908            } else {
3909                configs_for_dialect_type(config.base_dialect).transformer
3910            };
3911
3912        // Build the custom preprocess: use custom if provided
3913        #[cfg(feature = "transpile")]
3914        let custom_preprocess: Option<
3915            Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3916        > = config.preprocess.as_ref().map(|p| {
3917            let p = Arc::clone(p);
3918            Box::new(move |e: Expression| p(e))
3919                as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3920        });
3921
3922        Self {
3923            dialect_type: config.base_dialect,
3924            tokenizer: Tokenizer::new(config.tokenizer_config.clone()),
3925            #[cfg(feature = "generate")]
3926            generator_config: Arc::new(config.generator_config.clone()),
3927            #[cfg(feature = "transpile")]
3928            transformer,
3929            #[cfg(feature = "generate")]
3930            generator_config_for_expr: None,
3931            #[cfg(feature = "transpile")]
3932            custom_preprocess,
3933        }
3934    }
3935
3936    /// Get the dialect type
3937    pub fn dialect_type(&self) -> DialectType {
3938        self.dialect_type
3939    }
3940
3941    /// Get the generator configuration
3942    #[cfg(feature = "generate")]
3943    pub fn generator_config(&self) -> &GeneratorConfig {
3944        &self.generator_config
3945    }
3946
3947    /// Parses a SQL string into a list of [`Expression`] AST nodes.
3948    ///
3949    /// The input may contain multiple semicolon-separated statements; each one
3950    /// produces a separate element in the returned vector. Tokenization uses
3951    /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3952    pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3953        self.parse_with_guard(sql, self.default_complexity_guard())
3954    }
3955
3956    fn parse_with_guard(
3957        &self,
3958        sql: &str,
3959        complexity_guard: ComplexityGuardOptions,
3960    ) -> Result<Vec<Expression>> {
3961        enforce_input(sql, &complexity_guard)?;
3962        let tokens = self.tokenizer.tokenize(sql)?;
3963        let config = crate::parser::ParserConfig {
3964            dialect: Some(self.dialect_type),
3965            complexity_guard,
3966            ..Default::default()
3967        };
3968        let mut parser = Parser::with_source(tokens, config, sql.to_string());
3969        parser.parse()
3970    }
3971
3972    fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3973        let mut guard = ComplexityGuardOptions::default();
3974        if matches!(self.dialect_type, DialectType::ClickHouse) {
3975            guard.max_ast_depth = Some(4_096);
3976            guard.max_function_call_depth = Some(512);
3977        }
3978        guard
3979    }
3980
3981    #[cfg(feature = "transpile")]
3982    fn default_transpile_complexity_guard(
3983        &self,
3984        target_dialect: &Dialect,
3985        guard: ComplexityGuardOptions,
3986    ) -> ComplexityGuardOptions {
3987        if guard != ComplexityGuardOptions::default() {
3988            return guard;
3989        }
3990
3991        if matches!(self.dialect_type, DialectType::ClickHouse)
3992            || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3993        {
3994            let mut guard = guard;
3995            guard.max_ast_depth = Some(4_096);
3996            guard.max_function_call_depth = Some(512);
3997            guard
3998        } else {
3999            guard
4000        }
4001    }
4002
4003    /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
4004    ///
4005    /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
4006    /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
4007    pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
4008        let complexity_guard = self.default_complexity_guard();
4009        enforce_input(sql, &complexity_guard)?;
4010        let tokens = self.tokenizer.tokenize(sql)?;
4011        let config = crate::parser::ParserConfig {
4012            dialect: Some(self.dialect_type),
4013            complexity_guard,
4014            ..Default::default()
4015        };
4016        let mut parser = Parser::with_source(tokens, config, sql.to_string());
4017        parser.parse_standalone_data_type()
4018    }
4019
4020    /// Tokenize SQL using this dialect's tokenizer configuration.
4021    pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
4022        self.tokenizer.tokenize(sql)
4023    }
4024
4025    /// Get the generator config for a specific expression (supports hybrid dialects).
4026    /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
4027    #[cfg(feature = "generate")]
4028    fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
4029        if let Some(ref config_fn) = self.generator_config_for_expr {
4030            config_fn(expr)
4031        } else {
4032            (*self.generator_config).clone()
4033        }
4034    }
4035
4036    /// Generates a SQL string from an [`Expression`] AST node.
4037    ///
4038    /// The output uses this dialect's generator configuration for identifier quoting,
4039    /// keyword casing, function name normalization, and syntax style. The result is
4040    /// a single-line (non-pretty) SQL string.
4041    #[cfg(feature = "generate")]
4042    pub fn generate(&self, expr: &Expression) -> Result<String> {
4043        // Fast path: when no per-expression config override, share the Arc cheaply.
4044        if self.generator_config_for_expr.is_none() {
4045            let mut generator = Generator::with_arc_config(self.generator_config.clone());
4046            return generator.generate(expr);
4047        }
4048        let config = self.get_config_for_expr(expr);
4049        let mut generator = Generator::with_config(config);
4050        generator.generate(expr)
4051    }
4052
4053    /// Generate SQL from an expression with pretty printing enabled
4054    #[cfg(feature = "generate")]
4055    pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
4056        let mut config = self.get_config_for_expr(expr);
4057        config.pretty = true;
4058        let mut generator = Generator::with_config(config);
4059        generator.generate(expr)
4060    }
4061
4062    /// Generate SQL from an expression with source dialect info (for transpilation)
4063    #[cfg(feature = "generate")]
4064    pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
4065        let mut config = self.get_config_for_expr(expr);
4066        config.source_dialect = Some(source);
4067        let mut generator = Generator::with_config(config);
4068        generator.generate(expr)
4069    }
4070
4071    /// Generate SQL from an expression with pretty printing and source dialect info
4072    #[cfg(feature = "generate")]
4073    pub fn generate_pretty_with_source(
4074        &self,
4075        expr: &Expression,
4076        source: DialectType,
4077    ) -> Result<String> {
4078        let mut config = self.get_config_for_expr(expr);
4079        config.pretty = true;
4080        config.source_dialect = Some(source);
4081        let mut generator = Generator::with_config(config);
4082        generator.generate(expr)
4083    }
4084
4085    /// Generate SQL from an expression with source dialect and transpile options.
4086    #[cfg(all(feature = "generate", feature = "transpile"))]
4087    fn generate_with_transpile_options(
4088        &self,
4089        expr: &Expression,
4090        source: DialectType,
4091        opts: &TranspileOptions,
4092    ) -> Result<String> {
4093        let mut config = self.get_config_for_expr(expr);
4094        config.source_dialect = Some(source);
4095        config.pretty = opts.pretty;
4096        config.unsupported_level = opts.unsupported_level;
4097        config.max_unsupported = opts.max_unsupported.max(1);
4098        config.complexity_guard = opts.complexity_guard;
4099        let mut generator = Generator::with_config(config);
4100        generator.generate(expr)
4101    }
4102
4103    /// Generate SQL from an expression with forced identifier quoting (identify=True)
4104    #[cfg(feature = "generate")]
4105    pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
4106        let mut config = self.get_config_for_expr(expr);
4107        config.always_quote_identifiers = true;
4108        let mut generator = Generator::with_config(config);
4109        generator.generate(expr)
4110    }
4111
4112    /// Generate SQL from an expression with pretty printing and forced identifier quoting
4113    #[cfg(feature = "generate")]
4114    pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
4115        let mut config = (*self.generator_config).clone();
4116        config.pretty = true;
4117        config.always_quote_identifiers = true;
4118        let mut generator = Generator::with_config(config);
4119        generator.generate(expr)
4120    }
4121
4122    /// Generate SQL from an expression with caller-specified config overrides
4123    #[cfg(feature = "generate")]
4124    pub fn generate_with_overrides(
4125        &self,
4126        expr: &Expression,
4127        overrides: impl FnOnce(&mut GeneratorConfig),
4128    ) -> Result<String> {
4129        let mut config = self.get_config_for_expr(expr);
4130        overrides(&mut config);
4131        let mut generator = Generator::with_config(config);
4132        generator.generate(expr)
4133    }
4134
4135    /// Transforms an expression tree to conform to this dialect's syntax and semantics.
4136    ///
4137    /// The transformation proceeds in two phases:
4138    /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
4139    ///    ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
4140    /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
4141    ///    that applies this dialect's [`DialectImpl::transform_expr`] to every node.
4142    ///
4143    /// This method is used both during transpilation (to rewrite an AST for a target dialect)
4144    /// and for identity transforms (normalizing SQL within the same dialect).
4145    #[cfg(feature = "transpile")]
4146    pub fn transform(&self, expr: Expression) -> Result<Expression> {
4147        self.transform_with_guard(expr, self.default_complexity_guard())
4148    }
4149
4150    #[cfg(feature = "transpile")]
4151    fn transform_with_guard(
4152        &self,
4153        expr: Expression,
4154        complexity_guard: ComplexityGuardOptions,
4155    ) -> Result<Expression> {
4156        enforce_generate_ast(&expr, &complexity_guard)?;
4157        // Apply preprocessing transforms based on dialect
4158        let preprocessed = self.preprocess(expr)?;
4159        // Then apply recursive transformation
4160        transform_recursive(preprocessed, &self.transformer)
4161    }
4162
4163    /// Apply dialect-specific preprocessing transforms
4164    #[cfg(feature = "transpile")]
4165    fn preprocess(&self, expr: Expression) -> Result<Expression> {
4166        // If a custom preprocess function is set, use it instead of the built-in logic
4167        if let Some(ref custom_preprocess) = self.custom_preprocess {
4168            return custom_preprocess(expr);
4169        }
4170
4171        #[cfg(any(
4172            feature = "dialect-mysql",
4173            feature = "dialect-postgresql",
4174            feature = "dialect-bigquery",
4175            feature = "dialect-snowflake",
4176            feature = "dialect-tsql",
4177            feature = "dialect-spark",
4178            feature = "dialect-databricks",
4179            feature = "dialect-hive",
4180            feature = "dialect-sqlite",
4181            feature = "dialect-trino",
4182            feature = "dialect-presto",
4183            feature = "dialect-duckdb",
4184            feature = "dialect-redshift",
4185            feature = "dialect-starrocks",
4186            feature = "dialect-oracle",
4187            feature = "dialect-clickhouse",
4188        ))]
4189        use crate::transforms;
4190
4191        match self.dialect_type {
4192            // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
4193            // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
4194            #[cfg(feature = "dialect-mysql")]
4195            DialectType::MySQL => {
4196                let expr = transforms::eliminate_qualify(expr)?;
4197                let expr = transforms::eliminate_full_outer_join(expr)?;
4198                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4199                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4200                Ok(expr)
4201            }
4202            // PostgreSQL doesn't support QUALIFY
4203            // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
4204            // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
4205            #[cfg(feature = "dialect-postgresql")]
4206            DialectType::PostgreSQL => {
4207                let expr = transforms::eliminate_qualify(expr)?;
4208                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4209                let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
4210                // Normalize SET ... TO to SET ... = in CREATE FUNCTION
4211                // Only normalize when sqlglot would fully parse (no body) —
4212                // sqlglot falls back to Command for complex function bodies,
4213                // preserving the original text including TO.
4214                let expr = if let Expression::CreateFunction(mut cf) = expr {
4215                    if cf.body.is_none() {
4216                        for opt in &mut cf.set_options {
4217                            if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
4218                                &mut opt.value
4219                            {
4220                                *use_to = false;
4221                            }
4222                        }
4223                    }
4224                    Expression::CreateFunction(cf)
4225                } else {
4226                    expr
4227                };
4228                Ok(expr)
4229            }
4230            // BigQuery doesn't support DISTINCT ON or CTE column aliases
4231            #[cfg(feature = "dialect-bigquery")]
4232            DialectType::BigQuery => {
4233                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4234                let expr = transforms::pushdown_cte_column_names(expr)?;
4235                let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
4236                Ok(expr)
4237            }
4238            // Snowflake
4239            #[cfg(feature = "dialect-snowflake")]
4240            DialectType::Snowflake => {
4241                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4242                let expr = transforms::eliminate_window_clause(expr)?;
4243                let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
4244                Ok(expr)
4245            }
4246            // TSQL doesn't support QUALIFY
4247            // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
4248            // TSQL doesn't support CTEs in subqueries (hoist to top level)
4249            // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
4250            // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
4251            #[cfg(feature = "dialect-tsql")]
4252            DialectType::TSQL => {
4253                let expr = transforms::eliminate_qualify(expr)?;
4254                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4255                let expr = transforms::ensure_bools(expr)?;
4256                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4257                let expr = transforms::move_ctes_to_top_level(expr)?;
4258                let expr = transforms::qualify_derived_table_outputs(expr)?;
4259                Ok(expr)
4260            }
4261            // Spark doesn't support QUALIFY (but Databricks does)
4262            // Spark doesn't support CTEs in subqueries (hoist to top level)
4263            #[cfg(feature = "dialect-spark")]
4264            DialectType::Spark => {
4265                let expr = transforms::eliminate_qualify(expr)?;
4266                let expr = transforms::add_auto_table_alias(expr)?;
4267                let expr = transforms::simplify_nested_paren_values(expr)?;
4268                let expr = transforms::move_ctes_to_top_level(expr)?;
4269                Ok(expr)
4270            }
4271            // Databricks supports QUALIFY natively
4272            // Databricks doesn't support CTEs in subqueries (hoist to top level)
4273            #[cfg(feature = "dialect-databricks")]
4274            DialectType::Databricks => {
4275                let expr = transforms::add_auto_table_alias(expr)?;
4276                let expr = transforms::simplify_nested_paren_values(expr)?;
4277                let expr = transforms::move_ctes_to_top_level(expr)?;
4278                Ok(expr)
4279            }
4280            // Hive doesn't support QUALIFY or CTEs in subqueries
4281            #[cfg(feature = "dialect-hive")]
4282            DialectType::Hive => {
4283                let expr = transforms::eliminate_qualify(expr)?;
4284                let expr = transforms::move_ctes_to_top_level(expr)?;
4285                Ok(expr)
4286            }
4287            // SQLite doesn't support QUALIFY
4288            #[cfg(feature = "dialect-sqlite")]
4289            DialectType::SQLite => {
4290                let expr = transforms::eliminate_qualify(expr)?;
4291                Ok(expr)
4292            }
4293            // Trino doesn't support QUALIFY
4294            #[cfg(feature = "dialect-trino")]
4295            DialectType::Trino => {
4296                let expr = transforms::eliminate_qualify(expr)?;
4297                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
4298                Ok(expr)
4299            }
4300            // Presto doesn't support QUALIFY or WINDOW clause
4301            #[cfg(feature = "dialect-presto")]
4302            DialectType::Presto => {
4303                let expr = transforms::eliminate_qualify(expr)?;
4304                let expr = transforms::eliminate_window_clause(expr)?;
4305                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
4306                Ok(expr)
4307            }
4308            // DuckDB supports QUALIFY - no elimination needed
4309            // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
4310            // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
4311            #[cfg(feature = "dialect-duckdb")]
4312            DialectType::DuckDB => {
4313                let expr = transforms::expand_posexplode_duckdb(expr)?;
4314                let expr = transforms::expand_like_any(expr)?;
4315                Ok(expr)
4316            }
4317            // Redshift doesn't support QUALIFY, WINDOW clause, or GENERATE_DATE_ARRAY
4318            #[cfg(feature = "dialect-redshift")]
4319            DialectType::Redshift => {
4320                let expr = transforms::eliminate_qualify(expr)?;
4321                let expr = transforms::eliminate_window_clause(expr)?;
4322                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4323                Ok(expr)
4324            }
4325            // StarRocks doesn't support BETWEEN in DELETE statements or QUALIFY
4326            #[cfg(feature = "dialect-starrocks")]
4327            DialectType::StarRocks => {
4328                let expr = transforms::eliminate_qualify(expr)?;
4329                let expr = transforms::expand_between_in_delete(expr)?;
4330                let expr = transforms::eliminate_distinct_on_for_dialect(
4331                    expr,
4332                    Some(DialectType::StarRocks),
4333                    Some(DialectType::StarRocks),
4334                )?;
4335                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4336                Ok(expr)
4337            }
4338            // DataFusion supports QUALIFY and semi/anti joins natively
4339            #[cfg(feature = "dialect-datafusion")]
4340            DialectType::DataFusion => Ok(expr),
4341            // Oracle doesn't support QUALIFY
4342            #[cfg(feature = "dialect-oracle")]
4343            DialectType::Oracle => {
4344                let expr = transforms::eliminate_qualify(expr)?;
4345                Ok(expr)
4346            }
4347            // Drill - no special preprocessing needed
4348            #[cfg(feature = "dialect-drill")]
4349            DialectType::Drill => Ok(expr),
4350            // Teradata - no special preprocessing needed
4351            #[cfg(feature = "dialect-teradata")]
4352            DialectType::Teradata => Ok(expr),
4353            // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
4354            #[cfg(feature = "dialect-clickhouse")]
4355            DialectType::ClickHouse => {
4356                let expr = transforms::no_limit_order_by_union(expr)?;
4357                Ok(expr)
4358            }
4359            // Other dialects - no preprocessing
4360            _ => Ok(expr),
4361        }
4362    }
4363
4364    /// Transpile SQL from this dialect to the given target dialect.
4365    ///
4366    /// The target may be specified as either a built-in [`DialectType`] enum variant
4367    /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
4368    ///
4369    /// ```rust,ignore
4370    /// let pg = Dialect::get(DialectType::PostgreSQL);
4371    /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?;   // enum
4372    /// pg.transpile("SELECT NOW()", &custom_dialect)?;         // handle
4373    /// ```
4374    ///
4375    /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
4376    #[cfg(feature = "transpile")]
4377    pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
4378        self.transpile_with(sql, target, TranspileOptions::default())
4379    }
4380
4381    /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
4382    #[cfg(feature = "transpile")]
4383    pub fn transpile_with<T: TranspileTarget>(
4384        &self,
4385        sql: &str,
4386        target: T,
4387        opts: TranspileOptions,
4388    ) -> Result<Vec<String>> {
4389        target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
4390    }
4391
4392    #[cfg(feature = "transpile")]
4393    fn transpile_inner(
4394        &self,
4395        sql: &str,
4396        target_dialect: &Dialect,
4397        opts: &TranspileOptions,
4398    ) -> Result<Vec<String>> {
4399        let mut effective_opts = opts.clone();
4400        effective_opts.complexity_guard =
4401            self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
4402        let opts = &effective_opts;
4403        let target = target_dialect.dialect_type;
4404        if matches!(self.dialect_type, DialectType::PostgreSQL)
4405            && matches!(target, DialectType::SQLite)
4406        {
4407            self.reject_pgvector_distance_operators_for_sqlite(sql)?;
4408        }
4409        let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
4410        let generic_identity =
4411            self.dialect_type == DialectType::Generic && target == DialectType::Generic;
4412
4413        if generic_identity {
4414            return expressions
4415                .into_iter()
4416                .map(|expr| {
4417                    Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
4418                    target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
4419                })
4420                .collect();
4421        }
4422
4423        expressions
4424            .into_iter()
4425            .map(|expr| {
4426                // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
4427                // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
4428                // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
4429                let expr = if matches!(self.dialect_type, DialectType::DuckDB) {
4430                    use crate::expressions::DataType as DT;
4431                    transform_recursive(expr, &|e| match e {
4432                        Expression::DataType(DT::VarChar { .. }) => {
4433                            Ok(Expression::DataType(DT::Text))
4434                        }
4435                        Expression::DataType(DT::Char { .. }) => Ok(Expression::DataType(DT::Text)),
4436                        _ => Ok(e),
4437                    })?
4438                } else {
4439                    expr
4440                };
4441
4442                // When source and target differ, first normalize the source dialect's
4443                // AST constructs to standard SQL, so that the target dialect can handle them.
4444                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
4445                let normalized =
4446                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
4447                        self.transform_with_guard(expr, opts.complexity_guard)?
4448                    } else {
4449                        expr
4450                    };
4451
4452                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
4453                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
4454                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
4455                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
4456                let normalized =
4457                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4458                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
4459                    {
4460                        transform_recursive(normalized, &|e| {
4461                            if let Expression::Function(ref f) = e {
4462                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
4463                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
4464                                    if let (
4465                                        Expression::Function(ref jq),
4466                                        Expression::Function(ref jv),
4467                                    ) = (&f.args[0], &f.args[1])
4468                                    {
4469                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
4470                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
4471                                        {
4472                                            // Unwrap: return just JSON_QUERY(...)
4473                                            return Ok(f.args[0].clone());
4474                                        }
4475                                    }
4476                                }
4477                            }
4478                            Ok(e)
4479                        })?
4480                    } else {
4481                        normalized
4482                    };
4483
4484                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME
4485                // Snowflake's CURRENT_TIME is equivalent to LOCALTIME in other dialects.
4486                // Python sqlglot parses Snowflake's CURRENT_TIME as Localtime expression.
4487                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4488                    && !matches!(target, DialectType::Snowflake)
4489                {
4490                    transform_recursive(normalized, &|e| {
4491                        if let Expression::Function(ref f) = e {
4492                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") {
4493                                return Ok(Expression::Localtime(Box::new(
4494                                    crate::expressions::Localtime { this: None },
4495                                )));
4496                            }
4497                        }
4498                        Ok(e)
4499                    })?
4500                } else {
4501                    normalized
4502                };
4503
4504                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
4505                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
4506                // transform. DuckDB requires the count argument to be BIGINT.
4507                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4508                    && matches!(target, DialectType::DuckDB)
4509                {
4510                    transform_recursive(normalized, &|e| {
4511                        if let Expression::Function(ref f) = e {
4512                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
4513                                // Check if first arg is space string literal
4514                                if let Expression::Literal(ref lit) = f.args[0] {
4515                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
4516                                    {
4517                                        if s == " " {
4518                                            // Wrap second arg in CAST(... AS BIGINT) if not already
4519                                            if !matches!(f.args[1], Expression::Cast(_)) {
4520                                                let mut new_args = f.args.clone();
4521                                                new_args[1] = Expression::Cast(Box::new(
4522                                                    crate::expressions::Cast {
4523                                                        this: new_args[1].clone(),
4524                                                        to: crate::expressions::DataType::BigInt {
4525                                                            length: None,
4526                                                        },
4527                                                        trailing_comments: Vec::new(),
4528                                                        double_colon_syntax: false,
4529                                                        format: None,
4530                                                        default: None,
4531                                                        inferred_type: None,
4532                                                    },
4533                                                ));
4534                                                return Ok(Expression::Function(Box::new(
4535                                                    crate::expressions::Function {
4536                                                        name: f.name.clone(),
4537                                                        args: new_args,
4538                                                        distinct: f.distinct,
4539                                                        trailing_comments: f
4540                                                            .trailing_comments
4541                                                            .clone(),
4542                                                        use_bracket_syntax: f.use_bracket_syntax,
4543                                                        no_parens: f.no_parens,
4544                                                        quoted: f.quoted,
4545                                                        span: None,
4546                                                        inferred_type: None,
4547                                                    },
4548                                                )));
4549                                            }
4550                                        }
4551                                    }
4552                                }
4553                            }
4554                        }
4555                        Ok(e)
4556                    })?
4557                } else {
4558                    normalized
4559                };
4560
4561                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
4562                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
4563                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4564                    && !matches!(target, DialectType::BigQuery)
4565                {
4566                    crate::transforms::propagate_struct_field_names(normalized)?
4567                } else {
4568                    normalized
4569                };
4570
4571                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
4572                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
4573                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
4574                // functions handle their generator args differently (as float seeds).
4575                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4576                    && matches!(target, DialectType::DuckDB)
4577                {
4578                    fn make_scaled_random() -> Expression {
4579                        let lower =
4580                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
4581                                "-9.223372036854776E+18".to_string(),
4582                            )));
4583                        let upper =
4584                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
4585                                "9.223372036854776e+18".to_string(),
4586                            )));
4587                        let random_call = Expression::Random(crate::expressions::Random);
4588                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
4589                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
4590                                left: upper,
4591                                right: lower.clone(),
4592                                left_comments: vec![],
4593                                operator_comments: vec![],
4594                                trailing_comments: vec![],
4595                                inferred_type: None,
4596                            })),
4597                            trailing_comments: vec![],
4598                        }));
4599                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
4600                            left: random_call,
4601                            right: range_size,
4602                            left_comments: vec![],
4603                            operator_comments: vec![],
4604                            trailing_comments: vec![],
4605                            inferred_type: None,
4606                        }));
4607                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
4608                            left: lower,
4609                            right: scaled,
4610                            left_comments: vec![],
4611                            operator_comments: vec![],
4612                            trailing_comments: vec![],
4613                            inferred_type: None,
4614                        }));
4615                        Expression::Cast(Box::new(crate::expressions::Cast {
4616                            this: shifted,
4617                            to: crate::expressions::DataType::BigInt { length: None },
4618                            trailing_comments: vec![],
4619                            double_colon_syntax: false,
4620                            format: None,
4621                            default: None,
4622                            inferred_type: None,
4623                        }))
4624                    }
4625
4626                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
4627                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
4628                    // This prevents transform_recursive (which is bottom-up) from expanding
4629                    // seeded RANDOM into make_scaled_random() and losing the seed value.
4630                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4631                    // and then un-expanded back to Expression::Random by the code below.
4632                    let normalized = transform_recursive(normalized, &|e| {
4633                        if let Expression::Function(ref f) = e {
4634                            let n = f.name.to_ascii_uppercase();
4635                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4636                                if let Expression::Function(mut f) = e {
4637                                    for arg in f.args.iter_mut() {
4638                                        if let Expression::Rand(ref r) = arg {
4639                                            if r.lower.is_none() && r.upper.is_none() {
4640                                                if let Some(ref seed) = r.seed {
4641                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4642                                                    // so it won't be expanded by the RANDOM expansion below
4643                                                    *arg = Expression::Function(Box::new(
4644                                                        crate::expressions::Function::new(
4645                                                            "RANDOM".to_string(),
4646                                                            vec![*seed.clone()],
4647                                                        ),
4648                                                    ));
4649                                                }
4650                                            }
4651                                        }
4652                                    }
4653                                    return Ok(Expression::Function(f));
4654                                }
4655                            }
4656                        }
4657                        Ok(e)
4658                    })?;
4659
4660                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4661                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4662                    // we see the parent. We detect this and undo the expansion by replacing
4663                    // the expanded pattern back with Expression::Random.
4664                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4665                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4666                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4667                    transform_recursive(normalized, &|e| {
4668                        if let Expression::Function(ref f) = e {
4669                            let n = f.name.to_ascii_uppercase();
4670                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4671                                if let Expression::Function(mut f) = e {
4672                                    for arg in f.args.iter_mut() {
4673                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4674                                        if let Expression::Cast(ref cast) = arg {
4675                                            if matches!(
4676                                                cast.to,
4677                                                crate::expressions::DataType::BigInt { .. }
4678                                            ) {
4679                                                if let Expression::Add(ref add) = cast.this {
4680                                                    if let Expression::Literal(ref lit) = add.left {
4681                                                        if let crate::expressions::Literal::Number(
4682                                                            ref num,
4683                                                        ) = lit.as_ref()
4684                                                        {
4685                                                            if num == "-9.223372036854776E+18" {
4686                                                                *arg = Expression::Random(
4687                                                                    crate::expressions::Random,
4688                                                                );
4689                                                            }
4690                                                        }
4691                                                    }
4692                                                }
4693                                            }
4694                                        }
4695                                    }
4696                                    return Ok(Expression::Function(f));
4697                                }
4698                                return Ok(e);
4699                            }
4700                        }
4701                        match e {
4702                            Expression::Random(_) => Ok(make_scaled_random()),
4703                            // Rand(seed) with no bounds: drop seed and expand
4704                            // (DuckDB RANDOM doesn't support seeds)
4705                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4706                                Ok(make_scaled_random())
4707                            }
4708                            _ => Ok(e),
4709                        }
4710                    })?
4711                } else {
4712                    normalized
4713                };
4714
4715                // Apply cross-dialect semantic normalizations
4716                let normalized =
4717                    Self::cross_dialect_normalize(normalized, self.dialect_type, target)?;
4718
4719                let normalized =
4720                    if matches!(
4721                        self.dialect_type,
4722                        DialectType::PostgreSQL | DialectType::CockroachDB
4723                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4724                    {
4725                        Self::normalize_postgres_type_function_casts(normalized)?
4726                    } else {
4727                        normalized
4728                    };
4729
4730                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4731                    && !matches!(target, DialectType::SQLite)
4732                {
4733                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
4734                } else {
4735                    normalized
4736                };
4737
4738                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4739                    && matches!(target, DialectType::SQLite)
4740                {
4741                    Self::normalize_postgres_to_sqlite_types(normalized)?
4742                } else {
4743                    normalized
4744                };
4745
4746                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4747                    && matches!(target, DialectType::Fabric)
4748                {
4749                    Self::normalize_postgres_to_fabric_decimal_types(normalized)?
4750                } else {
4751                    normalized
4752                };
4753
4754                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4755                // (SELECT UNNEST(..., max_depth => 2)) subquery
4756                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4757                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4758                    && matches!(target, DialectType::DuckDB)
4759                {
4760                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4761                } else {
4762                    normalized
4763                };
4764
4765                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4766                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4767                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4768                    && matches!(
4769                        target,
4770                        DialectType::DuckDB
4771                            | DialectType::Presto
4772                            | DialectType::Trino
4773                            | DialectType::Athena
4774                            | DialectType::Spark
4775                            | DialectType::Databricks
4776                    ) {
4777                    crate::transforms::unnest_alias_to_column_alias(normalized)?
4778                } else if matches!(self.dialect_type, DialectType::BigQuery)
4779                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4780                {
4781                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4782                    // but don't convert alias format (no _t0 wrapper)
4783                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4784                    // For Redshift: strip UNNEST when arg is a column reference path
4785                    if matches!(target, DialectType::Redshift) {
4786                        crate::transforms::strip_unnest_column_refs(result)?
4787                    } else {
4788                        result
4789                    }
4790                } else {
4791                    normalized
4792                };
4793
4794                // For Presto/Trino targets from PostgreSQL/Redshift source:
4795                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4796                let normalized = if matches!(
4797                    self.dialect_type,
4798                    DialectType::PostgreSQL | DialectType::Redshift
4799                ) && matches!(
4800                    target,
4801                    DialectType::Presto | DialectType::Trino | DialectType::Athena
4802                ) {
4803                    crate::transforms::wrap_unnest_join_aliases(normalized)?
4804                } else {
4805                    normalized
4806                };
4807
4808                // Eliminate DISTINCT ON with target-dialect awareness
4809                // This must happen after source transform (which may produce DISTINCT ON)
4810                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4811                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4812                    normalized,
4813                    Some(target),
4814                    Some(self.dialect_type),
4815                )?;
4816
4817                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4818                let normalized = if matches!(target, DialectType::Snowflake) {
4819                    Self::transform_generate_date_array_snowflake(normalized)?
4820                } else {
4821                    normalized
4822                };
4823
4824                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4825                let normalized = if matches!(
4826                    target,
4827                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
4828                ) {
4829                    crate::transforms::unnest_to_explode_select(normalized)?
4830                } else {
4831                    normalized
4832                };
4833
4834                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4835                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4836                    crate::transforms::no_limit_order_by_union(normalized)?
4837                } else {
4838                    normalized
4839                };
4840
4841                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4842                // Python sqlglot does this in the TSQL generator, but we can't do it there
4843                // because it would break TSQL -> TSQL identity
4844                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4845                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4846                {
4847                    transform_recursive(normalized, &|e| {
4848                        if let Expression::Count(ref c) = e {
4849                            // Build COUNT_BIG(...) as an AggregateFunction
4850                            let args = if c.star {
4851                                vec![Expression::Star(crate::expressions::Star {
4852                                    table: None,
4853                                    except: None,
4854                                    replace: None,
4855                                    rename: None,
4856                                    trailing_comments: Vec::new(),
4857                                    span: None,
4858                                })]
4859                            } else if let Some(ref this) = c.this {
4860                                vec![this.clone()]
4861                            } else {
4862                                vec![]
4863                            };
4864                            Ok(Expression::AggregateFunction(Box::new(
4865                                crate::expressions::AggregateFunction {
4866                                    name: "COUNT_BIG".to_string(),
4867                                    args,
4868                                    distinct: c.distinct,
4869                                    filter: c.filter.clone(),
4870                                    order_by: Vec::new(),
4871                                    limit: None,
4872                                    ignore_nulls: None,
4873                                    inferred_type: None,
4874                                },
4875                            )))
4876                        } else {
4877                            Ok(e)
4878                        }
4879                    })?
4880                } else {
4881                    normalized
4882                };
4883
4884                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4885                // contexts intact, but materialize boolean-valued expressions used
4886                // as values before target transforms add ORDER BY null sort keys.
4887                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4888                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4889                {
4890                    Self::rewrite_boolean_values_for_tsql(normalized)?
4891                } else {
4892                    normalized
4893                };
4894
4895                let transformed =
4896                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4897
4898                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4899                // remaining filters after target transforms so special aggregate rewrites
4900                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4901                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4902                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4903                } else {
4904                    transformed
4905                };
4906
4907                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4908                let transformed = if matches!(target, DialectType::DuckDB) {
4909                    Self::seq_rownum_to_range(transformed)?
4910                } else {
4911                    transformed
4912                };
4913
4914                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4915
4916                let mut sql = target_dialect.generate_with_transpile_options(
4917                    &transformed,
4918                    self.dialect_type,
4919                    opts,
4920                )?;
4921
4922                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
4923                if opts.pretty && target == DialectType::Snowflake {
4924                    sql = Self::normalize_snowflake_pretty(sql);
4925                }
4926
4927                Ok(sql)
4928            })
4929            .collect()
4930    }
4931}
4932
4933// Transpile-only methods: cross-dialect normalization and helpers
4934#[cfg(feature = "transpile")]
4935impl Dialect {
4936    fn reject_strict_unsupported(
4937        expr: &Expression,
4938        source: DialectType,
4939        target: DialectType,
4940        opts: &TranspileOptions,
4941    ) -> Result<()> {
4942        if !matches!(
4943            opts.unsupported_level,
4944            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4945        ) {
4946            return Ok(());
4947        }
4948
4949        let mut diagnostics = Vec::new();
4950
4951        for node in expr.dfs() {
4952            if matches!(target, DialectType::Fabric | DialectType::Hive)
4953                && Self::node_has_recursive_with(node)
4954            {
4955                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
4956            }
4957
4958            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4959                && Self::node_has_lateral(node)
4960            {
4961                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
4962            }
4963
4964            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
4965                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
4966            }
4967
4968            if !Self::target_supports_remaining_explode(target) && Self::node_is_explode(node) {
4969                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
4970            }
4971
4972            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
4973                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
4974            }
4975
4976            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4977                && Self::node_is_regex_predicate(node)
4978            {
4979                Self::push_unsupported_diagnostic(
4980                    &mut diagnostics,
4981                    "regular expression predicates",
4982                );
4983            }
4984
4985            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4986                && Self::node_is_non_subquery_any(node)
4987            {
4988                Self::push_unsupported_diagnostic(
4989                    &mut diagnostics,
4990                    "ANY over non-subquery expressions",
4991                );
4992            }
4993
4994            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4995                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4996            {
4997                if Self::node_is_function_named(node, "JSONB_BUILD_OBJECT") {
4998                    Self::push_unsupported_diagnostic(
4999                        &mut diagnostics,
5000                        "PostgreSQL JSONB_BUILD_OBJECT",
5001                    );
5002                }
5003                if Self::node_is_function_named(node, "TO_TSVECTOR") {
5004                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5005                }
5006                if matches!(target, DialectType::TSQL | DialectType::Fabric)
5007                    && Self::node_is_postgres_type_function_cast(node)
5008                {
5009                    Self::push_unsupported_diagnostic(
5010                        &mut diagnostics,
5011                        "PostgreSQL type-name function casts",
5012                    );
5013                }
5014            }
5015
5016            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5017                break;
5018            }
5019        }
5020
5021        if diagnostics.is_empty() {
5022            return Ok(());
5023        }
5024
5025        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5026            1
5027        } else {
5028            opts.max_unsupported.max(1)
5029        };
5030        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5031        if diagnostics.len() > limit {
5032            messages.push(format!("... and {} more", diagnostics.len() - limit));
5033        }
5034
5035        Err(crate::error::Error::unsupported(
5036            messages.join("; "),
5037            target.to_string(),
5038        ))
5039    }
5040
5041    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5042        if !diagnostics.iter().any(|existing| existing == message) {
5043            diagnostics.push(message.to_string());
5044        }
5045    }
5046
5047    fn node_has_recursive_with(expr: &Expression) -> bool {
5048        fn recursive(with: &Option<With>) -> bool {
5049            with.as_ref().is_some_and(|with| with.recursive)
5050        }
5051
5052        match expr {
5053            Expression::With(with) => with.recursive,
5054            Expression::Select(select) => recursive(&select.with),
5055            Expression::Union(union) => recursive(&union.with),
5056            Expression::Intersect(intersect) => recursive(&intersect.with),
5057            Expression::Except(except) => recursive(&except.with),
5058            Expression::Pivot(pivot) => recursive(&pivot.with),
5059            Expression::Insert(insert) => recursive(&insert.with),
5060            Expression::Update(update) => recursive(&update.with),
5061            Expression::Delete(delete) => recursive(&delete.with),
5062            _ => false,
5063        }
5064    }
5065
5066    fn node_has_lateral(expr: &Expression) -> bool {
5067        fn join_has_lateral(join: &Join) -> bool {
5068            matches!(
5069                join.kind,
5070                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5071            ) || Dialect::node_has_lateral(&join.this)
5072                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5073                || join
5074                    .match_condition
5075                    .as_ref()
5076                    .is_some_and(Dialect::node_has_lateral)
5077                || join.pivots.iter().any(Dialect::node_has_lateral)
5078        }
5079
5080        fn joins_have_lateral(joins: &[Join]) -> bool {
5081            joins.iter().any(join_has_lateral)
5082        }
5083
5084        match expr {
5085            Expression::Subquery(subquery) => {
5086                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5087            }
5088            Expression::Lateral(_) | Expression::LateralView(_) => true,
5089            Expression::Join(join) => join_has_lateral(join),
5090            Expression::Select(select) => {
5091                !select.lateral_views.is_empty()
5092                    || joins_have_lateral(&select.joins)
5093                    || select
5094                        .from
5095                        .as_ref()
5096                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5097            }
5098            Expression::JoinedTable(joined) => {
5099                !joined.lateral_views.is_empty()
5100                    || Dialect::node_has_lateral(&joined.left)
5101                    || joins_have_lateral(&joined.joins)
5102            }
5103            Expression::Update(update) => {
5104                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5105            }
5106            _ => false,
5107        }
5108    }
5109
5110    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5111        matches!(
5112            target,
5113            DialectType::PostgreSQL
5114                | DialectType::BigQuery
5115                | DialectType::DuckDB
5116                | DialectType::Presto
5117                | DialectType::Trino
5118                | DialectType::Athena
5119        )
5120    }
5121
5122    fn target_supports_remaining_explode(target: DialectType) -> bool {
5123        matches!(
5124            target,
5125            DialectType::Spark | DialectType::Databricks | DialectType::Hive
5126        )
5127    }
5128
5129    fn target_lacks_array_agg(target: DialectType) -> bool {
5130        matches!(
5131            target,
5132            DialectType::Fabric
5133                | DialectType::TSQL
5134                | DialectType::MySQL
5135                | DialectType::SQLite
5136                | DialectType::Oracle
5137        )
5138    }
5139
5140    fn node_is_unnest(expr: &Expression) -> bool {
5141        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
5142    }
5143
5144    fn node_is_explode(expr: &Expression) -> bool {
5145        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
5146            || Self::node_is_function_named(expr, "EXPLODE")
5147            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
5148    }
5149
5150    fn node_is_array_agg(expr: &Expression) -> bool {
5151        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
5152    }
5153
5154    fn node_is_regex_predicate(expr: &Expression) -> bool {
5155        matches!(
5156            expr,
5157            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
5158        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
5159            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
5160            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
5161    }
5162
5163    fn node_is_non_subquery_any(expr: &Expression) -> bool {
5164        matches!(
5165            expr,
5166            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
5167        )
5168    }
5169
5170    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
5171        match expr {
5172            Expression::Select(_) | Expression::Subquery(_) => true,
5173            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
5174            _ => false,
5175        }
5176    }
5177
5178    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
5179        match expr {
5180            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
5181            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
5182            _ => false,
5183        }
5184    }
5185
5186    fn normalize_postgres_type_function_casts(expr: Expression) -> Result<Expression> {
5187        transform_recursive(expr, &|e| match e {
5188            Expression::Function(function) => {
5189                let mut function = *function;
5190                if function.args.len() == 1
5191                    && !function.distinct
5192                    && !function.quoted
5193                    && !function.use_bracket_syntax
5194                    && !function.name.contains('.')
5195                {
5196                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
5197                        let this = function.args.remove(0);
5198                        return Ok(Expression::Cast(Box::new(Cast {
5199                            this,
5200                            to,
5201                            trailing_comments: function.trailing_comments,
5202                            double_colon_syntax: false,
5203                            format: None,
5204                            default: None,
5205                            inferred_type: function.inferred_type,
5206                        })));
5207                    }
5208                }
5209                Ok(Expression::Function(Box::new(function)))
5210            }
5211            _ => Ok(e),
5212        })
5213    }
5214
5215    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
5216        matches!(
5217            expr,
5218            Expression::Function(function)
5219                if !function.quoted
5220                    && !function.use_bracket_syntax
5221                    && !function.name.contains('.')
5222                    && Self::postgres_type_function_data_type(&function.name).is_some()
5223        )
5224    }
5225
5226    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
5227        match name.to_ascii_uppercase().as_str() {
5228            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
5229                precision: None,
5230                scale: None,
5231            }),
5232            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
5233            "INT4" | "INT" => Some(DataType::Int {
5234                length: None,
5235                integer_spelling: false,
5236            }),
5237            "INTEGER" => Some(DataType::Int {
5238                length: None,
5239                integer_spelling: true,
5240            }),
5241            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
5242            "FLOAT4" | "REAL" => Some(DataType::Float {
5243                precision: None,
5244                scale: None,
5245                real_spelling: true,
5246            }),
5247            "FLOAT8" => Some(DataType::Double {
5248                precision: None,
5249                scale: None,
5250            }),
5251            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
5252            "TEXT" => Some(DataType::Text),
5253            "VARCHAR" => Some(DataType::VarChar {
5254                length: None,
5255                parenthesized_length: false,
5256            }),
5257            "UUID" => Some(DataType::Uuid),
5258            _ => None,
5259        }
5260    }
5261
5262    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
5263        match expr {
5264            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
5265            Expression::Subquery(mut subquery) => {
5266                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
5267                Ok(Expression::Subquery(subquery))
5268            }
5269            Expression::Union(mut union) => {
5270                let left = std::mem::replace(&mut union.left, Expression::null());
5271                let right = std::mem::replace(&mut union.right, Expression::null());
5272                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
5273                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
5274                if let Some(mut with) = union.with.take() {
5275                    with.ctes = with
5276                        .ctes
5277                        .into_iter()
5278                        .map(|mut cte| {
5279                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
5280                            Ok(cte)
5281                        })
5282                        .collect::<Result<Vec<_>>>()?;
5283                    union.with = Some(with);
5284                }
5285                Ok(Expression::Union(union))
5286            }
5287            Expression::Intersect(mut intersect) => {
5288                let left = std::mem::replace(&mut intersect.left, Expression::null());
5289                let right = std::mem::replace(&mut intersect.right, Expression::null());
5290                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
5291                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
5292                Ok(Expression::Intersect(intersect))
5293            }
5294            Expression::Except(mut except) => {
5295                let left = std::mem::replace(&mut except.left, Expression::null());
5296                let right = std::mem::replace(&mut except.right, Expression::null());
5297                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
5298                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
5299                Ok(Expression::Except(except))
5300            }
5301            other => Self::rewrite_tsql_boolean_embedded_queries(other),
5302        }
5303    }
5304
5305    fn rewrite_boolean_values_in_tsql_select(
5306        mut select: Box<crate::expressions::Select>,
5307    ) -> Result<Expression> {
5308        if let Some(mut with) = select.with.take() {
5309            with.ctes = with
5310                .ctes
5311                .into_iter()
5312                .map(|mut cte| {
5313                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
5314                    Ok(cte)
5315                })
5316                .collect::<Result<Vec<_>>>()?;
5317            select.with = Some(with);
5318        }
5319
5320        select.expressions = select
5321            .expressions
5322            .into_iter()
5323            .map(Self::rewrite_tsql_boolean_scalar_value)
5324            .collect::<Result<Vec<_>>>()?;
5325
5326        if let Some(mut from) = select.from.take() {
5327            from.expressions = from
5328                .expressions
5329                .into_iter()
5330                .map(Self::rewrite_tsql_boolean_embedded_queries)
5331                .collect::<Result<Vec<_>>>()?;
5332            select.from = Some(from);
5333        }
5334
5335        select.joins = select
5336            .joins
5337            .into_iter()
5338            .map(|mut join| {
5339                join.this = Self::rewrite_tsql_boolean_embedded_queries(join.this)?;
5340                if let Some(on) = join.on.take() {
5341                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
5342                }
5343                if let Some(match_condition) = join.match_condition.take() {
5344                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
5345                        match_condition,
5346                    )?);
5347                }
5348                join.pivots = join
5349                    .pivots
5350                    .into_iter()
5351                    .map(Self::rewrite_tsql_boolean_embedded_queries)
5352                    .collect::<Result<Vec<_>>>()?;
5353                Ok(join)
5354            })
5355            .collect::<Result<Vec<_>>>()?;
5356
5357        select.lateral_views = select
5358            .lateral_views
5359            .into_iter()
5360            .map(|mut lateral_view| {
5361                lateral_view.this = Self::rewrite_tsql_boolean_embedded_queries(lateral_view.this)?;
5362                Ok(lateral_view)
5363            })
5364            .collect::<Result<Vec<_>>>()?;
5365
5366        if let Some(prewhere) = select.prewhere.take() {
5367            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
5368        }
5369
5370        if let Some(mut where_clause) = select.where_clause.take() {
5371            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
5372            select.where_clause = Some(where_clause);
5373        }
5374
5375        if let Some(mut group_by) = select.group_by.take() {
5376            group_by.expressions = group_by
5377                .expressions
5378                .into_iter()
5379                .map(Self::rewrite_tsql_boolean_scalar_value)
5380                .collect::<Result<Vec<_>>>()?;
5381            select.group_by = Some(group_by);
5382        }
5383
5384        if let Some(mut having) = select.having.take() {
5385            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
5386            select.having = Some(having);
5387        }
5388
5389        if let Some(mut qualify) = select.qualify.take() {
5390            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
5391            select.qualify = Some(qualify);
5392        }
5393
5394        if let Some(mut order_by) = select.order_by.take() {
5395            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
5396            select.order_by = Some(order_by);
5397        }
5398
5399        if let Some(mut distribute_by) = select.distribute_by.take() {
5400            distribute_by.expressions = distribute_by
5401                .expressions
5402                .into_iter()
5403                .map(Self::rewrite_tsql_boolean_scalar_value)
5404                .collect::<Result<Vec<_>>>()?;
5405            select.distribute_by = Some(distribute_by);
5406        }
5407
5408        if let Some(mut cluster_by) = select.cluster_by.take() {
5409            cluster_by.expressions =
5410                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
5411            select.cluster_by = Some(cluster_by);
5412        }
5413
5414        if let Some(mut sort_by) = select.sort_by.take() {
5415            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
5416            select.sort_by = Some(sort_by);
5417        }
5418
5419        if let Some(limit_by) = select.limit_by.take() {
5420            select.limit_by = Some(
5421                limit_by
5422                    .into_iter()
5423                    .map(Self::rewrite_tsql_boolean_scalar_value)
5424                    .collect::<Result<Vec<_>>>()?,
5425            );
5426        }
5427
5428        if let Some(distinct_on) = select.distinct_on.take() {
5429            select.distinct_on = Some(
5430                distinct_on
5431                    .into_iter()
5432                    .map(Self::rewrite_tsql_boolean_scalar_value)
5433                    .collect::<Result<Vec<_>>>()?,
5434            );
5435        }
5436
5437        if let Some(mut sample) = select.sample.take() {
5438            sample.size = Self::rewrite_tsql_boolean_embedded_queries(sample.size)?;
5439            if let Some(offset) = sample.offset.take() {
5440                sample.offset = Some(Self::rewrite_tsql_boolean_embedded_queries(offset)?);
5441            }
5442            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
5443                sample.bucket_numerator = Some(Box::new(
5444                    Self::rewrite_tsql_boolean_embedded_queries(*bucket_numerator)?,
5445                ));
5446            }
5447            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
5448                sample.bucket_denominator = Some(Box::new(
5449                    Self::rewrite_tsql_boolean_embedded_queries(*bucket_denominator)?,
5450                ));
5451            }
5452            if let Some(bucket_field) = sample.bucket_field.take() {
5453                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
5454                    *bucket_field,
5455                )?));
5456            }
5457            select.sample = Some(sample);
5458        }
5459
5460        if let Some(settings) = select.settings.take() {
5461            select.settings = Some(
5462                settings
5463                    .into_iter()
5464                    .map(Self::rewrite_tsql_boolean_embedded_queries)
5465                    .collect::<Result<Vec<_>>>()?,
5466            );
5467        }
5468
5469        if let Some(format) = select.format.take() {
5470            select.format = Some(Self::rewrite_tsql_boolean_embedded_queries(format)?);
5471        }
5472
5473        if let Some(mut windows) = select.windows.take() {
5474            for window in windows.iter_mut() {
5475                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
5476            }
5477            select.windows = Some(windows);
5478        }
5479
5480        Ok(Expression::Select(select))
5481    }
5482
5483    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
5484        if Self::is_tsql_boolean_value_expression(&expr) {
5485            return Ok(Self::tsql_boolean_value_case(expr));
5486        }
5487
5488        match expr {
5489            Expression::Alias(mut alias) => {
5490                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
5491                Ok(Expression::Alias(alias))
5492            }
5493            Expression::Paren(mut paren) => {
5494                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
5495                Ok(Expression::Paren(paren))
5496            }
5497            Expression::Cast(mut cast) => {
5498                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
5499                if let Some(format) = cast.format.take() {
5500                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
5501                        *format,
5502                    )?));
5503                }
5504                if let Some(default) = cast.default.take() {
5505                    cast.default =
5506                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
5507                }
5508                Ok(Expression::Cast(cast))
5509            }
5510            Expression::TryCast(mut cast) => {
5511                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
5512                if let Some(format) = cast.format.take() {
5513                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
5514                        *format,
5515                    )?));
5516                }
5517                if let Some(default) = cast.default.take() {
5518                    cast.default =
5519                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
5520                }
5521                Ok(Expression::TryCast(cast))
5522            }
5523            Expression::SafeCast(mut cast) => {
5524                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
5525                if let Some(format) = cast.format.take() {
5526                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
5527                        *format,
5528                    )?));
5529                }
5530                if let Some(default) = cast.default.take() {
5531                    cast.default =
5532                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
5533                }
5534                Ok(Expression::SafeCast(cast))
5535            }
5536            Expression::Case(mut case) => {
5537                if let Some(operand) = case.operand.take() {
5538                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
5539                }
5540                case.whens = case
5541                    .whens
5542                    .into_iter()
5543                    .map(|(condition, result)| {
5544                        Ok((
5545                            Self::rewrite_tsql_boolean_predicate_context(condition)?,
5546                            Self::rewrite_tsql_boolean_scalar_value(result)?,
5547                        ))
5548                    })
5549                    .collect::<Result<Vec<_>>>()?;
5550                if let Some(else_) = case.else_.take() {
5551                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
5552                }
5553                Ok(Expression::Case(case))
5554            }
5555            Expression::IfFunc(mut if_func) => {
5556                if_func.condition =
5557                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
5558                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
5559                if let Some(false_value) = if_func.false_value.take() {
5560                    if_func.false_value =
5561                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
5562                }
5563                Ok(Expression::IfFunc(if_func))
5564            }
5565            Expression::WindowFunction(mut window_function) => {
5566                window_function.this =
5567                    Self::rewrite_tsql_boolean_embedded_queries(window_function.this)?;
5568                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
5569                if let Some(mut keep) = window_function.keep.take() {
5570                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
5571                    window_function.keep = Some(keep);
5572                }
5573                Ok(Expression::WindowFunction(window_function))
5574            }
5575            Expression::WithinGroup(mut within_group) => {
5576                within_group.this = Self::rewrite_tsql_boolean_embedded_queries(within_group.this)?;
5577                within_group.order_by =
5578                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
5579                Ok(Expression::WithinGroup(within_group))
5580            }
5581            Expression::Subquery(mut subquery) => {
5582                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
5583                Ok(Expression::Subquery(subquery))
5584            }
5585            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
5586            other => Self::rewrite_tsql_boolean_embedded_queries(other),
5587        }
5588    }
5589
5590    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
5591        Self::rewrite_tsql_boolean_embedded_queries(expr)
5592    }
5593
5594    fn rewrite_tsql_boolean_embedded_queries(expr: Expression) -> Result<Expression> {
5595        transform_recursive(expr, &|e| match e {
5596            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
5597            Expression::Subquery(mut subquery) => {
5598                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
5599                Ok(Expression::Subquery(subquery))
5600            }
5601            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
5602                Self::rewrite_boolean_values_for_tsql(e)
5603            }
5604            other => Ok(other),
5605        })
5606    }
5607
5608    fn rewrite_tsql_boolean_ordered_values(
5609        ordered: Vec<crate::expressions::Ordered>,
5610    ) -> Result<Vec<crate::expressions::Ordered>> {
5611        ordered
5612            .into_iter()
5613            .map(|mut ordered| {
5614                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
5615                if let Some(with_fill) = ordered.with_fill.take() {
5616                    ordered.with_fill = Some(Box::new(
5617                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
5618                    ));
5619                }
5620                Ok(ordered)
5621            })
5622            .collect()
5623    }
5624
5625    fn rewrite_tsql_boolean_with_fill_values(
5626        mut with_fill: crate::expressions::WithFill,
5627    ) -> Result<crate::expressions::WithFill> {
5628        if let Some(from) = with_fill.from_.take() {
5629            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
5630        }
5631        if let Some(to) = with_fill.to.take() {
5632            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
5633        }
5634        if let Some(step) = with_fill.step.take() {
5635            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
5636        }
5637        if let Some(staleness) = with_fill.staleness.take() {
5638            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
5639                *staleness,
5640            )?));
5641        }
5642        if let Some(interpolate) = with_fill.interpolate.take() {
5643            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
5644                *interpolate,
5645            )?));
5646        }
5647        Ok(with_fill)
5648    }
5649
5650    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
5651        over.partition_by = std::mem::take(&mut over.partition_by)
5652            .into_iter()
5653            .map(Self::rewrite_tsql_boolean_scalar_value)
5654            .collect::<Result<Vec<_>>>()?;
5655        over.order_by =
5656            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
5657        Ok(())
5658    }
5659
5660    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
5661        match expr {
5662            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
5663            Expression::Eq(_)
5664            | Expression::Neq(_)
5665            | Expression::Lt(_)
5666            | Expression::Lte(_)
5667            | Expression::Gt(_)
5668            | Expression::Gte(_)
5669            | Expression::Is(_)
5670            | Expression::IsNull(_)
5671            | Expression::IsTrue(_)
5672            | Expression::IsFalse(_)
5673            | Expression::Like(_)
5674            | Expression::ILike(_)
5675            | Expression::SimilarTo(_)
5676            | Expression::Glob(_)
5677            | Expression::RegexpLike(_)
5678            | Expression::In(_)
5679            | Expression::Between(_)
5680            | Expression::Exists(_)
5681            | Expression::And(_)
5682            | Expression::Or(_)
5683            | Expression::Not(_)
5684            | Expression::Any(_)
5685            | Expression::All(_)
5686            | Expression::EqualNull(_) => true,
5687            _ => false,
5688        }
5689    }
5690
5691    fn tsql_boolean_value_case(predicate: Expression) -> Expression {
5692        Expression::Case(Box::new(crate::expressions::Case {
5693            operand: None,
5694            whens: vec![
5695                (predicate.clone(), Expression::number(1)),
5696                (
5697                    Expression::Not(Box::new(crate::expressions::UnaryOp {
5698                        this: predicate,
5699                        inferred_type: None,
5700                    })),
5701                    Expression::number(0),
5702                ),
5703            ],
5704            else_: None,
5705            comments: Vec::new(),
5706            inferred_type: None,
5707        }))
5708    }
5709
5710    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
5711        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
5712    }
5713
5714    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
5715        macro_rules! rewrite_agg_filter {
5716            ($variant:ident, $agg:expr) => {{
5717                let mut agg = $agg;
5718                if let Some(filter) = agg.filter.take() {
5719                    let this = std::mem::replace(&mut agg.this, Expression::null());
5720                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5721                }
5722                Ok(Expression::$variant(agg))
5723            }};
5724        }
5725
5726        match expr {
5727            Expression::Filter(filter) => {
5728                let condition = match *filter.expression {
5729                    Expression::Where(where_) => where_.this,
5730                    other => other,
5731                };
5732                Ok(Self::push_filter_into_tsql_aggregate(
5733                    *filter.this,
5734                    condition,
5735                ))
5736            }
5737            Expression::AggregateFunction(mut agg) => {
5738                if let Some(filter) = agg.filter.take() {
5739                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
5740                }
5741                Ok(Expression::AggregateFunction(agg))
5742            }
5743            Expression::Count(mut count) => {
5744                if let Some(filter) = count.filter.take() {
5745                    let value = if count.star {
5746                        Expression::number(1)
5747                    } else {
5748                        count.this.take().unwrap_or_else(|| Expression::number(1))
5749                    };
5750                    count.star = false;
5751                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
5752                }
5753                Ok(Expression::Count(count))
5754            }
5755            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
5756            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
5757            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
5758            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
5759            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
5760            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
5761            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
5762            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
5763            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
5764            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
5765            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
5766            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
5767            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
5768            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
5769            Expression::First(agg) => rewrite_agg_filter!(First, agg),
5770            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
5771            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
5772            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
5773            Expression::ApproxCountDistinct(agg) => {
5774                rewrite_agg_filter!(ApproxCountDistinct, agg)
5775            }
5776            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
5777            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
5778            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
5779            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
5780            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
5781            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
5782            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
5783            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
5784            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
5785            Expression::StringAgg(mut agg) => {
5786                if let Some(filter) = agg.filter.take() {
5787                    let this = std::mem::replace(&mut agg.this, Expression::null());
5788                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5789                }
5790                Ok(Expression::StringAgg(agg))
5791            }
5792            Expression::GroupConcat(mut agg) => {
5793                if let Some(filter) = agg.filter.take() {
5794                    let this = std::mem::replace(&mut agg.this, Expression::null());
5795                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5796                }
5797                Ok(Expression::GroupConcat(agg))
5798            }
5799            Expression::ListAgg(mut agg) => {
5800                if let Some(filter) = agg.filter.take() {
5801                    let this = std::mem::replace(&mut agg.this, Expression::null());
5802                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5803                }
5804                Ok(Expression::ListAgg(agg))
5805            }
5806            Expression::WithinGroup(mut within_group) => {
5807                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
5808                Ok(Expression::WithinGroup(within_group))
5809            }
5810            other => Ok(other),
5811        }
5812    }
5813
5814    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
5815        macro_rules! push_agg_filter {
5816            ($variant:ident, $agg:expr) => {{
5817                let mut agg = $agg;
5818                let this = std::mem::replace(&mut agg.this, Expression::null());
5819                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5820                agg.filter = None;
5821                Expression::$variant(agg)
5822            }};
5823        }
5824
5825        match expr {
5826            Expression::AggregateFunction(mut agg) => {
5827                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
5828                Expression::AggregateFunction(agg)
5829            }
5830            Expression::Count(mut count) => {
5831                let value = if count.star {
5832                    Expression::number(1)
5833                } else {
5834                    count.this.take().unwrap_or_else(|| Expression::number(1))
5835                };
5836                count.star = false;
5837                count.filter = None;
5838                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
5839                Expression::Count(count)
5840            }
5841            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
5842            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
5843            Expression::Min(agg) => push_agg_filter!(Min, agg),
5844            Expression::Max(agg) => push_agg_filter!(Max, agg),
5845            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
5846            Expression::CountIf(mut agg) => {
5847                agg.filter = Some(filter);
5848                Expression::CountIf(agg)
5849            }
5850            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
5851            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
5852            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
5853            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
5854            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
5855            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
5856            Expression::Median(agg) => push_agg_filter!(Median, agg),
5857            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
5858            Expression::First(agg) => push_agg_filter!(First, agg),
5859            Expression::Last(agg) => push_agg_filter!(Last, agg),
5860            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
5861            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
5862            Expression::ApproxCountDistinct(agg) => {
5863                push_agg_filter!(ApproxCountDistinct, agg)
5864            }
5865            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
5866            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
5867            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
5868            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
5869            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
5870            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
5871            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
5872            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
5873            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
5874            Expression::StringAgg(mut agg) => {
5875                let this = std::mem::replace(&mut agg.this, Expression::null());
5876                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5877                agg.filter = None;
5878                Expression::StringAgg(agg)
5879            }
5880            Expression::GroupConcat(mut agg) => {
5881                let this = std::mem::replace(&mut agg.this, Expression::null());
5882                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5883                agg.filter = None;
5884                Expression::GroupConcat(agg)
5885            }
5886            Expression::ListAgg(mut agg) => {
5887                let this = std::mem::replace(&mut agg.this, Expression::null());
5888                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
5889                agg.filter = None;
5890                Expression::ListAgg(agg)
5891            }
5892            Expression::WithinGroup(mut within_group) => {
5893                within_group.this =
5894                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
5895                Expression::WithinGroup(within_group)
5896            }
5897            other => Expression::Filter(Box::new(crate::expressions::Filter {
5898                this: Box::new(other),
5899                expression: Box::new(filter),
5900            })),
5901        }
5902    }
5903
5904    fn rewrite_generic_aggregate_filter_for_tsql(
5905        agg: &mut crate::expressions::AggregateFunction,
5906        filter: Expression,
5907    ) {
5908        let is_count =
5909            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
5910        let is_count_star = is_count
5911            && (agg.args.is_empty()
5912                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
5913
5914        if is_count_star {
5915            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
5916                filter,
5917                Expression::number(1),
5918            )];
5919        } else if !agg.args.is_empty() {
5920            agg.args = agg
5921                .args
5922                .drain(..)
5923                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
5924                .collect();
5925        } else {
5926            agg.filter = Some(filter);
5927        }
5928    }
5929
5930    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
5931        Expression::Case(Box::new(crate::expressions::Case {
5932            operand: None,
5933            whens: vec![(filter, value)],
5934            else_: None,
5935            comments: Vec::new(),
5936            inferred_type: None,
5937        }))
5938    }
5939
5940    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
5941        let tokens = self.tokenize(sql)?;
5942        for (i, token) in tokens.iter().enumerate() {
5943            if token.token_type == TokenType::NullsafeEq {
5944                return Err(crate::error::Error::unsupported(
5945                    "PostgreSQL pgvector cosine distance operator <=>",
5946                    "SQLite",
5947                ));
5948            }
5949            if token.token_type == TokenType::Lt
5950                && tokens
5951                    .get(i + 1)
5952                    .is_some_and(|token| token.token_type == TokenType::Tilde)
5953                && tokens
5954                    .get(i + 2)
5955                    .is_some_and(|token| token.token_type == TokenType::Gt)
5956            {
5957                return Err(crate::error::Error::unsupported(
5958                    "PostgreSQL pgvector Hamming distance operator <~>",
5959                    "SQLite",
5960                ));
5961            }
5962        }
5963        Ok(())
5964    }
5965
5966    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
5967        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
5968            transform_recursive(expr, &|e| match e {
5969                Expression::Column(col)
5970                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
5971                {
5972                    Ok(Expression::Literal(Box::new(Literal::String(
5973                        col.name.name,
5974                    ))))
5975                }
5976                Expression::Identifier(id) if id.quoted => {
5977                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
5978                }
5979                _ => Ok(e),
5980            })
5981        }
5982
5983        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
5984            if let Some(default) = col.default.take() {
5985                col.default = Some(normalize_default_expr(default)?);
5986            }
5987
5988            for constraint in &mut col.constraints {
5989                if let ColumnConstraint::Default(default) = constraint {
5990                    *default = normalize_default_expr(default.clone())?;
5991                }
5992            }
5993
5994            Ok(())
5995        }
5996
5997        transform_recursive(expr, &|e| match e {
5998            Expression::CreateTable(mut ct) => {
5999                for column in &mut ct.columns {
6000                    normalize_column_default(column)?;
6001                }
6002                Ok(Expression::CreateTable(ct))
6003            }
6004            Expression::ColumnDef(mut col) => {
6005                normalize_column_default(&mut col)?;
6006                Ok(Expression::ColumnDef(col))
6007            }
6008            _ => Ok(e),
6009        })
6010    }
6011
6012    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
6013        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
6014            use crate::expressions::DataType;
6015
6016            match dt {
6017                DataType::Bit { .. } => DataType::Int {
6018                    length: None,
6019                    integer_spelling: true,
6020                },
6021                DataType::TextWithLength { .. } => DataType::Text,
6022                DataType::VarChar { .. } => DataType::Text,
6023                DataType::Char { .. } => DataType::Text,
6024                DataType::Timestamp { timezone: true, .. } => DataType::Text,
6025                DataType::Custom { name } => {
6026                    let base = name
6027                        .split_once('(')
6028                        .map_or(name.as_str(), |(base, _)| base)
6029                        .trim();
6030                    if base.eq_ignore_ascii_case("TSVECTOR")
6031                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
6032                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
6033                        || base.eq_ignore_ascii_case("NVARCHAR")
6034                        || base.eq_ignore_ascii_case("NCHAR")
6035                    {
6036                        DataType::Text
6037                    } else {
6038                        DataType::Custom { name }
6039                    }
6040                }
6041                _ => dt,
6042            }
6043        }
6044
6045        transform_recursive(expr, &|e| match e {
6046            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
6047            Expression::CreateTable(mut ct) => {
6048                for column in &mut ct.columns {
6049                    column.data_type = sqlite_type(column.data_type.clone());
6050                }
6051                Ok(Expression::CreateTable(ct))
6052            }
6053            _ => Ok(e),
6054        })
6055    }
6056
6057    fn normalize_postgres_to_fabric_decimal_types(expr: Expression) -> Result<Expression> {
6058        fn fabric_decimal_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
6059            use crate::expressions::DataType;
6060
6061            match dt {
6062                DataType::Decimal {
6063                    precision: None,
6064                    scale: None,
6065                } => DataType::Decimal {
6066                    precision: Some(38),
6067                    scale: Some(10),
6068                },
6069                _ => dt,
6070            }
6071        }
6072
6073        transform_recursive(expr, &|e| match e {
6074            Expression::DataType(dt) => Ok(Expression::DataType(fabric_decimal_type(dt))),
6075            Expression::CreateTable(mut ct) => {
6076                for column in &mut ct.columns {
6077                    column.data_type = fabric_decimal_type(column.data_type.clone());
6078                }
6079                Ok(Expression::CreateTable(ct))
6080            }
6081            Expression::ColumnDef(mut col) => {
6082                col.data_type = fabric_decimal_type(col.data_type);
6083                Ok(Expression::ColumnDef(col))
6084            }
6085            _ => Ok(e),
6086        })
6087    }
6088
6089    /// For DuckDB target: when FROM clause contains RANGE(n), replace
6090    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
6091    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
6092    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
6093        if let Expression::Select(mut select) = expr {
6094            // Check if FROM contains a RANGE function
6095            let has_range_from = if let Some(ref from) = select.from {
6096                from.expressions.iter().any(|e| {
6097                    // Check for direct RANGE(...) or aliased RANGE(...)
6098                    match e {
6099                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
6100                        Expression::Alias(a) => {
6101                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
6102                        }
6103                        _ => false,
6104                    }
6105                })
6106            } else {
6107                false
6108            };
6109
6110            if has_range_from {
6111                // Replace the ROW_NUMBER pattern in select expressions
6112                select.expressions = select
6113                    .expressions
6114                    .into_iter()
6115                    .map(|e| Self::replace_rownum_with_range(e))
6116                    .collect();
6117            }
6118
6119            Ok(Expression::Select(select))
6120        } else {
6121            Ok(expr)
6122        }
6123    }
6124
6125    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
6126    fn replace_rownum_with_range(expr: Expression) -> Expression {
6127        match expr {
6128            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
6129            Expression::Mod(op) => {
6130                let new_left = Self::try_replace_rownum_paren(&op.left);
6131                Expression::Mod(Box::new(crate::expressions::BinaryOp {
6132                    left: new_left,
6133                    right: op.right,
6134                    left_comments: op.left_comments,
6135                    operator_comments: op.operator_comments,
6136                    trailing_comments: op.trailing_comments,
6137                    inferred_type: op.inferred_type,
6138                }))
6139            }
6140            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
6141            Expression::Paren(p) => {
6142                let inner = Self::replace_rownum_with_range(p.this);
6143                Expression::Paren(Box::new(crate::expressions::Paren {
6144                    this: inner,
6145                    trailing_comments: p.trailing_comments,
6146                }))
6147            }
6148            Expression::Case(mut c) => {
6149                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
6150                c.whens = c
6151                    .whens
6152                    .into_iter()
6153                    .map(|(cond, then)| {
6154                        (
6155                            Self::replace_rownum_with_range(cond),
6156                            Self::replace_rownum_with_range(then),
6157                        )
6158                    })
6159                    .collect();
6160                if let Some(else_) = c.else_ {
6161                    c.else_ = Some(Self::replace_rownum_with_range(else_));
6162                }
6163                Expression::Case(c)
6164            }
6165            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
6166                left: Self::replace_rownum_with_range(op.left),
6167                right: op.right,
6168                left_comments: op.left_comments,
6169                operator_comments: op.operator_comments,
6170                trailing_comments: op.trailing_comments,
6171                inferred_type: op.inferred_type,
6172            })),
6173            Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
6174                left: Self::replace_rownum_with_range(op.left),
6175                right: op.right,
6176                left_comments: op.left_comments,
6177                operator_comments: op.operator_comments,
6178                trailing_comments: op.trailing_comments,
6179                inferred_type: op.inferred_type,
6180            })),
6181            Expression::Alias(mut a) => {
6182                a.this = Self::replace_rownum_with_range(a.this);
6183                Expression::Alias(a)
6184            }
6185            other => other,
6186        }
6187    }
6188
6189    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
6190    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
6191        if let Expression::Paren(ref p) = expr {
6192            if let Expression::Sub(ref sub) = p.this {
6193                if let Expression::WindowFunction(ref wf) = sub.left {
6194                    if let Expression::Function(ref f) = wf.this {
6195                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
6196                            if let Expression::Literal(ref lit) = sub.right {
6197                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
6198                                    if n == "1" {
6199                                        return Expression::column("range");
6200                                    }
6201                                }
6202                            }
6203                        }
6204                    }
6205                }
6206            }
6207        }
6208        expr.clone()
6209    }
6210
6211    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
6212    /// Converts:
6213    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
6214    /// To:
6215    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
6216    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
6217    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
6218        use crate::expressions::*;
6219        transform_recursive(expr, &|e| {
6220            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
6221            if let Expression::ArraySize(ref af) = e {
6222                if let Expression::Function(ref f) = af.this {
6223                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
6224                        let result = Self::convert_array_size_gda_snowflake(f)?;
6225                        return Ok(result);
6226                    }
6227                }
6228            }
6229
6230            let Expression::Select(mut sel) = e else {
6231                return Ok(e);
6232            };
6233
6234            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
6235            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
6236            let mut gda_join_idx: Option<usize> = None;
6237
6238            for (idx, join) in sel.joins.iter().enumerate() {
6239                // The join.this may be:
6240                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
6241                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
6242                let (unnest_ref, alias_name) = match &join.this {
6243                    Expression::Unnest(ref unnest) => {
6244                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
6245                        (Some(unnest.as_ref()), alias)
6246                    }
6247                    Expression::Alias(ref a) => {
6248                        if let Expression::Unnest(ref unnest) = a.this {
6249                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
6250                        } else {
6251                            (None, None)
6252                        }
6253                    }
6254                    _ => (None, None),
6255                };
6256
6257                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
6258                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
6259                    if let Expression::Function(ref f) = unnest.this {
6260                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
6261                            let start_expr = f.args[0].clone();
6262                            let end_expr = f.args[1].clone();
6263                            let step = f.args.get(2).cloned();
6264
6265                            // Extract unit from step interval
6266                            let unit = if let Some(Expression::Interval(ref iv)) = step {
6267                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
6268                                    Some(format!("{:?}", unit).to_ascii_uppercase())
6269                                } else if let Some(ref this) = iv.this {
6270                                    // The interval may be stored as a string like "1 MONTH"
6271                                    if let Expression::Literal(lit) = this {
6272                                        if let Literal::String(ref s) = lit.as_ref() {
6273                                            let parts: Vec<&str> = s.split_whitespace().collect();
6274                                            if parts.len() == 2 {
6275                                                Some(parts[1].to_ascii_uppercase())
6276                                            } else if parts.len() == 1 {
6277                                                // Single word like "MONTH" or just "1"
6278                                                let upper = parts[0].to_ascii_uppercase();
6279                                                if matches!(
6280                                                    upper.as_str(),
6281                                                    "YEAR"
6282                                                        | "QUARTER"
6283                                                        | "MONTH"
6284                                                        | "WEEK"
6285                                                        | "DAY"
6286                                                        | "HOUR"
6287                                                        | "MINUTE"
6288                                                        | "SECOND"
6289                                                ) {
6290                                                    Some(upper)
6291                                                } else {
6292                                                    None
6293                                                }
6294                                            } else {
6295                                                None
6296                                            }
6297                                        } else {
6298                                            None
6299                                        }
6300                                    } else {
6301                                        None
6302                                    }
6303                                } else {
6304                                    None
6305                                }
6306                            } else {
6307                                None
6308                            };
6309
6310                            if let Some(unit_str) = unit {
6311                                gda_info = Some((alias, start_expr, end_expr, unit_str));
6312                                gda_join_idx = Some(idx);
6313                            }
6314                        }
6315                    }
6316                }
6317                if gda_info.is_some() {
6318                    break;
6319                }
6320            }
6321
6322            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
6323                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
6324                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
6325                let result = Self::try_transform_from_gda_snowflake(sel);
6326                return result;
6327            };
6328            let join_idx = gda_join_idx.unwrap();
6329
6330            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
6331            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
6332            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
6333            let datediff = Expression::Function(Box::new(Function::new(
6334                "DATEDIFF".to_string(),
6335                vec![
6336                    Expression::boxed_column(Column {
6337                        name: Identifier::new(&unit_str),
6338                        table: None,
6339                        join_mark: false,
6340                        trailing_comments: vec![],
6341                        span: None,
6342                        inferred_type: None,
6343                    }),
6344                    start_expr.clone(),
6345                    end_expr.clone(),
6346                ],
6347            )));
6348            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
6349                left: datediff,
6350                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
6351                left_comments: vec![],
6352                operator_comments: vec![],
6353                trailing_comments: vec![],
6354                inferred_type: None,
6355            }));
6356
6357            let array_gen_range = Expression::Function(Box::new(Function::new(
6358                "ARRAY_GENERATE_RANGE".to_string(),
6359                vec![
6360                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
6361                    datediff_plus_one,
6362                ],
6363            )));
6364
6365            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
6366            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
6367                name: Identifier::new("INPUT"),
6368                value: array_gen_range,
6369                separator: crate::expressions::NamedArgSeparator::DArrow,
6370            }));
6371            let flatten = Expression::Function(Box::new(Function::new(
6372                "FLATTEN".to_string(),
6373                vec![flatten_input],
6374            )));
6375
6376            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
6377            let alias_table = Alias {
6378                this: flatten,
6379                alias: Identifier::new("_t0"),
6380                column_aliases: vec![
6381                    Identifier::new("seq"),
6382                    Identifier::new("key"),
6383                    Identifier::new("path"),
6384                    Identifier::new("index"),
6385                    Identifier::new(&alias_name),
6386                    Identifier::new("this"),
6387                ],
6388                alias_explicit_as: false,
6389                alias_keyword: None,
6390                pre_alias_comments: vec![],
6391                trailing_comments: vec![],
6392                inferred_type: None,
6393            };
6394            let lateral_expr = Expression::Lateral(Box::new(Lateral {
6395                this: Box::new(Expression::Alias(Box::new(alias_table))),
6396                view: None,
6397                outer: None,
6398                alias: None,
6399                alias_quoted: false,
6400                cross_apply: None,
6401                ordinality: None,
6402                column_aliases: vec![],
6403            }));
6404
6405            // Remove the original join and add to FROM expressions
6406            sel.joins.remove(join_idx);
6407            if let Some(ref mut from) = sel.from {
6408                from.expressions.push(lateral_expr);
6409            }
6410
6411            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
6412            let dateadd_expr = Expression::Function(Box::new(Function::new(
6413                "DATEADD".to_string(),
6414                vec![
6415                    Expression::boxed_column(Column {
6416                        name: Identifier::new(&unit_str),
6417                        table: None,
6418                        join_mark: false,
6419                        trailing_comments: vec![],
6420                        span: None,
6421                        inferred_type: None,
6422                    }),
6423                    Expression::Cast(Box::new(Cast {
6424                        this: Expression::boxed_column(Column {
6425                            name: Identifier::new(&alias_name),
6426                            table: None,
6427                            join_mark: false,
6428                            trailing_comments: vec![],
6429                            span: None,
6430                            inferred_type: None,
6431                        }),
6432                        to: DataType::Int {
6433                            length: None,
6434                            integer_spelling: false,
6435                        },
6436                        trailing_comments: vec![],
6437                        double_colon_syntax: false,
6438                        format: None,
6439                        default: None,
6440                        inferred_type: None,
6441                    })),
6442                    Expression::Cast(Box::new(Cast {
6443                        this: start_expr.clone(),
6444                        to: DataType::Date,
6445                        trailing_comments: vec![],
6446                        double_colon_syntax: false,
6447                        format: None,
6448                        default: None,
6449                        inferred_type: None,
6450                    })),
6451                ],
6452            )));
6453
6454            // Replace references to the alias in the SELECT list
6455            let new_exprs: Vec<Expression> = sel
6456                .expressions
6457                .iter()
6458                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
6459                .collect();
6460            sel.expressions = new_exprs;
6461
6462            Ok(Expression::Select(sel))
6463        })
6464    }
6465
6466    /// Helper: replace column references to `alias_name` with dateadd expression
6467    fn replace_column_ref_with_dateadd(
6468        expr: &Expression,
6469        alias_name: &str,
6470        dateadd: &Expression,
6471    ) -> Expression {
6472        use crate::expressions::*;
6473        match expr {
6474            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
6475                // Plain column reference -> DATEADD(...) AS alias_name
6476                Expression::Alias(Box::new(Alias {
6477                    this: dateadd.clone(),
6478                    alias: Identifier::new(alias_name),
6479                    column_aliases: vec![],
6480                    alias_explicit_as: false,
6481                    alias_keyword: None,
6482                    pre_alias_comments: vec![],
6483                    trailing_comments: vec![],
6484                    inferred_type: None,
6485                }))
6486            }
6487            Expression::Alias(a) => {
6488                // Check if the inner expression references the alias
6489                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
6490                Expression::Alias(Box::new(Alias {
6491                    this: new_this,
6492                    alias: a.alias.clone(),
6493                    column_aliases: a.column_aliases.clone(),
6494                    alias_explicit_as: false,
6495                    alias_keyword: None,
6496                    pre_alias_comments: a.pre_alias_comments.clone(),
6497                    trailing_comments: a.trailing_comments.clone(),
6498                    inferred_type: None,
6499                }))
6500            }
6501            _ => expr.clone(),
6502        }
6503    }
6504
6505    /// Helper: replace column references in inner expression (not top-level)
6506    fn replace_column_ref_inner(
6507        expr: &Expression,
6508        alias_name: &str,
6509        dateadd: &Expression,
6510    ) -> Expression {
6511        use crate::expressions::*;
6512        match expr {
6513            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
6514                dateadd.clone()
6515            }
6516            Expression::Add(op) => {
6517                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
6518                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
6519                Expression::Add(Box::new(BinaryOp {
6520                    left,
6521                    right,
6522                    left_comments: op.left_comments.clone(),
6523                    operator_comments: op.operator_comments.clone(),
6524                    trailing_comments: op.trailing_comments.clone(),
6525                    inferred_type: None,
6526                }))
6527            }
6528            Expression::Sub(op) => {
6529                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
6530                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
6531                Expression::Sub(Box::new(BinaryOp {
6532                    left,
6533                    right,
6534                    left_comments: op.left_comments.clone(),
6535                    operator_comments: op.operator_comments.clone(),
6536                    trailing_comments: op.trailing_comments.clone(),
6537                    inferred_type: None,
6538                }))
6539            }
6540            Expression::Mul(op) => {
6541                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
6542                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
6543                Expression::Mul(Box::new(BinaryOp {
6544                    left,
6545                    right,
6546                    left_comments: op.left_comments.clone(),
6547                    operator_comments: op.operator_comments.clone(),
6548                    trailing_comments: op.trailing_comments.clone(),
6549                    inferred_type: None,
6550                }))
6551            }
6552            _ => expr.clone(),
6553        }
6554    }
6555
6556    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
6557    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
6558    fn try_transform_from_gda_snowflake(
6559        mut sel: Box<crate::expressions::Select>,
6560    ) -> Result<Expression> {
6561        use crate::expressions::*;
6562
6563        // Extract GDA info from FROM clause
6564        let mut gda_info: Option<(
6565            usize,
6566            String,
6567            Expression,
6568            Expression,
6569            String,
6570            Option<(String, Vec<Identifier>)>,
6571        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
6572
6573        if let Some(ref from) = sel.from {
6574            for (idx, table_expr) in from.expressions.iter().enumerate() {
6575                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
6576                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
6577                let (unnest_opt, outer_alias_info) = match table_expr {
6578                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
6579                    Expression::Alias(ref a) => {
6580                        if let Expression::Unnest(ref unnest) = a.this {
6581                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
6582                            (Some(unnest.as_ref()), Some(alias_info))
6583                        } else {
6584                            (None, None)
6585                        }
6586                    }
6587                    _ => (None, None),
6588                };
6589
6590                if let Some(unnest) = unnest_opt {
6591                    // Check for GENERATE_DATE_ARRAY function
6592                    let func_opt = match &unnest.this {
6593                        Expression::Function(ref f)
6594                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
6595                                && f.args.len() >= 2 =>
6596                        {
6597                            Some(f)
6598                        }
6599                        // Also check for GenerateSeries (from earlier normalization)
6600                        _ => None,
6601                    };
6602
6603                    if let Some(f) = func_opt {
6604                        let start_expr = f.args[0].clone();
6605                        let end_expr = f.args[1].clone();
6606                        let step = f.args.get(2).cloned();
6607
6608                        // Extract unit and column name
6609                        let unit = Self::extract_interval_unit_str(&step);
6610                        let col_name = outer_alias_info
6611                            .as_ref()
6612                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
6613                            .unwrap_or_else(|| "value".to_string());
6614
6615                        if let Some(unit_str) = unit {
6616                            gda_info = Some((
6617                                idx,
6618                                col_name,
6619                                start_expr,
6620                                end_expr,
6621                                unit_str,
6622                                outer_alias_info,
6623                            ));
6624                            break;
6625                        }
6626                    }
6627                }
6628            }
6629        }
6630
6631        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
6632        else {
6633            return Ok(Expression::Select(sel));
6634        };
6635
6636        // Build the Snowflake subquery:
6637        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
6638        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
6639
6640        // DATEDIFF(unit, start, end)
6641        let datediff = Expression::Function(Box::new(Function::new(
6642            "DATEDIFF".to_string(),
6643            vec![
6644                Expression::boxed_column(Column {
6645                    name: Identifier::new(&unit_str),
6646                    table: None,
6647                    join_mark: false,
6648                    trailing_comments: vec![],
6649                    span: None,
6650                    inferred_type: None,
6651                }),
6652                start_expr.clone(),
6653                end_expr.clone(),
6654            ],
6655        )));
6656        // DATEDIFF(...) + 1
6657        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
6658            left: datediff,
6659            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
6660            left_comments: vec![],
6661            operator_comments: vec![],
6662            trailing_comments: vec![],
6663            inferred_type: None,
6664        }));
6665
6666        let array_gen_range = Expression::Function(Box::new(Function::new(
6667            "ARRAY_GENERATE_RANGE".to_string(),
6668            vec![
6669                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
6670                datediff_plus_one,
6671            ],
6672        )));
6673
6674        // TABLE(FLATTEN(INPUT => ...))
6675        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
6676            name: Identifier::new("INPUT"),
6677            value: array_gen_range,
6678            separator: crate::expressions::NamedArgSeparator::DArrow,
6679        }));
6680        let flatten = Expression::Function(Box::new(Function::new(
6681            "FLATTEN".to_string(),
6682            vec![flatten_input],
6683        )));
6684
6685        // Determine alias name for the table: use outer alias or _t0
6686        let table_alias_name = outer_alias_info
6687            .as_ref()
6688            .map(|(name, _)| name.clone())
6689            .unwrap_or_else(|| "_t0".to_string());
6690
6691        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
6692        let table_func =
6693            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
6694        let flatten_aliased = Expression::Alias(Box::new(Alias {
6695            this: table_func,
6696            alias: Identifier::new(&table_alias_name),
6697            column_aliases: vec![
6698                Identifier::new("seq"),
6699                Identifier::new("key"),
6700                Identifier::new("path"),
6701                Identifier::new("index"),
6702                Identifier::new(&col_name),
6703                Identifier::new("this"),
6704            ],
6705            alias_explicit_as: false,
6706            alias_keyword: None,
6707            pre_alias_comments: vec![],
6708            trailing_comments: vec![],
6709            inferred_type: None,
6710        }));
6711
6712        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
6713        let dateadd_expr = Expression::Function(Box::new(Function::new(
6714            "DATEADD".to_string(),
6715            vec![
6716                Expression::boxed_column(Column {
6717                    name: Identifier::new(&unit_str),
6718                    table: None,
6719                    join_mark: false,
6720                    trailing_comments: vec![],
6721                    span: None,
6722                    inferred_type: None,
6723                }),
6724                Expression::Cast(Box::new(Cast {
6725                    this: Expression::boxed_column(Column {
6726                        name: Identifier::new(&col_name),
6727                        table: None,
6728                        join_mark: false,
6729                        trailing_comments: vec![],
6730                        span: None,
6731                        inferred_type: None,
6732                    }),
6733                    to: DataType::Int {
6734                        length: None,
6735                        integer_spelling: false,
6736                    },
6737                    trailing_comments: vec![],
6738                    double_colon_syntax: false,
6739                    format: None,
6740                    default: None,
6741                    inferred_type: None,
6742                })),
6743                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
6744                start_expr.clone(),
6745            ],
6746        )));
6747        let dateadd_aliased = Expression::Alias(Box::new(Alias {
6748            this: dateadd_expr,
6749            alias: Identifier::new(&col_name),
6750            column_aliases: vec![],
6751            alias_explicit_as: false,
6752            alias_keyword: None,
6753            pre_alias_comments: vec![],
6754            trailing_comments: vec![],
6755            inferred_type: None,
6756        }));
6757
6758        // Build inner SELECT
6759        let mut inner_select = Select::new();
6760        inner_select.expressions = vec![dateadd_aliased];
6761        inner_select.from = Some(From {
6762            expressions: vec![flatten_aliased],
6763        });
6764
6765        let inner_select_expr = Expression::Select(Box::new(inner_select));
6766        let subquery = Expression::Subquery(Box::new(Subquery {
6767            this: inner_select_expr,
6768            alias: None,
6769            column_aliases: vec![],
6770            alias_explicit_as: false,
6771            alias_keyword: None,
6772            order_by: None,
6773            limit: None,
6774            offset: None,
6775            distribute_by: None,
6776            sort_by: None,
6777            cluster_by: None,
6778            lateral: false,
6779            modifiers_inside: false,
6780            trailing_comments: vec![],
6781            inferred_type: None,
6782        }));
6783
6784        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
6785        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
6786            Expression::Alias(Box::new(Alias {
6787                this: subquery,
6788                alias: Identifier::new(&alias_name),
6789                column_aliases: col_aliases,
6790                alias_explicit_as: false,
6791                alias_keyword: None,
6792                pre_alias_comments: vec![],
6793                trailing_comments: vec![],
6794                inferred_type: None,
6795            }))
6796        } else {
6797            subquery
6798        };
6799
6800        // Replace the FROM expression
6801        if let Some(ref mut from) = sel.from {
6802            from.expressions[from_idx] = replacement;
6803        }
6804
6805        Ok(Expression::Select(sel))
6806    }
6807
6808    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
6809    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
6810    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
6811    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
6812        use crate::expressions::*;
6813
6814        let start_expr = f.args[0].clone();
6815        let end_expr = f.args[1].clone();
6816        let step = f.args.get(2).cloned();
6817        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
6818        let col_name = "value";
6819
6820        // Build the inner subquery: same as try_transform_from_gda_snowflake
6821        let datediff = Expression::Function(Box::new(Function::new(
6822            "DATEDIFF".to_string(),
6823            vec![
6824                Expression::boxed_column(Column {
6825                    name: Identifier::new(&unit_str),
6826                    table: None,
6827                    join_mark: false,
6828                    trailing_comments: vec![],
6829                    span: None,
6830                    inferred_type: None,
6831                }),
6832                start_expr.clone(),
6833                end_expr.clone(),
6834            ],
6835        )));
6836        // DATEDIFF(...) + 1
6837        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
6838            left: datediff,
6839            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
6840            left_comments: vec![],
6841            operator_comments: vec![],
6842            trailing_comments: vec![],
6843            inferred_type: None,
6844        }));
6845
6846        let array_gen_range = Expression::Function(Box::new(Function::new(
6847            "ARRAY_GENERATE_RANGE".to_string(),
6848            vec![
6849                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
6850                datediff_plus_one,
6851            ],
6852        )));
6853
6854        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
6855            name: Identifier::new("INPUT"),
6856            value: array_gen_range,
6857            separator: crate::expressions::NamedArgSeparator::DArrow,
6858        }));
6859        let flatten = Expression::Function(Box::new(Function::new(
6860            "FLATTEN".to_string(),
6861            vec![flatten_input],
6862        )));
6863
6864        let table_func =
6865            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
6866        let flatten_aliased = Expression::Alias(Box::new(Alias {
6867            this: table_func,
6868            alias: Identifier::new("_t0"),
6869            column_aliases: vec![
6870                Identifier::new("seq"),
6871                Identifier::new("key"),
6872                Identifier::new("path"),
6873                Identifier::new("index"),
6874                Identifier::new(col_name),
6875                Identifier::new("this"),
6876            ],
6877            alias_explicit_as: false,
6878            alias_keyword: None,
6879            pre_alias_comments: vec![],
6880            trailing_comments: vec![],
6881            inferred_type: None,
6882        }));
6883
6884        let dateadd_expr = Expression::Function(Box::new(Function::new(
6885            "DATEADD".to_string(),
6886            vec![
6887                Expression::boxed_column(Column {
6888                    name: Identifier::new(&unit_str),
6889                    table: None,
6890                    join_mark: false,
6891                    trailing_comments: vec![],
6892                    span: None,
6893                    inferred_type: None,
6894                }),
6895                Expression::Cast(Box::new(Cast {
6896                    this: Expression::boxed_column(Column {
6897                        name: Identifier::new(col_name),
6898                        table: None,
6899                        join_mark: false,
6900                        trailing_comments: vec![],
6901                        span: None,
6902                        inferred_type: None,
6903                    }),
6904                    to: DataType::Int {
6905                        length: None,
6906                        integer_spelling: false,
6907                    },
6908                    trailing_comments: vec![],
6909                    double_colon_syntax: false,
6910                    format: None,
6911                    default: None,
6912                    inferred_type: None,
6913                })),
6914                start_expr.clone(),
6915            ],
6916        )));
6917        let dateadd_aliased = Expression::Alias(Box::new(Alias {
6918            this: dateadd_expr,
6919            alias: Identifier::new(col_name),
6920            column_aliases: vec![],
6921            alias_explicit_as: false,
6922            alias_keyword: None,
6923            pre_alias_comments: vec![],
6924            trailing_comments: vec![],
6925            inferred_type: None,
6926        }));
6927
6928        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
6929        let mut inner_select = Select::new();
6930        inner_select.expressions = vec![dateadd_aliased];
6931        inner_select.from = Some(From {
6932            expressions: vec![flatten_aliased],
6933        });
6934
6935        // Wrap in subquery for the inner part
6936        let inner_subquery = Expression::Subquery(Box::new(Subquery {
6937            this: Expression::Select(Box::new(inner_select)),
6938            alias: None,
6939            column_aliases: vec![],
6940            alias_explicit_as: false,
6941            alias_keyword: None,
6942            order_by: None,
6943            limit: None,
6944            offset: None,
6945            distribute_by: None,
6946            sort_by: None,
6947            cluster_by: None,
6948            lateral: false,
6949            modifiers_inside: false,
6950            trailing_comments: vec![],
6951            inferred_type: None,
6952        }));
6953
6954        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
6955        let star = Expression::Star(Star {
6956            table: None,
6957            except: None,
6958            replace: None,
6959            rename: None,
6960            trailing_comments: vec![],
6961            span: None,
6962        });
6963        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
6964            this: star,
6965            distinct: false,
6966            filter: None,
6967            order_by: vec![],
6968            name: Some("ARRAY_AGG".to_string()),
6969            ignore_nulls: None,
6970            having_max: None,
6971            limit: None,
6972            inferred_type: None,
6973        }));
6974
6975        let mut outer_select = Select::new();
6976        outer_select.expressions = vec![array_agg];
6977        outer_select.from = Some(From {
6978            expressions: vec![inner_subquery],
6979        });
6980
6981        // Wrap in a subquery
6982        let outer_subquery = Expression::Subquery(Box::new(Subquery {
6983            this: Expression::Select(Box::new(outer_select)),
6984            alias: None,
6985            column_aliases: vec![],
6986            alias_explicit_as: false,
6987            alias_keyword: None,
6988            order_by: None,
6989            limit: None,
6990            offset: None,
6991            distribute_by: None,
6992            sort_by: None,
6993            cluster_by: None,
6994            lateral: false,
6995            modifiers_inside: false,
6996            trailing_comments: vec![],
6997            inferred_type: None,
6998        }));
6999
7000        // ARRAY_SIZE(subquery)
7001        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
7002            outer_subquery,
7003        ))))
7004    }
7005
7006    /// Extract interval unit string from an optional step expression.
7007    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
7008        use crate::expressions::*;
7009        if let Some(Expression::Interval(ref iv)) = step {
7010            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
7011                return Some(format!("{:?}", unit).to_ascii_uppercase());
7012            }
7013            if let Some(ref this) = iv.this {
7014                if let Expression::Literal(lit) = this {
7015                    if let Literal::String(ref s) = lit.as_ref() {
7016                        let parts: Vec<&str> = s.split_whitespace().collect();
7017                        if parts.len() == 2 {
7018                            return Some(parts[1].to_ascii_uppercase());
7019                        } else if parts.len() == 1 {
7020                            let upper = parts[0].to_ascii_uppercase();
7021                            if matches!(
7022                                upper.as_str(),
7023                                "YEAR"
7024                                    | "QUARTER"
7025                                    | "MONTH"
7026                                    | "WEEK"
7027                                    | "DAY"
7028                                    | "HOUR"
7029                                    | "MINUTE"
7030                                    | "SECOND"
7031                            ) {
7032                                return Some(upper);
7033                            }
7034                        }
7035                    }
7036                }
7037            }
7038        }
7039        // Default to DAY if no step or no interval
7040        if step.is_none() {
7041            return Some("DAY".to_string());
7042        }
7043        None
7044    }
7045
7046    fn normalize_snowflake_pretty(mut sql: String) -> String {
7047        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
7048            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
7049        {
7050            sql = sql.replace(
7051                "AND uc.user_id <> ALL (SELECT DISTINCT\n      _id\n    FROM users, LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)\n    WHERE\n      GET_PATH(datasource.value, 'name') = 'something')",
7052                "AND uc.user_id <> ALL (\n      SELECT DISTINCT\n        _id\n      FROM users, LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)\n      WHERE\n        GET_PATH(datasource.value, 'name') = 'something'\n    )",
7053            );
7054
7055            sql = sql.replace(
7056                "CROSS JOIN TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1))) AS _u(seq, key, path, index, pos, this)",
7057                "CROSS JOIN TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, (\n  GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1\n) + 1))) AS _u(seq, key, path, index, pos, this)",
7058            );
7059
7060            sql = sql.replace(
7061                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
7062                "OR (\n    _u.pos > (\n      ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1\n    )\n    AND _u_2.pos_2 = (\n      ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1\n    )\n  )",
7063            );
7064        }
7065
7066        sql
7067    }
7068
7069    /// Apply cross-dialect semantic normalizations that depend on knowing both source and target.
7070    /// This handles cases where the same syntax has different semantics across dialects.
7071    fn cross_dialect_normalize(
7072        expr: Expression,
7073        source: DialectType,
7074        target: DialectType,
7075    ) -> Result<Expression> {
7076        use crate::expressions::{
7077            AggFunc, BinaryOp, Case, Cast, ConvertTimezone, DataType, DateTimeField, DateTruncFunc,
7078            Function, Identifier, IsNull, Literal, Null, Paren,
7079        };
7080
7081        // Helper to tag which kind of transform to apply
7082        #[derive(Debug)]
7083        enum Action {
7084            None,
7085            GreatestLeastNull,
7086            ArrayGenerateRange,
7087            Div0TypedDivision,
7088            ArrayAggCollectList,
7089            ArrayAggWithinGroupFilter,
7090            ArrayAggFilter,
7091            CastTimestampToDatetime,
7092            DateTruncWrapCast,
7093            ToDateToCast,
7094            ConvertTimezoneToExpr,
7095            SetToVariable,
7096            RegexpReplaceSnowflakeToDuckDB,
7097            BigQueryFunctionNormalize,
7098            BigQuerySafeDivide,
7099            BigQueryCastType,
7100            BigQueryToHexBare,        // _BQ_TO_HEX(x) with no LOWER/UPPER wrapper
7101            BigQueryToHexLower,       // LOWER(_BQ_TO_HEX(x))
7102            BigQueryToHexUpper,       // UPPER(_BQ_TO_HEX(x))
7103            BigQueryLastDayStripUnit, // LAST_DAY(date, MONTH) -> LAST_DAY(date)
7104            BigQueryCastFormat, // CAST(x AS type FORMAT 'fmt') -> PARSE_DATE/PARSE_TIMESTAMP etc.
7105            BigQueryAnyValueHaving, // ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
7106            BigQueryApproxQuantiles, // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
7107            GenericFunctionNormalize, // Cross-dialect function renaming (non-BigQuery sources)
7108            RegexpLikeToDuckDB,      // RegexpLike -> REGEXP_MATCHES for DuckDB target
7109            EpochConvert,            // Expression::Epoch -> target-specific epoch function
7110            EpochMsConvert,          // Expression::EpochMs -> target-specific epoch ms function
7111            TSQLTypeNormalize, // TSQL types (MONEY, SMALLMONEY, REAL, DATETIME2) -> standard types
7112            MySQLSafeDivide,   // MySQL a/b -> a / NULLIF(b, 0) with optional CAST
7113            NullsOrdering,     // Add NULLS FIRST/LAST for ORDER BY
7114            AlterTableRenameStripSchema, // ALTER TABLE db.t1 RENAME TO db.t2 -> ALTER TABLE db.t1 RENAME TO t2
7115            StringAggConvert,            // STRING_AGG/WITHIN GROUP -> target-specific aggregate
7116            GroupConcatConvert,          // GROUP_CONCAT -> target-specific aggregate
7117            TempTableHash,               // TSQL #table -> temp table normalization
7118            ArrayLengthConvert,          // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific
7119            DatePartUnquote, // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
7120            NvlClearOriginal, // Clear NVL original_name for cross-dialect transpilation
7121            HiveCastToTryCast, // Hive/Spark CAST -> TRY_CAST for targets that support it
7122            XorExpand,       // MySQL XOR -> (a AND NOT b) OR (NOT a AND b) for non-XOR targets
7123            CastTimestampStripTz, // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark
7124            JsonExtractToGetJsonObject, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
7125            JsonExtractScalarToGetJsonObject, // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
7126            JsonQueryValueConvert, // JsonQuery/JsonValue -> target-specific (ISNULL wrapper for TSQL, GET_JSON_OBJECT for Spark, etc.)
7127            JsonLiteralToJsonParse, // JSON 'x' -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake; also DuckDB CAST(x AS JSON)
7128            DuckDBCastJsonToVariant, // DuckDB CAST(x AS JSON) -> CAST(x AS VARIANT) for Snowflake
7129            DuckDBTryCastJsonToTryJsonParse, // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
7130            DuckDBJsonFuncToJsonParse, // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
7131            DuckDBJsonValidToIsJson,   // DuckDB json_valid(x) -> x IS JSON for Trino/Presto/Athena
7132            ArraySyntaxConvert,        // ARRAY[x] -> ARRAY(x) for Spark, [x] for BigQuery/DuckDB
7133            AtTimeZoneConvert, // AT TIME ZONE -> AT_TIMEZONE (Presto) / FROM_UTC_TIMESTAMP (Spark)
7134            DayOfWeekConvert,  // DAY_OF_WEEK -> dialect-specific
7135            MaxByMinByConvert, // MAX_BY/MIN_BY -> argMax/argMin for ClickHouse
7136            ArrayAggToCollectList, // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
7137            ArrayAggToGroupConcat, // ARRAY_AGG(x) -> GROUP_CONCAT(x) for MySQL-like targets
7138            ElementAtConvert, // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
7139            CurrentUserParens, // CURRENT_USER -> CURRENT_USER() for Snowflake
7140            CastToJsonForSpark, // CAST(x AS JSON) -> TO_JSON(x) for Spark
7141            CastJsonToFromJson, // CAST(JSON_PARSE(literal) AS ARRAY/MAP) -> FROM_JSON(literal, type_string)
7142            ToJsonConvert,      // TO_JSON(x) -> JSON_FORMAT(CAST(x AS JSON)) for Presto etc.
7143            ArrayAggNullFilter, // ARRAY_AGG(x) FILTER(WHERE cond) -> add AND NOT x IS NULL for DuckDB
7144            ArrayAggIgnoreNullsDuckDB, // ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, ...) for DuckDB
7145            BigQueryPercentileContToDuckDB, // PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
7146            BigQueryArraySelectAsStructToSnowflake, // ARRAY(SELECT AS STRUCT ...) -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT(...)))
7147            CountDistinctMultiArg, // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END)
7148            VarianceToClickHouse,  // Expression::Variance -> varSamp for ClickHouse
7149            StddevToClickHouse,    // Expression::Stddev -> stddevSamp for ClickHouse
7150            ApproxQuantileConvert, // Expression::ApproxQuantile -> APPROX_PERCENTILE for Snowflake
7151            ArrayIndexConvert,     // array[1] -> array[0] for BigQuery (1-based to 0-based)
7152            DollarParamConvert,    // $foo -> @foo for BigQuery
7153            TablesampleReservoir, // TABLESAMPLE (n ROWS) -> TABLESAMPLE RESERVOIR (n ROWS) for DuckDB
7154            BitAggFloatCast, // BIT_OR/BIT_AND/BIT_XOR float arg -> CAST(ROUND(CAST(arg)) AS INT) for DuckDB
7155            BitAggSnowflakeRename, // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG etc. for Snowflake
7156            StrftimeCastTimestamp, // CAST TIMESTAMP -> TIMESTAMP_NTZ for Spark in STRFTIME
7157            AnyValueIgnoreNulls, // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
7158            CreateTableStripComment, // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
7159            EscapeStringNormalize,   // e'Hello\nworld' literal newline -> \n
7160            AnyToExists,             // PostgreSQL x <op> ANY(array) -> EXISTS(array, x -> ...)
7161            ArrayConcatBracketConvert, // [1,2] -> ARRAY[1,2] for PostgreSQL in ARRAY_CAT
7162            SnowflakeIntervalFormat, // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
7163            AlterTableToSpRename,    // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
7164            StraightJoinCase,        // STRAIGHT_JOIN -> straight_join for DuckDB
7165            RespectNullsConvert,     // RESPECT NULLS window function handling
7166            MysqlNullsOrdering,      // MySQL doesn't support NULLS ordering
7167            MysqlNullsLastRewrite, // Add CASE WHEN to ORDER BY for DuckDB -> MySQL (NULLS LAST simulation)
7168            BigQueryNullsOrdering, // BigQuery doesn't support NULLS FIRST/LAST - strip
7169            SnowflakeFloatProtect, // Protect FLOAT from being converted to DOUBLE by Snowflake target transform
7170            JsonToGetPath,         // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
7171            FilterToIff,           // FILTER(WHERE) -> IFF wrapping for Snowflake
7172            AggFilterToIff, // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
7173            StructToRow,    // DuckDB struct -> Presto ROW / BigQuery STRUCT
7174            SparkStructConvert, // Spark STRUCT(x AS col1, ...) -> ROW/DuckDB struct
7175            DecimalDefaultPrecision, // DECIMAL -> DECIMAL(18, 3) for Snowflake in BIT agg
7176            ApproxCountDistinctToApproxDistinct, // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
7177            CollectListToArrayAgg,               // COLLECT_LIST -> ARRAY_AGG for Presto/DuckDB
7178            CollectSetConvert, // COLLECT_SET -> SET_AGG/ARRAY_AGG(DISTINCT)/ARRAY_UNIQUE_AGG
7179            PercentileConvert, // PERCENTILE -> QUANTILE/APPROX_PERCENTILE
7180            CorrIsnanWrap, // CORR(a,b) -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END
7181            TruncToDateTrunc, // TRUNC(ts, unit) -> DATE_TRUNC(unit, ts)
7182            ArrayContainsConvert, // ARRAY_CONTAINS -> CONTAINS/target-specific
7183            StrPositionExpand, // StrPosition with position -> complex STRPOS expansion for Presto/DuckDB
7184            TablesampleSnowflakeStrip, // Strip method and PERCENT for Snowflake target
7185            FirstToAnyValue,   // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
7186            MonthsBetweenConvert, // Expression::MonthsBetween -> target-specific
7187            CurrentUserSparkParens, // CURRENT_USER -> CURRENT_USER() for Spark
7188            SparkDateFuncCast, // MONTH/YEAR/DAY('str') -> MONTH/YEAR/DAY(CAST('str' AS DATE)) from Spark
7189            MapFromArraysConvert, // Expression::MapFromArrays -> MAP/OBJECT_CONSTRUCT/MAP_FROM_ARRAYS
7190            AddMonthsConvert,     // Expression::AddMonths -> target-specific DATEADD/DATE_ADD
7191            PercentileContConvert, // PERCENTILE_CONT/DISC WITHIN GROUP -> APPROX_PERCENTILE/PERCENTILE_APPROX
7192            GenerateSeriesConvert, // GENERATE_SERIES -> SEQUENCE/UNNEST(SEQUENCE)/EXPLODE(SEQUENCE)
7193            ConcatCoalesceWrap, // CONCAT(a, b) -> CONCAT(COALESCE(CAST(a), ''), ...) for Presto/ClickHouse
7194            PipeConcatToConcat, // a || b -> CONCAT(CAST(a), CAST(b)) for Presto
7195            DivFuncConvert,     // DIV(a, b) -> a // b for DuckDB, CAST for BigQuery
7196            JsonObjectAggConvert, // JSON_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
7197            JsonbExistsConvert, // JSONB_EXISTS -> JSON_EXISTS for DuckDB
7198            DateBinConvert,     // DATE_BIN -> TIME_BUCKET for DuckDB
7199            MysqlCastCharToText, // MySQL CAST(x AS CHAR) -> CAST(x AS TEXT/VARCHAR/STRING) for targets
7200            SparkCastVarcharToString, // Spark CAST(x AS VARCHAR/CHAR) -> CAST(x AS STRING) for Spark targets
7201            JsonExtractToArrow,       // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB
7202            JsonExtractToTsql, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> ISNULL(JSON_QUERY, JSON_VALUE) for TSQL
7203            JsonExtractToClickHouse, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
7204            JsonExtractScalarConvert, // JSON_EXTRACT_SCALAR -> target-specific (PostgreSQL, Snowflake, SQLite)
7205            JsonPathNormalize, // Normalize JSON path format (brackets, wildcards, quotes) for various dialects
7206            MinMaxToLeastGreatest, // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
7207            ClickHouseUniqToApproxCountDistinct, // uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
7208            ClickHouseAnyToAnyValue, // any(x) -> ANY_VALUE(x) for non-ClickHouse targets
7209            OracleVarchar2ToVarchar, // VARCHAR2(N CHAR/BYTE) -> VARCHAR(N) for non-Oracle targets
7210            Nvl2Expand,              // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END
7211            IfnullToCoalesce,        // IFNULL(a, b) -> COALESCE(a, b)
7212            IsAsciiConvert,          // IS_ASCII(x) -> dialect-specific ASCII check
7213            StrPositionConvert,      // STR_POSITION(haystack, needle[, pos]) -> dialect-specific
7214            DecodeSimplify,          // DECODE with null-safe -> simple = comparison
7215            ArraySumConvert,         // ARRAY_SUM -> target-specific
7216            ArraySizeConvert,        // ARRAY_SIZE -> target-specific
7217            ArrayAnyConvert,         // ARRAY_ANY -> target-specific
7218            CastTimestamptzToFunc,   // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) for MySQL/StarRocks
7219            TsOrDsToDateConvert,     // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific
7220            TsOrDsToDateStrConvert,  // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
7221            DateStrToDateConvert,    // DATE_STR_TO_DATE(x) -> CAST(x AS DATE)
7222            TimeStrToDateConvert,    // TIME_STR_TO_DATE(x) -> CAST(x AS DATE)
7223            TimeStrToTimeConvert,    // TIME_STR_TO_TIME(x) -> CAST(x AS TIMESTAMP)
7224            DateToDateStrConvert,    // DATE_TO_DATE_STR(x) -> CAST(x AS TEXT/VARCHAR/STRING)
7225            DateToDiConvert, // DATE_TO_DI(x) -> dialect-specific (CAST date to YYYYMMDD integer)
7226            DiToDateConvert, // DI_TO_DATE(x) -> dialect-specific (integer YYYYMMDD to date)
7227            TsOrDiToDiConvert, // TS_OR_DI_TO_DI(x) -> dialect-specific
7228            UnixToStrConvert, // UNIX_TO_STR(x, fmt) -> dialect-specific
7229            UnixToTimeConvert, // UNIX_TO_TIME(x) -> dialect-specific
7230            UnixToTimeStrConvert, // UNIX_TO_TIME_STR(x) -> dialect-specific
7231            TimeToUnixConvert, // TIME_TO_UNIX(x) -> dialect-specific
7232            TimeToStrConvert, // TIME_TO_STR(x, fmt) -> dialect-specific
7233            StrToUnixConvert, // STR_TO_UNIX(x, fmt) -> dialect-specific
7234            DateTruncSwapArgs, // DATE_TRUNC('unit', x) -> DATE_TRUNC(x, unit) / TRUNC(x, unit)
7235            TimestampTruncConvert, // TIMESTAMP_TRUNC(x, UNIT[, tz]) -> dialect-specific
7236            StrToDateConvert, // STR_TO_DATE(x, fmt) from Generic -> CAST(StrToTime(x,fmt) AS DATE)
7237            TsOrDsAddConvert, // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> DATE_ADD per dialect
7238            DateFromUnixDateConvert, // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
7239            TimeStrToUnixConvert, // TIME_STR_TO_UNIX(x) -> dialect-specific
7240            TimeToTimeStrConvert, // TIME_TO_TIME_STR(x) -> CAST(x AS type)
7241            CreateTableLikeToCtas, // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
7242            CreateTableLikeToSelectInto, // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
7243            CreateTableLikeToAs, // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
7244            ArrayRemoveConvert, // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
7245            ArrayReverseConvert, // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
7246            JsonKeysConvert,    // JSON_KEYS -> JSON_OBJECT_KEYS/OBJECT_KEYS
7247            ParseJsonStrip,     // PARSE_JSON(x) -> x (strip wrapper)
7248            ArraySizeDrill,     // ARRAY_SIZE -> REPEATED_COUNT for Drill
7249            WeekOfYearToWeekIso, // WEEKOFYEAR -> WEEKISO for Snowflake cross-dialect
7250            RegexpSubstrSnowflakeToDuckDB, // REGEXP_SUBSTR(s, p, ...) -> REGEXP_EXTRACT variants for DuckDB
7251            RegexpSubstrSnowflakeIdentity, // REGEXP_SUBSTR/REGEXP_SUBSTR_ALL strip trailing group=0 for Snowflake identity
7252            RegexpSubstrAllSnowflakeToDuckDB, // REGEXP_SUBSTR_ALL(s, p, ...) -> REGEXP_EXTRACT_ALL variants for DuckDB
7253            RegexpCountSnowflakeToDuckDB, // REGEXP_COUNT(s, p, ...) -> LENGTH(REGEXP_EXTRACT_ALL(...)) for DuckDB
7254            RegexpInstrSnowflakeToDuckDB, // REGEXP_INSTR(s, p, ...) -> complex CASE expression for DuckDB
7255            RegexpReplacePositionSnowflakeToDuckDB, // REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
7256            RlikeSnowflakeToDuckDB, // RLIKE(a, b[, flags]) -> REGEXP_FULL_MATCH(a, b[, flags]) for DuckDB
7257            RegexpExtractAllToSnowflake, // BigQuery REGEXP_EXTRACT_ALL -> REGEXP_SUBSTR_ALL for Snowflake
7258            ArrayExceptConvert, // ARRAY_EXCEPT -> DuckDB complex CASE / Snowflake ARRAY_EXCEPT / Presto ARRAY_EXCEPT
7259            ArrayPositionSnowflakeSwap, // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
7260            RegexpLikeExasolAnchor, // RegexpLike -> Exasol REGEXP_LIKE with .*pattern.* anchoring
7261            ArrayDistinctConvert,   // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
7262            ArrayDistinctClickHouse, // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
7263            ArrayContainsDuckDBConvert, // ARRAY_CONTAINS -> DuckDB CASE with NULL-aware check
7264            SnowflakeWindowFrameStrip, // Strip default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for Snowflake target
7265            SnowflakeWindowFrameAdd, // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for non-Snowflake target
7266            SnowflakeArrayPositionToDuckDB, // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB
7267        }
7268
7269        // Handle SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake/etc.
7270        let expr = if matches!(source, DialectType::TSQL | DialectType::Fabric) {
7271            Self::transform_select_into(expr, source, target)
7272        } else {
7273            expr
7274        };
7275
7276        // Strip OFFSET ROWS for non-TSQL/Oracle targets
7277        let expr = if !matches!(
7278            target,
7279            DialectType::TSQL | DialectType::Oracle | DialectType::Fabric
7280        ) {
7281            if let Expression::Select(mut select) = expr {
7282                if let Some(ref mut offset) = select.offset {
7283                    offset.rows = None;
7284                }
7285                Expression::Select(select)
7286            } else {
7287                expr
7288            }
7289        } else {
7290            expr
7291        };
7292
7293        // Oracle: LIMIT -> FETCH FIRST, OFFSET -> OFFSET ROWS
7294        let expr = if matches!(target, DialectType::Oracle) {
7295            if let Expression::Select(mut select) = expr {
7296                if let Some(limit) = select.limit.take() {
7297                    // Convert LIMIT to FETCH FIRST n ROWS ONLY
7298                    select.fetch = Some(crate::expressions::Fetch {
7299                        direction: "FIRST".to_string(),
7300                        count: Some(limit.this),
7301                        percent: false,
7302                        rows: true,
7303                        with_ties: false,
7304                    });
7305                }
7306                // Add ROWS to OFFSET if present
7307                if let Some(ref mut offset) = select.offset {
7308                    offset.rows = Some(true);
7309                }
7310                Expression::Select(select)
7311            } else {
7312                expr
7313            }
7314        } else {
7315            expr
7316        };
7317
7318        // Handle CreateTable WITH properties transformation before recursive transforms
7319        let expr = if let Expression::CreateTable(mut ct) = expr {
7320            Self::transform_create_table_properties(&mut ct, source, target);
7321
7322            // Handle Hive-style PARTITIONED BY (col_name type, ...) -> target-specific
7323            // When the PARTITIONED BY clause contains column definitions, merge them into the
7324            // main column list and adjust the PARTITIONED BY clause for the target dialect.
7325            if matches!(
7326                source,
7327                DialectType::Hive | DialectType::Spark | DialectType::Databricks
7328            ) {
7329                let mut partition_col_names: Vec<String> = Vec::new();
7330                let mut partition_col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
7331                let mut has_col_def_partitions = false;
7332
7333                // Check if any PARTITIONED BY property contains ColumnDef expressions
7334                for prop in &ct.properties {
7335                    if let Expression::PartitionedByProperty(ref pbp) = prop {
7336                        if let Expression::Tuple(ref tuple) = *pbp.this {
7337                            for expr in &tuple.expressions {
7338                                if let Expression::ColumnDef(ref cd) = expr {
7339                                    has_col_def_partitions = true;
7340                                    partition_col_names.push(cd.name.name.clone());
7341                                    partition_col_defs.push(*cd.clone());
7342                                }
7343                            }
7344                        }
7345                    }
7346                }
7347
7348                if has_col_def_partitions && !matches!(target, DialectType::Hive) {
7349                    // Merge partition columns into main column list
7350                    for cd in partition_col_defs {
7351                        ct.columns.push(cd);
7352                    }
7353
7354                    // Replace PARTITIONED BY property with column-name-only version
7355                    ct.properties
7356                        .retain(|p| !matches!(p, Expression::PartitionedByProperty(_)));
7357
7358                    if matches!(
7359                        target,
7360                        DialectType::Presto | DialectType::Trino | DialectType::Athena
7361                    ) {
7362                        // Presto: WITH (PARTITIONED_BY=ARRAY['y', 'z'])
7363                        let array_elements: Vec<String> = partition_col_names
7364                            .iter()
7365                            .map(|n| format!("'{}'", n))
7366                            .collect();
7367                        let array_value = format!("ARRAY[{}]", array_elements.join(", "));
7368                        ct.with_properties
7369                            .push(("PARTITIONED_BY".to_string(), array_value));
7370                    } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
7371                        // Spark: PARTITIONED BY (y, z) - just column names
7372                        let name_exprs: Vec<Expression> = partition_col_names
7373                            .iter()
7374                            .map(|n| {
7375                                Expression::Column(Box::new(crate::expressions::Column {
7376                                    name: crate::expressions::Identifier::new(n.clone()),
7377                                    table: None,
7378                                    join_mark: false,
7379                                    trailing_comments: Vec::new(),
7380                                    span: None,
7381                                    inferred_type: None,
7382                                }))
7383                            })
7384                            .collect();
7385                        ct.properties.insert(
7386                            0,
7387                            Expression::PartitionedByProperty(Box::new(
7388                                crate::expressions::PartitionedByProperty {
7389                                    this: Box::new(Expression::Tuple(Box::new(
7390                                        crate::expressions::Tuple {
7391                                            expressions: name_exprs,
7392                                        },
7393                                    ))),
7394                                },
7395                            )),
7396                        );
7397                    }
7398                    // For DuckDB and other targets, just drop the PARTITIONED BY (already retained above)
7399                }
7400
7401                // Note: Non-ColumnDef partitions (e.g., function expressions like MONTHS(y))
7402                // are handled by transform_create_table_properties which runs first
7403            }
7404
7405            // Strip LOCATION property for Presto/Trino (not supported)
7406            if matches!(
7407                target,
7408                DialectType::Presto | DialectType::Trino | DialectType::Athena
7409            ) {
7410                ct.properties
7411                    .retain(|p| !matches!(p, Expression::LocationProperty(_)));
7412            }
7413
7414            // Strip table-level constraints for Spark/Hive/Databricks
7415            // Keep PRIMARY KEY and LIKE constraints but strip TSQL-specific modifiers; remove all others
7416            if matches!(
7417                target,
7418                DialectType::Spark | DialectType::Databricks | DialectType::Hive
7419            ) {
7420                ct.constraints.retain(|c| {
7421                    matches!(
7422                        c,
7423                        crate::expressions::TableConstraint::PrimaryKey { .. }
7424                            | crate::expressions::TableConstraint::Like { .. }
7425                    )
7426                });
7427                for constraint in &mut ct.constraints {
7428                    if let crate::expressions::TableConstraint::PrimaryKey {
7429                        columns,
7430                        modifiers,
7431                        ..
7432                    } = constraint
7433                    {
7434                        // Strip ASC/DESC from column names
7435                        for col in columns.iter_mut() {
7436                            if col.name.ends_with(" ASC") {
7437                                col.name = col.name[..col.name.len() - 4].to_string();
7438                            } else if col.name.ends_with(" DESC") {
7439                                col.name = col.name[..col.name.len() - 5].to_string();
7440                            }
7441                        }
7442                        // Strip TSQL-specific modifiers
7443                        modifiers.clustered = None;
7444                        modifiers.with_options.clear();
7445                        modifiers.on_filegroup = None;
7446                    }
7447                }
7448            }
7449
7450            // Databricks: IDENTITY columns with INT/INTEGER -> BIGINT
7451            if matches!(target, DialectType::Databricks) {
7452                for col in &mut ct.columns {
7453                    if col.auto_increment {
7454                        if matches!(col.data_type, crate::expressions::DataType::Int { .. }) {
7455                            col.data_type = crate::expressions::DataType::BigInt { length: None };
7456                        }
7457                    }
7458                }
7459            }
7460
7461            // Spark/Databricks: INTEGER -> INT in column definitions
7462            // Python sqlglot always outputs INT for Spark/Databricks
7463            if matches!(target, DialectType::Spark | DialectType::Databricks) {
7464                for col in &mut ct.columns {
7465                    if let crate::expressions::DataType::Int {
7466                        integer_spelling, ..
7467                    } = &mut col.data_type
7468                    {
7469                        *integer_spelling = false;
7470                    }
7471                }
7472            }
7473
7474            // Strip explicit NULL constraints for Hive/Spark (B INTEGER NULL -> B INTEGER)
7475            if matches!(target, DialectType::Hive | DialectType::Spark) {
7476                for col in &mut ct.columns {
7477                    // If nullable is explicitly true (NULL), change to None (omit it)
7478                    if col.nullable == Some(true) {
7479                        col.nullable = None;
7480                    }
7481                    // Also remove from constraints if stored there
7482                    col.constraints
7483                        .retain(|c| !matches!(c, crate::expressions::ColumnConstraint::Null));
7484                }
7485            }
7486
7487            // Strip TSQL ON filegroup for non-TSQL/Fabric targets
7488            if ct.on_property.is_some()
7489                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
7490            {
7491                ct.on_property = None;
7492            }
7493
7494            // Snowflake: strip ARRAY type parameters (ARRAY<INT> -> ARRAY, ARRAY<ARRAY<INT>> -> ARRAY)
7495            // Snowflake doesn't support typed arrays in DDL
7496            if matches!(target, DialectType::Snowflake) {
7497                fn strip_array_type_params(dt: &mut crate::expressions::DataType) {
7498                    if let crate::expressions::DataType::Array { .. } = dt {
7499                        *dt = crate::expressions::DataType::Custom {
7500                            name: "ARRAY".to_string(),
7501                        };
7502                    }
7503                }
7504                for col in &mut ct.columns {
7505                    strip_array_type_params(&mut col.data_type);
7506                }
7507            }
7508
7509            // PostgreSQL target: ensure IDENTITY columns have NOT NULL
7510            // If NOT NULL was explicit in source (present in constraint_order), preserve original order.
7511            // If NOT NULL was not explicit, add it after IDENTITY (GENERATED BY DEFAULT AS IDENTITY NOT NULL).
7512            if matches!(target, DialectType::PostgreSQL) {
7513                for col in &mut ct.columns {
7514                    if col.auto_increment && !col.constraint_order.is_empty() {
7515                        use crate::expressions::ConstraintType;
7516                        let has_explicit_not_null = col
7517                            .constraint_order
7518                            .iter()
7519                            .any(|ct| *ct == ConstraintType::NotNull);
7520
7521                        if has_explicit_not_null {
7522                            // Source had explicit NOT NULL - preserve original order
7523                            // Just ensure nullable is set
7524                            if col.nullable != Some(false) {
7525                                col.nullable = Some(false);
7526                            }
7527                        } else {
7528                            // Source didn't have explicit NOT NULL - build order with
7529                            // AutoIncrement + NotNull first, then remaining constraints
7530                            let mut new_order = Vec::new();
7531                            // Put AutoIncrement (IDENTITY) first, followed by synthetic NotNull
7532                            new_order.push(ConstraintType::AutoIncrement);
7533                            new_order.push(ConstraintType::NotNull);
7534                            // Add remaining constraints in original order (except AutoIncrement)
7535                            for ct_type in &col.constraint_order {
7536                                if *ct_type != ConstraintType::AutoIncrement {
7537                                    new_order.push(ct_type.clone());
7538                                }
7539                            }
7540                            col.constraint_order = new_order;
7541                            col.nullable = Some(false);
7542                        }
7543                    }
7544                }
7545            }
7546
7547            Expression::CreateTable(ct)
7548        } else {
7549            expr
7550        };
7551
7552        // Handle CreateView column stripping for Presto/Trino target
7553        let expr = if let Expression::CreateView(mut cv) = expr {
7554            // Presto/Trino: drop column list when view has a SELECT body
7555            if matches!(target, DialectType::Presto | DialectType::Trino) && !cv.columns.is_empty()
7556            {
7557                if !matches!(&cv.query, Expression::Null(_)) {
7558                    cv.columns.clear();
7559                }
7560            }
7561            Expression::CreateView(cv)
7562        } else {
7563            expr
7564        };
7565
7566        // Wrap bare VALUES in CTE bodies with SELECT * FROM (...) AS _values for generic/non-Presto targets
7567        let expr = if !matches!(
7568            target,
7569            DialectType::Presto | DialectType::Trino | DialectType::Athena
7570        ) {
7571            if let Expression::Select(mut select) = expr {
7572                if let Some(ref mut with) = select.with {
7573                    for cte in &mut with.ctes {
7574                        if let Expression::Values(ref vals) = cte.this {
7575                            // Build: SELECT * FROM (VALUES ...) AS _values
7576                            let values_subquery =
7577                                Expression::Subquery(Box::new(crate::expressions::Subquery {
7578                                    this: Expression::Values(vals.clone()),
7579                                    alias: Some(Identifier::new("_values".to_string())),
7580                                    column_aliases: Vec::new(),
7581                                    alias_explicit_as: false,
7582                                    alias_keyword: None,
7583                                    order_by: None,
7584                                    limit: None,
7585                                    offset: None,
7586                                    distribute_by: None,
7587                                    sort_by: None,
7588                                    cluster_by: None,
7589                                    lateral: false,
7590                                    modifiers_inside: false,
7591                                    trailing_comments: Vec::new(),
7592                                    inferred_type: None,
7593                                }));
7594                            let mut new_select = crate::expressions::Select::new();
7595                            new_select.expressions =
7596                                vec![Expression::Star(crate::expressions::Star {
7597                                    table: None,
7598                                    except: None,
7599                                    replace: None,
7600                                    rename: None,
7601                                    trailing_comments: Vec::new(),
7602                                    span: None,
7603                                })];
7604                            new_select.from = Some(crate::expressions::From {
7605                                expressions: vec![values_subquery],
7606                            });
7607                            cte.this = Expression::Select(Box::new(new_select));
7608                        }
7609                    }
7610                }
7611                Expression::Select(select)
7612            } else {
7613                expr
7614            }
7615        } else {
7616            expr
7617        };
7618
7619        // PostgreSQL CREATE INDEX: add NULLS FIRST to index columns that don't have nulls ordering
7620        let expr = if matches!(target, DialectType::PostgreSQL) {
7621            if let Expression::CreateIndex(mut ci) = expr {
7622                for col in &mut ci.columns {
7623                    if col.nulls_first.is_none() {
7624                        col.nulls_first = Some(true);
7625                    }
7626                }
7627                Expression::CreateIndex(ci)
7628            } else {
7629                expr
7630            }
7631        } else {
7632            expr
7633        };
7634
7635        transform_recursive(expr, &|e| {
7636            // BigQuery CAST(ARRAY[STRUCT(...)] AS STRUCT_TYPE[]) -> DuckDB: convert unnamed Structs to ROW()
7637            // This converts auto-named struct literals {'_0': x, '_1': y} inside typed arrays to ROW(x, y)
7638            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
7639                if let Expression::Cast(ref c) = e {
7640                    // Check if this is a CAST of an array to a struct array type
7641                    let is_struct_array_cast =
7642                        matches!(&c.to, crate::expressions::DataType::Array { .. });
7643                    if is_struct_array_cast {
7644                        let has_auto_named_structs = match &c.this {
7645                            Expression::Array(arr) => arr.expressions.iter().any(|elem| {
7646                                if let Expression::Struct(s) = elem {
7647                                    s.fields.iter().all(|(name, _)| {
7648                                        name.as_ref().map_or(true, |n| {
7649                                            n.starts_with('_') && n[1..].parse::<usize>().is_ok()
7650                                        })
7651                                    })
7652                                } else {
7653                                    false
7654                                }
7655                            }),
7656                            Expression::ArrayFunc(arr) => arr.expressions.iter().any(|elem| {
7657                                if let Expression::Struct(s) = elem {
7658                                    s.fields.iter().all(|(name, _)| {
7659                                        name.as_ref().map_or(true, |n| {
7660                                            n.starts_with('_') && n[1..].parse::<usize>().is_ok()
7661                                        })
7662                                    })
7663                                } else {
7664                                    false
7665                                }
7666                            }),
7667                            _ => false,
7668                        };
7669                        if has_auto_named_structs {
7670                            let convert_struct_to_row = |elem: Expression| -> Expression {
7671                                if let Expression::Struct(s) = elem {
7672                                    let row_args: Vec<Expression> =
7673                                        s.fields.into_iter().map(|(_, v)| v).collect();
7674                                    Expression::Function(Box::new(Function::new(
7675                                        "ROW".to_string(),
7676                                        row_args,
7677                                    )))
7678                                } else {
7679                                    elem
7680                                }
7681                            };
7682                            let mut c_clone = c.as_ref().clone();
7683                            match &mut c_clone.this {
7684                                Expression::Array(arr) => {
7685                                    arr.expressions = arr
7686                                        .expressions
7687                                        .drain(..)
7688                                        .map(convert_struct_to_row)
7689                                        .collect();
7690                                }
7691                                Expression::ArrayFunc(arr) => {
7692                                    arr.expressions = arr
7693                                        .expressions
7694                                        .drain(..)
7695                                        .map(convert_struct_to_row)
7696                                        .collect();
7697                                }
7698                                _ => {}
7699                            }
7700                            return Ok(Expression::Cast(Box::new(c_clone)));
7701                        }
7702                    }
7703                }
7704            }
7705
7706            // BigQuery SELECT AS STRUCT -> DuckDB struct literal {'key': value, ...}
7707            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
7708                if let Expression::Select(ref sel) = e {
7709                    if sel.kind.as_deref() == Some("STRUCT") {
7710                        let mut fields = Vec::new();
7711                        for expr in &sel.expressions {
7712                            match expr {
7713                                Expression::Alias(a) => {
7714                                    fields.push((Some(a.alias.name.clone()), a.this.clone()));
7715                                }
7716                                Expression::Column(c) => {
7717                                    fields.push((Some(c.name.name.clone()), expr.clone()));
7718                                }
7719                                _ => {
7720                                    fields.push((None, expr.clone()));
7721                                }
7722                            }
7723                        }
7724                        let struct_lit =
7725                            Expression::Struct(Box::new(crate::expressions::Struct { fields }));
7726                        let mut new_select = sel.as_ref().clone();
7727                        new_select.kind = None;
7728                        new_select.expressions = vec![struct_lit];
7729                        return Ok(Expression::Select(Box::new(new_select)));
7730                    }
7731                }
7732            }
7733
7734            // Convert @variable -> ${variable} for Spark/Hive/Databricks
7735            if matches!(source, DialectType::TSQL | DialectType::Fabric)
7736                && matches!(
7737                    target,
7738                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
7739                )
7740            {
7741                if let Expression::Parameter(ref p) = e {
7742                    if p.style == crate::expressions::ParameterStyle::At {
7743                        if let Some(ref name) = p.name {
7744                            return Ok(Expression::Parameter(Box::new(
7745                                crate::expressions::Parameter {
7746                                    name: Some(name.clone()),
7747                                    index: p.index,
7748                                    style: crate::expressions::ParameterStyle::DollarBrace,
7749                                    quoted: p.quoted,
7750                                    string_quoted: p.string_quoted,
7751                                    expression: None,
7752                                },
7753                            )));
7754                        }
7755                    }
7756                }
7757                // Also handle Column("@x") -> Parameter("x", DollarBrace) for TSQL vars
7758                if let Expression::Column(ref col) = e {
7759                    if col.name.name.starts_with('@') && col.table.is_none() {
7760                        let var_name = col.name.name.trim_start_matches('@').to_string();
7761                        return Ok(Expression::Parameter(Box::new(
7762                            crate::expressions::Parameter {
7763                                name: Some(var_name),
7764                                index: None,
7765                                style: crate::expressions::ParameterStyle::DollarBrace,
7766                                quoted: false,
7767                                string_quoted: false,
7768                                expression: None,
7769                            },
7770                        )));
7771                    }
7772                }
7773            }
7774
7775            // Convert @variable -> variable in SET statements for Spark/Databricks
7776            if matches!(source, DialectType::TSQL | DialectType::Fabric)
7777                && matches!(target, DialectType::Spark | DialectType::Databricks)
7778            {
7779                if let Expression::SetStatement(ref s) = e {
7780                    let mut new_items = s.items.clone();
7781                    let mut changed = false;
7782                    for item in &mut new_items {
7783                        // Strip @ from the SET name (Parameter style)
7784                        if let Expression::Parameter(ref p) = item.name {
7785                            if p.style == crate::expressions::ParameterStyle::At {
7786                                if let Some(ref name) = p.name {
7787                                    item.name = Expression::Identifier(Identifier::new(name));
7788                                    changed = true;
7789                                }
7790                            }
7791                        }
7792                        // Strip @ from the SET name (Identifier style - SET parser)
7793                        if let Expression::Identifier(ref id) = item.name {
7794                            if id.name.starts_with('@') {
7795                                let var_name = id.name.trim_start_matches('@').to_string();
7796                                item.name = Expression::Identifier(Identifier::new(&var_name));
7797                                changed = true;
7798                            }
7799                        }
7800                        // Strip @ from the SET name (Column style - alternative parsing)
7801                        if let Expression::Column(ref col) = item.name {
7802                            if col.name.name.starts_with('@') && col.table.is_none() {
7803                                let var_name = col.name.name.trim_start_matches('@').to_string();
7804                                item.name = Expression::Identifier(Identifier::new(&var_name));
7805                                changed = true;
7806                            }
7807                        }
7808                    }
7809                    if changed {
7810                        let mut new_set = (**s).clone();
7811                        new_set.items = new_items;
7812                        return Ok(Expression::SetStatement(Box::new(new_set)));
7813                    }
7814                }
7815            }
7816
7817            // Strip NOLOCK hint for non-TSQL targets
7818            if matches!(source, DialectType::TSQL | DialectType::Fabric)
7819                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
7820            {
7821                if let Expression::Table(ref tr) = e {
7822                    if !tr.hints.is_empty() {
7823                        let mut new_tr = tr.clone();
7824                        new_tr.hints.clear();
7825                        return Ok(Expression::Table(new_tr));
7826                    }
7827                }
7828            }
7829
7830            // Snowflake: TRUE IS TRUE -> TRUE, FALSE IS FALSE -> FALSE
7831            // Snowflake simplifies IS TRUE/IS FALSE on boolean literals
7832            if matches!(target, DialectType::Snowflake) {
7833                if let Expression::IsTrue(ref itf) = e {
7834                    if let Expression::Boolean(ref b) = itf.this {
7835                        if !itf.not {
7836                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
7837                                value: b.value,
7838                            }));
7839                        } else {
7840                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
7841                                value: !b.value,
7842                            }));
7843                        }
7844                    }
7845                }
7846                if let Expression::IsFalse(ref itf) = e {
7847                    if let Expression::Boolean(ref b) = itf.this {
7848                        if !itf.not {
7849                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
7850                                value: !b.value,
7851                            }));
7852                        } else {
7853                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
7854                                value: b.value,
7855                            }));
7856                        }
7857                    }
7858                }
7859            }
7860
7861            // BigQuery: split dotted backtick identifiers in table names
7862            // e.g., `a.b.c` -> "a"."b"."c" when source is BigQuery and target is not BigQuery
7863            if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
7864                if let Expression::CreateTable(ref ct) = e {
7865                    let mut changed = false;
7866                    let mut new_ct = ct.clone();
7867                    // Split the table name
7868                    if ct.name.schema.is_none() && ct.name.name.name.contains('.') {
7869                        let parts: Vec<&str> = ct.name.name.name.split('.').collect();
7870                        // Use quoted identifiers when the original was quoted (backtick in BigQuery)
7871                        let was_quoted = ct.name.name.quoted;
7872                        let mk_id = |s: &str| {
7873                            if was_quoted {
7874                                Identifier::quoted(s)
7875                            } else {
7876                                Identifier::new(s)
7877                            }
7878                        };
7879                        if parts.len() == 3 {
7880                            new_ct.name.catalog = Some(mk_id(parts[0]));
7881                            new_ct.name.schema = Some(mk_id(parts[1]));
7882                            new_ct.name.name = mk_id(parts[2]);
7883                            changed = true;
7884                        } else if parts.len() == 2 {
7885                            new_ct.name.schema = Some(mk_id(parts[0]));
7886                            new_ct.name.name = mk_id(parts[1]);
7887                            changed = true;
7888                        }
7889                    }
7890                    // Split the clone source name
7891                    if let Some(ref clone_src) = ct.clone_source {
7892                        if clone_src.schema.is_none() && clone_src.name.name.contains('.') {
7893                            let parts: Vec<&str> = clone_src.name.name.split('.').collect();
7894                            let was_quoted = clone_src.name.quoted;
7895                            let mk_id = |s: &str| {
7896                                if was_quoted {
7897                                    Identifier::quoted(s)
7898                                } else {
7899                                    Identifier::new(s)
7900                                }
7901                            };
7902                            let mut new_src = clone_src.clone();
7903                            if parts.len() == 3 {
7904                                new_src.catalog = Some(mk_id(parts[0]));
7905                                new_src.schema = Some(mk_id(parts[1]));
7906                                new_src.name = mk_id(parts[2]);
7907                                new_ct.clone_source = Some(new_src);
7908                                changed = true;
7909                            } else if parts.len() == 2 {
7910                                new_src.schema = Some(mk_id(parts[0]));
7911                                new_src.name = mk_id(parts[1]);
7912                                new_ct.clone_source = Some(new_src);
7913                                changed = true;
7914                            }
7915                        }
7916                    }
7917                    if changed {
7918                        return Ok(Expression::CreateTable(new_ct));
7919                    }
7920                }
7921            }
7922
7923            // BigQuery array subscript: a[1], b[OFFSET(1)], c[ORDINAL(1)], d[SAFE_OFFSET(1)], e[SAFE_ORDINAL(1)]
7924            // -> DuckDB/Presto: convert 0-based to 1-based, handle SAFE_* -> ELEMENT_AT for Presto
7925            if matches!(source, DialectType::BigQuery)
7926                && matches!(
7927                    target,
7928                    DialectType::DuckDB
7929                        | DialectType::Presto
7930                        | DialectType::Trino
7931                        | DialectType::Athena
7932                )
7933            {
7934                if let Expression::Subscript(ref sub) = e {
7935                    let (new_index, is_safe) = match &sub.index {
7936                        // a[1] -> a[1+1] = a[2] (plain index is 0-based in BQ)
7937                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
7938                            let Literal::Number(n) = lit.as_ref() else {
7939                                unreachable!()
7940                            };
7941                            if let Ok(val) = n.parse::<i64>() {
7942                                (
7943                                    Some(Expression::Literal(Box::new(Literal::Number(
7944                                        (val + 1).to_string(),
7945                                    )))),
7946                                    false,
7947                                )
7948                            } else {
7949                                (None, false)
7950                            }
7951                        }
7952                        // OFFSET(n) -> n+1 (0-based)
7953                        Expression::Function(ref f)
7954                            if f.name.eq_ignore_ascii_case("OFFSET") && f.args.len() == 1 =>
7955                        {
7956                            if let Expression::Literal(lit) = &f.args[0] {
7957                                if let Literal::Number(n) = lit.as_ref() {
7958                                    if let Ok(val) = n.parse::<i64>() {
7959                                        (
7960                                            Some(Expression::Literal(Box::new(Literal::Number(
7961                                                (val + 1).to_string(),
7962                                            )))),
7963                                            false,
7964                                        )
7965                                    } else {
7966                                        (
7967                                            Some(Expression::Add(Box::new(
7968                                                crate::expressions::BinaryOp::new(
7969                                                    f.args[0].clone(),
7970                                                    Expression::number(1),
7971                                                ),
7972                                            ))),
7973                                            false,
7974                                        )
7975                                    }
7976                                } else {
7977                                    (None, false)
7978                                }
7979                            } else {
7980                                (
7981                                    Some(Expression::Add(Box::new(
7982                                        crate::expressions::BinaryOp::new(
7983                                            f.args[0].clone(),
7984                                            Expression::number(1),
7985                                        ),
7986                                    ))),
7987                                    false,
7988                                )
7989                            }
7990                        }
7991                        // ORDINAL(n) -> n (already 1-based)
7992                        Expression::Function(ref f)
7993                            if f.name.eq_ignore_ascii_case("ORDINAL") && f.args.len() == 1 =>
7994                        {
7995                            (Some(f.args[0].clone()), false)
7996                        }
7997                        // SAFE_OFFSET(n) -> n+1 (0-based, safe)
7998                        Expression::Function(ref f)
7999                            if f.name.eq_ignore_ascii_case("SAFE_OFFSET") && f.args.len() == 1 =>
8000                        {
8001                            if let Expression::Literal(lit) = &f.args[0] {
8002                                if let Literal::Number(n) = lit.as_ref() {
8003                                    if let Ok(val) = n.parse::<i64>() {
8004                                        (
8005                                            Some(Expression::Literal(Box::new(Literal::Number(
8006                                                (val + 1).to_string(),
8007                                            )))),
8008                                            true,
8009                                        )
8010                                    } else {
8011                                        (
8012                                            Some(Expression::Add(Box::new(
8013                                                crate::expressions::BinaryOp::new(
8014                                                    f.args[0].clone(),
8015                                                    Expression::number(1),
8016                                                ),
8017                                            ))),
8018                                            true,
8019                                        )
8020                                    }
8021                                } else {
8022                                    (None, false)
8023                                }
8024                            } else {
8025                                (
8026                                    Some(Expression::Add(Box::new(
8027                                        crate::expressions::BinaryOp::new(
8028                                            f.args[0].clone(),
8029                                            Expression::number(1),
8030                                        ),
8031                                    ))),
8032                                    true,
8033                                )
8034                            }
8035                        }
8036                        // SAFE_ORDINAL(n) -> n (already 1-based, safe)
8037                        Expression::Function(ref f)
8038                            if f.name.eq_ignore_ascii_case("SAFE_ORDINAL") && f.args.len() == 1 =>
8039                        {
8040                            (Some(f.args[0].clone()), true)
8041                        }
8042                        _ => (None, false),
8043                    };
8044                    if let Some(idx) = new_index {
8045                        if is_safe
8046                            && matches!(
8047                                target,
8048                                DialectType::Presto | DialectType::Trino | DialectType::Athena
8049                            )
8050                        {
8051                            // Presto: SAFE_OFFSET/SAFE_ORDINAL -> ELEMENT_AT(arr, idx)
8052                            return Ok(Expression::Function(Box::new(Function::new(
8053                                "ELEMENT_AT".to_string(),
8054                                vec![sub.this.clone(), idx],
8055                            ))));
8056                        } else {
8057                            // DuckDB or non-safe: just use subscript with converted index
8058                            return Ok(Expression::Subscript(Box::new(
8059                                crate::expressions::Subscript {
8060                                    this: sub.this.clone(),
8061                                    index: idx,
8062                                },
8063                            )));
8064                        }
8065                    }
8066                }
8067            }
8068
8069            // BigQuery LENGTH(x) -> DuckDB CASE TYPEOF(x) WHEN 'BLOB' THEN OCTET_LENGTH(...) ELSE LENGTH(...) END
8070            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
8071                if let Expression::Length(ref uf) = e {
8072                    let arg = uf.this.clone();
8073                    let typeof_func = Expression::Function(Box::new(Function::new(
8074                        "TYPEOF".to_string(),
8075                        vec![arg.clone()],
8076                    )));
8077                    let blob_cast = Expression::Cast(Box::new(Cast {
8078                        this: arg.clone(),
8079                        to: DataType::VarBinary { length: None },
8080                        trailing_comments: vec![],
8081                        double_colon_syntax: false,
8082                        format: None,
8083                        default: None,
8084                        inferred_type: None,
8085                    }));
8086                    let octet_length = Expression::Function(Box::new(Function::new(
8087                        "OCTET_LENGTH".to_string(),
8088                        vec![blob_cast],
8089                    )));
8090                    let text_cast = Expression::Cast(Box::new(Cast {
8091                        this: arg,
8092                        to: DataType::Text,
8093                        trailing_comments: vec![],
8094                        double_colon_syntax: false,
8095                        format: None,
8096                        default: None,
8097                        inferred_type: None,
8098                    }));
8099                    let length_text = Expression::Length(Box::new(crate::expressions::UnaryFunc {
8100                        this: text_cast,
8101                        original_name: None,
8102                        inferred_type: None,
8103                    }));
8104                    return Ok(Expression::Case(Box::new(Case {
8105                        operand: Some(typeof_func),
8106                        whens: vec![(
8107                            Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
8108                            octet_length,
8109                        )],
8110                        else_: Some(length_text),
8111                        comments: Vec::new(),
8112                        inferred_type: None,
8113                    })));
8114                }
8115            }
8116
8117            // BigQuery UNNEST alias handling (only for non-BigQuery sources):
8118            // UNNEST(...) AS x -> UNNEST(...) (drop unused table alias)
8119            // UNNEST(...) AS x(y) -> UNNEST(...) AS y (use column alias as main alias)
8120            if matches!(target, DialectType::BigQuery) && !matches!(source, DialectType::BigQuery) {
8121                if let Expression::Alias(ref a) = e {
8122                    if matches!(&a.this, Expression::Unnest(_)) {
8123                        if a.column_aliases.is_empty() {
8124                            // Drop the entire alias, return just the UNNEST expression
8125                            return Ok(a.this.clone());
8126                        } else {
8127                            // Use first column alias as the main alias
8128                            let mut new_alias = a.as_ref().clone();
8129                            new_alias.alias = a.column_aliases[0].clone();
8130                            new_alias.column_aliases.clear();
8131                            return Ok(Expression::Alias(Box::new(new_alias)));
8132                        }
8133                    }
8134                }
8135            }
8136
8137            // BigQuery IN UNNEST(expr) -> IN (SELECT UNNEST/EXPLODE(expr)) for non-BigQuery targets
8138            if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
8139                if let Expression::In(ref in_expr) = e {
8140                    if let Some(ref unnest_inner) = in_expr.unnest {
8141                        // Build the function call for the target dialect
8142                        let func_expr = if matches!(
8143                            target,
8144                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
8145                        ) {
8146                            // Use EXPLODE for Hive/Spark
8147                            Expression::Function(Box::new(Function::new(
8148                                "EXPLODE".to_string(),
8149                                vec![*unnest_inner.clone()],
8150                            )))
8151                        } else {
8152                            // Use UNNEST for Presto/Trino/DuckDB/etc.
8153                            Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
8154                                this: *unnest_inner.clone(),
8155                                expressions: Vec::new(),
8156                                with_ordinality: false,
8157                                alias: None,
8158                                offset_alias: None,
8159                            }))
8160                        };
8161
8162                        // Wrap in SELECT
8163                        let mut inner_select = crate::expressions::Select::new();
8164                        inner_select.expressions = vec![func_expr];
8165
8166                        let subquery_expr = Expression::Select(Box::new(inner_select));
8167
8168                        return Ok(Expression::In(Box::new(crate::expressions::In {
8169                            this: in_expr.this.clone(),
8170                            expressions: Vec::new(),
8171                            query: Some(subquery_expr),
8172                            not: in_expr.not,
8173                            global: in_expr.global,
8174                            unnest: None,
8175                            is_field: false,
8176                        })));
8177                    }
8178                }
8179            }
8180
8181            // SQLite: GENERATE_SERIES AS t(i) -> (SELECT value AS i FROM GENERATE_SERIES(...)) AS t
8182            // This handles the subquery wrapping for RANGE -> GENERATE_SERIES in FROM context
8183            if matches!(target, DialectType::SQLite) && matches!(source, DialectType::DuckDB) {
8184                if let Expression::Alias(ref a) = e {
8185                    if let Expression::Function(ref f) = a.this {
8186                        if f.name.eq_ignore_ascii_case("GENERATE_SERIES")
8187                            && !a.column_aliases.is_empty()
8188                        {
8189                            // Build: (SELECT value AS col_alias FROM GENERATE_SERIES(start, end)) AS table_alias
8190                            let col_alias = a.column_aliases[0].clone();
8191                            let mut inner_select = crate::expressions::Select::new();
8192                            inner_select.expressions =
8193                                vec![Expression::Alias(Box::new(crate::expressions::Alias::new(
8194                                    Expression::Identifier(Identifier::new("value".to_string())),
8195                                    col_alias,
8196                                )))];
8197                            inner_select.from = Some(crate::expressions::From {
8198                                expressions: vec![a.this.clone()],
8199                            });
8200                            let subquery =
8201                                Expression::Subquery(Box::new(crate::expressions::Subquery {
8202                                    this: Expression::Select(Box::new(inner_select)),
8203                                    alias: Some(a.alias.clone()),
8204                                    column_aliases: Vec::new(),
8205                                    alias_explicit_as: false,
8206                                    alias_keyword: None,
8207                                    order_by: None,
8208                                    limit: None,
8209                                    offset: None,
8210                                    lateral: false,
8211                                    modifiers_inside: false,
8212                                    trailing_comments: Vec::new(),
8213                                    distribute_by: None,
8214                                    sort_by: None,
8215                                    cluster_by: None,
8216                                    inferred_type: None,
8217                                }));
8218                            return Ok(subquery);
8219                        }
8220                    }
8221                }
8222            }
8223
8224            // BigQuery implicit UNNEST: comma-join on array path -> CROSS JOIN UNNEST
8225            // e.g., SELECT results FROM Coordinates, Coordinates.position AS results
8226            //     -> SELECT results FROM Coordinates CROSS JOIN UNNEST(Coordinates.position) AS results
8227            if matches!(source, DialectType::BigQuery) {
8228                if let Expression::Select(ref s) = e {
8229                    if let Some(ref from) = s.from {
8230                        if from.expressions.len() >= 2 {
8231                            // Collect table names from first expression
8232                            let first_tables: Vec<String> = from
8233                                .expressions
8234                                .iter()
8235                                .take(1)
8236                                .filter_map(|expr| {
8237                                    if let Expression::Table(t) = expr {
8238                                        Some(t.name.name.to_ascii_lowercase())
8239                                    } else {
8240                                        None
8241                                    }
8242                                })
8243                                .collect();
8244
8245                            // Check if any subsequent FROM expressions are schema-qualified with a matching table name
8246                            // or have a dotted name matching a table
8247                            let mut needs_rewrite = false;
8248                            for expr in from.expressions.iter().skip(1) {
8249                                if let Expression::Table(t) = expr {
8250                                    if let Some(ref schema) = t.schema {
8251                                        if first_tables.contains(&schema.name.to_ascii_lowercase())
8252                                        {
8253                                            needs_rewrite = true;
8254                                            break;
8255                                        }
8256                                    }
8257                                    // Also check dotted names in quoted identifiers (e.g., `Coordinates.position`)
8258                                    if t.schema.is_none() && t.name.name.contains('.') {
8259                                        let parts: Vec<&str> = t.name.name.split('.').collect();
8260                                        if parts.len() >= 2
8261                                            && first_tables.contains(&parts[0].to_ascii_lowercase())
8262                                        {
8263                                            needs_rewrite = true;
8264                                            break;
8265                                        }
8266                                    }
8267                                }
8268                            }
8269
8270                            if needs_rewrite {
8271                                let mut new_select = s.clone();
8272                                let mut new_from_exprs = vec![from.expressions[0].clone()];
8273                                let mut new_joins = s.joins.clone();
8274
8275                                for expr in from.expressions.iter().skip(1) {
8276                                    if let Expression::Table(ref t) = expr {
8277                                        if let Some(ref schema) = t.schema {
8278                                            if first_tables
8279                                                .contains(&schema.name.to_ascii_lowercase())
8280                                            {
8281                                                // This is an array path reference, convert to CROSS JOIN UNNEST
8282                                                let col_expr = Expression::Column(Box::new(
8283                                                    crate::expressions::Column {
8284                                                        name: t.name.clone(),
8285                                                        table: Some(schema.clone()),
8286                                                        join_mark: false,
8287                                                        trailing_comments: vec![],
8288                                                        span: None,
8289                                                        inferred_type: None,
8290                                                    },
8291                                                ));
8292                                                let unnest_expr = Expression::Unnest(Box::new(
8293                                                    crate::expressions::UnnestFunc {
8294                                                        this: col_expr,
8295                                                        expressions: Vec::new(),
8296                                                        with_ordinality: false,
8297                                                        alias: None,
8298                                                        offset_alias: None,
8299                                                    },
8300                                                ));
8301                                                let join_this = if let Some(ref alias) = t.alias {
8302                                                    if matches!(
8303                                                        target,
8304                                                        DialectType::Presto
8305                                                            | DialectType::Trino
8306                                                            | DialectType::Athena
8307                                                    ) {
8308                                                        // Presto: UNNEST(x) AS _t0(results)
8309                                                        Expression::Alias(Box::new(
8310                                                            crate::expressions::Alias {
8311                                                                this: unnest_expr,
8312                                                                alias: Identifier::new("_t0"),
8313                                                                column_aliases: vec![alias.clone()],
8314                                                                alias_explicit_as: false,
8315                                                                alias_keyword: None,
8316                                                                pre_alias_comments: vec![],
8317                                                                trailing_comments: vec![],
8318                                                                inferred_type: None,
8319                                                            },
8320                                                        ))
8321                                                    } else {
8322                                                        // BigQuery: UNNEST(x) AS results
8323                                                        Expression::Alias(Box::new(
8324                                                            crate::expressions::Alias {
8325                                                                this: unnest_expr,
8326                                                                alias: alias.clone(),
8327                                                                column_aliases: vec![],
8328                                                                alias_explicit_as: false,
8329                                                                alias_keyword: None,
8330                                                                pre_alias_comments: vec![],
8331                                                                trailing_comments: vec![],
8332                                                                inferred_type: None,
8333                                                            },
8334                                                        ))
8335                                                    }
8336                                                } else {
8337                                                    unnest_expr
8338                                                };
8339                                                new_joins.push(crate::expressions::Join {
8340                                                    kind: crate::expressions::JoinKind::Cross,
8341                                                    this: join_this,
8342                                                    on: None,
8343                                                    using: Vec::new(),
8344                                                    use_inner_keyword: false,
8345                                                    use_outer_keyword: false,
8346                                                    deferred_condition: false,
8347                                                    join_hint: None,
8348                                                    match_condition: None,
8349                                                    pivots: Vec::new(),
8350                                                    comments: Vec::new(),
8351                                                    nesting_group: 0,
8352                                                    directed: false,
8353                                                });
8354                                            } else {
8355                                                new_from_exprs.push(expr.clone());
8356                                            }
8357                                        } else if t.schema.is_none() && t.name.name.contains('.') {
8358                                            // Dotted name in quoted identifier: `Coordinates.position`
8359                                            let parts: Vec<&str> = t.name.name.split('.').collect();
8360                                            if parts.len() >= 2
8361                                                && first_tables
8362                                                    .contains(&parts[0].to_ascii_lowercase())
8363                                            {
8364                                                let join_this =
8365                                                    if matches!(target, DialectType::BigQuery) {
8366                                                        // BigQuery: keep as single quoted identifier, just convert comma -> CROSS JOIN
8367                                                        Expression::Table(t.clone())
8368                                                    } else {
8369                                                        // Other targets: split into "schema"."name"
8370                                                        let mut new_t = t.clone();
8371                                                        new_t.schema =
8372                                                            Some(Identifier::quoted(parts[0]));
8373                                                        new_t.name = Identifier::quoted(parts[1]);
8374                                                        Expression::Table(new_t)
8375                                                    };
8376                                                new_joins.push(crate::expressions::Join {
8377                                                    kind: crate::expressions::JoinKind::Cross,
8378                                                    this: join_this,
8379                                                    on: None,
8380                                                    using: Vec::new(),
8381                                                    use_inner_keyword: false,
8382                                                    use_outer_keyword: false,
8383                                                    deferred_condition: false,
8384                                                    join_hint: None,
8385                                                    match_condition: None,
8386                                                    pivots: Vec::new(),
8387                                                    comments: Vec::new(),
8388                                                    nesting_group: 0,
8389                                                    directed: false,
8390                                                });
8391                                            } else {
8392                                                new_from_exprs.push(expr.clone());
8393                                            }
8394                                        } else {
8395                                            new_from_exprs.push(expr.clone());
8396                                        }
8397                                    } else {
8398                                        new_from_exprs.push(expr.clone());
8399                                    }
8400                                }
8401
8402                                new_select.from = Some(crate::expressions::From {
8403                                    expressions: new_from_exprs,
8404                                    ..from.clone()
8405                                });
8406                                new_select.joins = new_joins;
8407                                return Ok(Expression::Select(new_select));
8408                            }
8409                        }
8410                    }
8411                }
8412            }
8413
8414            // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE for Hive/Spark
8415            if matches!(
8416                target,
8417                DialectType::Hive | DialectType::Spark | DialectType::Databricks
8418            ) {
8419                if let Expression::Select(ref s) = e {
8420                    // Check if any joins are CROSS JOIN with UNNEST/EXPLODE
8421                    let is_unnest_or_explode_expr = |expr: &Expression| -> bool {
8422                        matches!(expr, Expression::Unnest(_))
8423                            || matches!(expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("EXPLODE"))
8424                    };
8425                    let has_unnest_join = s.joins.iter().any(|j| {
8426                        j.kind == crate::expressions::JoinKind::Cross && (
8427                            matches!(&j.this, Expression::Alias(a) if is_unnest_or_explode_expr(&a.this))
8428                            || is_unnest_or_explode_expr(&j.this)
8429                        )
8430                    });
8431                    if has_unnest_join {
8432                        let mut select = s.clone();
8433                        let mut new_joins = Vec::new();
8434                        for join in select.joins.drain(..) {
8435                            if join.kind == crate::expressions::JoinKind::Cross {
8436                                // Extract the UNNEST/EXPLODE from the join
8437                                let (func_expr, table_alias, col_aliases) = match &join.this {
8438                                    Expression::Alias(a) => {
8439                                        let ta = if a.alias.is_empty() {
8440                                            None
8441                                        } else {
8442                                            Some(a.alias.clone())
8443                                        };
8444                                        let cas = a.column_aliases.clone();
8445                                        match &a.this {
8446                                            Expression::Unnest(u) => {
8447                                                // Multi-arg UNNEST(y, z) -> INLINE(ARRAYS_ZIP(y, z))
8448                                                if !u.expressions.is_empty() {
8449                                                    let mut all_args = vec![u.this.clone()];
8450                                                    all_args.extend(u.expressions.clone());
8451                                                    let arrays_zip =
8452                                                        Expression::Function(Box::new(
8453                                                            crate::expressions::Function::new(
8454                                                                "ARRAYS_ZIP".to_string(),
8455                                                                all_args,
8456                                                            ),
8457                                                        ));
8458                                                    let inline = Expression::Function(Box::new(
8459                                                        crate::expressions::Function::new(
8460                                                            "INLINE".to_string(),
8461                                                            vec![arrays_zip],
8462                                                        ),
8463                                                    ));
8464                                                    (Some(inline), ta, a.column_aliases.clone())
8465                                                } else {
8466                                                    // Convert UNNEST(x) to EXPLODE(x) or POSEXPLODE(x)
8467                                                    let func_name = if u.with_ordinality {
8468                                                        "POSEXPLODE"
8469                                                    } else {
8470                                                        "EXPLODE"
8471                                                    };
8472                                                    let explode = Expression::Function(Box::new(
8473                                                        crate::expressions::Function::new(
8474                                                            func_name.to_string(),
8475                                                            vec![u.this.clone()],
8476                                                        ),
8477                                                    ));
8478                                                    // For POSEXPLODE, add 'pos' to column aliases
8479                                                    let cas = if u.with_ordinality {
8480                                                        let mut pos_aliases =
8481                                                            vec![Identifier::new(
8482                                                                "pos".to_string(),
8483                                                            )];
8484                                                        pos_aliases
8485                                                            .extend(a.column_aliases.clone());
8486                                                        pos_aliases
8487                                                    } else {
8488                                                        a.column_aliases.clone()
8489                                                    };
8490                                                    (Some(explode), ta, cas)
8491                                                }
8492                                            }
8493                                            Expression::Function(f)
8494                                                if f.name.eq_ignore_ascii_case("EXPLODE") =>
8495                                            {
8496                                                (Some(Expression::Function(f.clone())), ta, cas)
8497                                            }
8498                                            _ => (None, None, Vec::new()),
8499                                        }
8500                                    }
8501                                    Expression::Unnest(u) => {
8502                                        let func_name = if u.with_ordinality {
8503                                            "POSEXPLODE"
8504                                        } else {
8505                                            "EXPLODE"
8506                                        };
8507                                        let explode = Expression::Function(Box::new(
8508                                            crate::expressions::Function::new(
8509                                                func_name.to_string(),
8510                                                vec![u.this.clone()],
8511                                            ),
8512                                        ));
8513                                        let ta = u.alias.clone();
8514                                        let col_aliases = if u.with_ordinality {
8515                                            vec![Identifier::new("pos".to_string())]
8516                                        } else {
8517                                            Vec::new()
8518                                        };
8519                                        (Some(explode), ta, col_aliases)
8520                                    }
8521                                    _ => (None, None, Vec::new()),
8522                                };
8523                                if let Some(func) = func_expr {
8524                                    select.lateral_views.push(crate::expressions::LateralView {
8525                                        this: func,
8526                                        table_alias,
8527                                        column_aliases: col_aliases,
8528                                        outer: false,
8529                                    });
8530                                } else {
8531                                    new_joins.push(join);
8532                                }
8533                            } else {
8534                                new_joins.push(join);
8535                            }
8536                        }
8537                        select.joins = new_joins;
8538                        return Ok(Expression::Select(select));
8539                    }
8540                }
8541            }
8542
8543            // UNNEST expansion: DuckDB SELECT UNNEST(arr) in SELECT list -> expanded query
8544            // for BigQuery, Presto/Trino, Snowflake
8545            if matches!(source, DialectType::DuckDB | DialectType::PostgreSQL)
8546                && matches!(
8547                    target,
8548                    DialectType::BigQuery
8549                        | DialectType::Presto
8550                        | DialectType::Trino
8551                        | DialectType::Snowflake
8552                )
8553            {
8554                if let Expression::Select(ref s) = e {
8555                    // Check if any SELECT expressions contain UNNEST
8556                    // Note: UNNEST can appear as Expression::Unnest OR Expression::Function("UNNEST")
8557                    let has_unnest_in_select = s.expressions.iter().any(|expr| {
8558                        fn contains_unnest(e: &Expression) -> bool {
8559                            match e {
8560                                Expression::Unnest(_) => true,
8561                                Expression::Function(f)
8562                                    if f.name.eq_ignore_ascii_case("UNNEST") =>
8563                                {
8564                                    true
8565                                }
8566                                Expression::Alias(a) => contains_unnest(&a.this),
8567                                Expression::Add(op)
8568                                | Expression::Sub(op)
8569                                | Expression::Mul(op)
8570                                | Expression::Div(op) => {
8571                                    contains_unnest(&op.left) || contains_unnest(&op.right)
8572                                }
8573                                _ => false,
8574                            }
8575                        }
8576                        contains_unnest(expr)
8577                    });
8578
8579                    if has_unnest_in_select {
8580                        let rewritten = Self::rewrite_unnest_expansion(s, target);
8581                        if let Some(new_select) = rewritten {
8582                            return Ok(Expression::Select(Box::new(new_select)));
8583                        }
8584                    }
8585                }
8586            }
8587
8588            // BigQuery -> PostgreSQL: convert escape sequences in string literals to actual characters
8589            // BigQuery '\n' -> PostgreSQL literal newline in string
8590            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::PostgreSQL)
8591            {
8592                if let Expression::Literal(ref lit) = e {
8593                    if let Literal::String(ref s) = lit.as_ref() {
8594                        if s.contains("\\n")
8595                            || s.contains("\\t")
8596                            || s.contains("\\r")
8597                            || s.contains("\\\\")
8598                        {
8599                            let converted = s
8600                                .replace("\\n", "\n")
8601                                .replace("\\t", "\t")
8602                                .replace("\\r", "\r")
8603                                .replace("\\\\", "\\");
8604                            return Ok(Expression::Literal(Box::new(Literal::String(converted))));
8605                        }
8606                    }
8607                }
8608            }
8609
8610            // Cross-dialect: convert Literal::Timestamp to target-specific CAST form
8611            // when source != target (identity tests keep the Literal::Timestamp for native handling)
8612            if source != target {
8613                if let Expression::Literal(ref lit) = e {
8614                    if let Literal::Timestamp(ref s) = lit.as_ref() {
8615                        let s = s.clone();
8616                        // MySQL: TIMESTAMP handling depends on source dialect
8617                        // BigQuery TIMESTAMP is timezone-aware -> TIMESTAMP() function in MySQL
8618                        // Other sources' TIMESTAMP is non-timezone -> CAST('x' AS DATETIME) in MySQL
8619                        if matches!(target, DialectType::MySQL) {
8620                            if matches!(source, DialectType::BigQuery) {
8621                                // BigQuery TIMESTAMP is timezone-aware -> MySQL TIMESTAMP() function
8622                                return Ok(Expression::Function(Box::new(Function::new(
8623                                    "TIMESTAMP".to_string(),
8624                                    vec![Expression::Literal(Box::new(Literal::String(s)))],
8625                                ))));
8626                            } else {
8627                                // Non-timezone TIMESTAMP -> CAST('x' AS DATETIME) in MySQL
8628                                return Ok(Expression::Cast(Box::new(Cast {
8629                                    this: Expression::Literal(Box::new(Literal::String(s))),
8630                                    to: DataType::Custom {
8631                                        name: "DATETIME".to_string(),
8632                                    },
8633                                    trailing_comments: Vec::new(),
8634                                    double_colon_syntax: false,
8635                                    format: None,
8636                                    default: None,
8637                                    inferred_type: None,
8638                                })));
8639                            }
8640                        }
8641                        let dt = match target {
8642                            DialectType::BigQuery | DialectType::StarRocks => DataType::Custom {
8643                                name: "DATETIME".to_string(),
8644                            },
8645                            DialectType::Snowflake => {
8646                                // BigQuery TIMESTAMP is timezone-aware -> use TIMESTAMPTZ for Snowflake
8647                                if matches!(source, DialectType::BigQuery) {
8648                                    DataType::Custom {
8649                                        name: "TIMESTAMPTZ".to_string(),
8650                                    }
8651                                } else if matches!(
8652                                    source,
8653                                    DialectType::PostgreSQL
8654                                        | DialectType::Redshift
8655                                        | DialectType::Snowflake
8656                                ) {
8657                                    DataType::Timestamp {
8658                                        precision: None,
8659                                        timezone: false,
8660                                    }
8661                                } else {
8662                                    DataType::Custom {
8663                                        name: "TIMESTAMPNTZ".to_string(),
8664                                    }
8665                                }
8666                            }
8667                            DialectType::Spark | DialectType::Databricks => {
8668                                // BigQuery TIMESTAMP is timezone-aware -> use plain TIMESTAMP for Spark/Databricks
8669                                if matches!(source, DialectType::BigQuery) {
8670                                    DataType::Timestamp {
8671                                        precision: None,
8672                                        timezone: false,
8673                                    }
8674                                } else {
8675                                    DataType::Custom {
8676                                        name: "TIMESTAMP_NTZ".to_string(),
8677                                    }
8678                                }
8679                            }
8680                            DialectType::ClickHouse => DataType::Nullable {
8681                                inner: Box::new(DataType::Custom {
8682                                    name: "DateTime".to_string(),
8683                                }),
8684                            },
8685                            DialectType::TSQL | DialectType::Fabric => DataType::Custom {
8686                                name: "DATETIME2".to_string(),
8687                            },
8688                            DialectType::DuckDB => {
8689                                // DuckDB: use TIMESTAMPTZ when source is BigQuery (BQ TIMESTAMP is always UTC/tz-aware)
8690                                // or when the timestamp string explicitly has timezone info
8691                                if matches!(source, DialectType::BigQuery)
8692                                    || Self::timestamp_string_has_timezone(&s)
8693                                {
8694                                    DataType::Custom {
8695                                        name: "TIMESTAMPTZ".to_string(),
8696                                    }
8697                                } else {
8698                                    DataType::Timestamp {
8699                                        precision: None,
8700                                        timezone: false,
8701                                    }
8702                                }
8703                            }
8704                            _ => DataType::Timestamp {
8705                                precision: None,
8706                                timezone: false,
8707                            },
8708                        };
8709                        return Ok(Expression::Cast(Box::new(Cast {
8710                            this: Expression::Literal(Box::new(Literal::String(s))),
8711                            to: dt,
8712                            trailing_comments: vec![],
8713                            double_colon_syntax: false,
8714                            format: None,
8715                            default: None,
8716                            inferred_type: None,
8717                        })));
8718                    }
8719                }
8720            }
8721
8722            // PostgreSQL DELETE requires explicit AS for table aliases
8723            if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
8724                if let Expression::Delete(ref del) = e {
8725                    if del.alias.is_some() && !del.alias_explicit_as {
8726                        let mut new_del = del.clone();
8727                        new_del.alias_explicit_as = true;
8728                        return Ok(Expression::Delete(new_del));
8729                    }
8730                }
8731            }
8732
8733            // UNION/INTERSECT/EXCEPT DISTINCT handling:
8734            // Some dialects require explicit DISTINCT (BigQuery, ClickHouse),
8735            // while others don't support it (Presto, Spark, DuckDB, etc.)
8736            {
8737                let needs_distinct =
8738                    matches!(target, DialectType::BigQuery | DialectType::ClickHouse);
8739                let drop_distinct = matches!(
8740                    target,
8741                    DialectType::Presto
8742                        | DialectType::Trino
8743                        | DialectType::Athena
8744                        | DialectType::Spark
8745                        | DialectType::Databricks
8746                        | DialectType::DuckDB
8747                        | DialectType::Hive
8748                        | DialectType::MySQL
8749                        | DialectType::PostgreSQL
8750                        | DialectType::SQLite
8751                        | DialectType::TSQL
8752                        | DialectType::Redshift
8753                        | DialectType::Snowflake
8754                        | DialectType::Oracle
8755                        | DialectType::Teradata
8756                        | DialectType::Drill
8757                        | DialectType::Doris
8758                        | DialectType::StarRocks
8759                );
8760                match &e {
8761                    Expression::Union(u) if !u.all && needs_distinct && !u.distinct => {
8762                        let mut new_u = (**u).clone();
8763                        new_u.distinct = true;
8764                        return Ok(Expression::Union(Box::new(new_u)));
8765                    }
8766                    Expression::Intersect(i) if !i.all && needs_distinct && !i.distinct => {
8767                        let mut new_i = (**i).clone();
8768                        new_i.distinct = true;
8769                        return Ok(Expression::Intersect(Box::new(new_i)));
8770                    }
8771                    Expression::Except(ex) if !ex.all && needs_distinct && !ex.distinct => {
8772                        let mut new_ex = (**ex).clone();
8773                        new_ex.distinct = true;
8774                        return Ok(Expression::Except(Box::new(new_ex)));
8775                    }
8776                    Expression::Union(u) if u.distinct && drop_distinct => {
8777                        let mut new_u = (**u).clone();
8778                        new_u.distinct = false;
8779                        return Ok(Expression::Union(Box::new(new_u)));
8780                    }
8781                    Expression::Intersect(i) if i.distinct && drop_distinct => {
8782                        let mut new_i = (**i).clone();
8783                        new_i.distinct = false;
8784                        return Ok(Expression::Intersect(Box::new(new_i)));
8785                    }
8786                    Expression::Except(ex) if ex.distinct && drop_distinct => {
8787                        let mut new_ex = (**ex).clone();
8788                        new_ex.distinct = false;
8789                        return Ok(Expression::Except(Box::new(new_ex)));
8790                    }
8791                    _ => {}
8792                }
8793            }
8794
8795            // ClickHouse: MAP('a', '1') -> map('a', '1') (lowercase function name)
8796            if matches!(target, DialectType::ClickHouse) {
8797                if let Expression::Function(ref f) = e {
8798                    if f.name.eq_ignore_ascii_case("MAP") && !f.args.is_empty() {
8799                        let mut new_f = f.as_ref().clone();
8800                        new_f.name = "map".to_string();
8801                        return Ok(Expression::Function(Box::new(new_f)));
8802                    }
8803                }
8804            }
8805
8806            // ClickHouse: INTERSECT ALL -> INTERSECT (ClickHouse doesn't support ALL on INTERSECT)
8807            if matches!(target, DialectType::ClickHouse) {
8808                if let Expression::Intersect(ref i) = e {
8809                    if i.all {
8810                        let mut new_i = (**i).clone();
8811                        new_i.all = false;
8812                        return Ok(Expression::Intersect(Box::new(new_i)));
8813                    }
8814                }
8815            }
8816
8817            // Integer division: a / b -> CAST(a AS DOUBLE) / b for dialects that need it
8818            // Only from Generic source, to prevent double-wrapping
8819            if matches!(source, DialectType::Generic) {
8820                if let Expression::Div(ref op) = e {
8821                    let cast_type = match target {
8822                        DialectType::TSQL | DialectType::Fabric => Some(DataType::Float {
8823                            precision: None,
8824                            scale: None,
8825                            real_spelling: false,
8826                        }),
8827                        DialectType::Drill
8828                        | DialectType::Trino
8829                        | DialectType::Athena
8830                        | DialectType::Presto => Some(DataType::Double {
8831                            precision: None,
8832                            scale: None,
8833                        }),
8834                        DialectType::PostgreSQL
8835                        | DialectType::Redshift
8836                        | DialectType::Materialize
8837                        | DialectType::Teradata
8838                        | DialectType::RisingWave => Some(DataType::Double {
8839                            precision: None,
8840                            scale: None,
8841                        }),
8842                        _ => None,
8843                    };
8844                    if let Some(dt) = cast_type {
8845                        let cast_left = Expression::Cast(Box::new(Cast {
8846                            this: op.left.clone(),
8847                            to: dt,
8848                            double_colon_syntax: false,
8849                            trailing_comments: Vec::new(),
8850                            format: None,
8851                            default: None,
8852                            inferred_type: None,
8853                        }));
8854                        let new_op = crate::expressions::BinaryOp {
8855                            left: cast_left,
8856                            right: op.right.clone(),
8857                            left_comments: op.left_comments.clone(),
8858                            operator_comments: op.operator_comments.clone(),
8859                            trailing_comments: op.trailing_comments.clone(),
8860                            inferred_type: None,
8861                        };
8862                        return Ok(Expression::Div(Box::new(new_op)));
8863                    }
8864                }
8865            }
8866
8867            // CREATE DATABASE -> CREATE SCHEMA for DuckDB target
8868            if matches!(target, DialectType::DuckDB) {
8869                if let Expression::CreateDatabase(db) = e {
8870                    let mut schema = crate::expressions::CreateSchema::new(db.name.name.clone());
8871                    schema.if_not_exists = db.if_not_exists;
8872                    return Ok(Expression::CreateSchema(Box::new(schema)));
8873                }
8874                if let Expression::DropDatabase(db) = e {
8875                    let mut schema = crate::expressions::DropSchema::new(db.name.name.clone());
8876                    schema.if_exists = db.if_exists;
8877                    return Ok(Expression::DropSchema(Box::new(schema)));
8878                }
8879            }
8880
8881            // Strip ClickHouse Nullable(...) wrapper for non-ClickHouse targets
8882            if matches!(source, DialectType::ClickHouse)
8883                && !matches!(target, DialectType::ClickHouse)
8884            {
8885                if let Expression::Cast(ref c) = e {
8886                    if let DataType::Custom { ref name } = c.to {
8887                        if name.len() >= 9
8888                            && name[..9].eq_ignore_ascii_case("NULLABLE(")
8889                            && name.ends_with(")")
8890                        {
8891                            let inner = &name[9..name.len() - 1]; // strip "Nullable(" and ")"
8892                            let inner_upper = inner.to_ascii_uppercase();
8893                            let new_dt = match inner_upper.as_str() {
8894                                "DATETIME" | "DATETIME64" => DataType::Timestamp {
8895                                    precision: None,
8896                                    timezone: false,
8897                                },
8898                                "DATE" => DataType::Date,
8899                                "INT64" | "BIGINT" => DataType::BigInt { length: None },
8900                                "INT32" | "INT" | "INTEGER" => DataType::Int {
8901                                    length: None,
8902                                    integer_spelling: false,
8903                                },
8904                                "FLOAT64" | "DOUBLE" => DataType::Double {
8905                                    precision: None,
8906                                    scale: None,
8907                                },
8908                                "STRING" => DataType::Text,
8909                                _ => DataType::Custom {
8910                                    name: inner.to_string(),
8911                                },
8912                            };
8913                            let mut new_cast = c.clone();
8914                            new_cast.to = new_dt;
8915                            return Ok(Expression::Cast(new_cast));
8916                        }
8917                    }
8918                }
8919            }
8920
8921            // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(...))
8922            if matches!(target, DialectType::Snowflake) {
8923                if let Expression::ArrayConcatAgg(ref agg) = e {
8924                    let mut agg_clone = agg.as_ref().clone();
8925                    agg_clone.name = None; // Clear name so generator uses default "ARRAY_AGG"
8926                    let array_agg = Expression::ArrayAgg(Box::new(agg_clone));
8927                    let flatten = Expression::Function(Box::new(Function::new(
8928                        "ARRAY_FLATTEN".to_string(),
8929                        vec![array_agg],
8930                    )));
8931                    return Ok(flatten);
8932                }
8933            }
8934
8935            // ARRAY_CONCAT_AGG -> others: keep as function for cross-dialect
8936            if !matches!(target, DialectType::BigQuery | DialectType::Snowflake) {
8937                if let Expression::ArrayConcatAgg(agg) = e {
8938                    let arg = agg.this;
8939                    return Ok(Expression::Function(Box::new(Function::new(
8940                        "ARRAY_CONCAT_AGG".to_string(),
8941                        vec![arg],
8942                    ))));
8943                }
8944            }
8945
8946            // Determine what action to take by inspecting e immutably
8947            let action = {
8948                let source_propagates_nulls =
8949                    matches!(source, DialectType::Snowflake | DialectType::BigQuery);
8950                let target_ignores_nulls =
8951                    matches!(target, DialectType::DuckDB | DialectType::PostgreSQL);
8952
8953                match &e {
8954                    Expression::Function(f) => {
8955                        let name = f.name.to_ascii_uppercase();
8956                        // DuckDB json(x) is a synonym for CAST(x AS JSON) — parses a string.
8957                        // Map to JSON_PARSE(x) for Trino/Presto/Athena to preserve semantics.
8958                        if name == "JSON"
8959                            && f.args.len() == 1
8960                            && matches!(source, DialectType::DuckDB)
8961                            && matches!(
8962                                target,
8963                                DialectType::Presto | DialectType::Trino | DialectType::Athena
8964                            )
8965                        {
8966                            Action::DuckDBJsonFuncToJsonParse
8967                        // DuckDB json_valid(x) has no direct Trino equivalent; emit the
8968                        // SQL:2016 `x IS JSON` predicate which has matching semantics.
8969                        } else if name == "JSON_VALID"
8970                            && f.args.len() == 1
8971                            && matches!(source, DialectType::DuckDB)
8972                            && matches!(
8973                                target,
8974                                DialectType::Presto | DialectType::Trino | DialectType::Athena
8975                            )
8976                        {
8977                            Action::DuckDBJsonValidToIsJson
8978                        // DATE_PART: strip quotes from first arg when target is Snowflake (source != Snowflake)
8979                        } else if (name == "DATE_PART" || name == "DATEPART")
8980                            && f.args.len() == 2
8981                            && matches!(target, DialectType::Snowflake)
8982                            && !matches!(source, DialectType::Snowflake)
8983                            && matches!(
8984                                &f.args[0],
8985                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
8986                            )
8987                        {
8988                            Action::DatePartUnquote
8989                        } else if source_propagates_nulls
8990                            && target_ignores_nulls
8991                            && (name == "GREATEST" || name == "LEAST")
8992                            && f.args.len() >= 2
8993                        {
8994                            Action::GreatestLeastNull
8995                        } else if matches!(source, DialectType::Snowflake)
8996                            && name == "ARRAY_GENERATE_RANGE"
8997                            && f.args.len() >= 2
8998                        {
8999                            Action::ArrayGenerateRange
9000                        } else if matches!(source, DialectType::Snowflake)
9001                            && matches!(target, DialectType::DuckDB)
9002                            && name == "DATE_TRUNC"
9003                            && f.args.len() == 2
9004                        {
9005                            // Determine if DuckDB DATE_TRUNC needs CAST wrapping to preserve input type.
9006                            // Logic based on Python sqlglot's input_type_preserved flag:
9007                            // - DATE + non-date-unit (HOUR, MINUTE, etc.) -> wrap
9008                            // - TIMESTAMP + date-unit (YEAR, QUARTER, MONTH, WEEK, DAY) -> wrap
9009                            // - TIMESTAMPTZ/TIMESTAMPLTZ/TIME -> always wrap
9010                            let unit_str = match &f.args[0] {
9011                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => {
9012                                    let crate::expressions::Literal::String(s) = lit.as_ref() else { unreachable!() };
9013                                    Some(s.to_ascii_uppercase())
9014                                }
9015                                _ => None,
9016                            };
9017                            let is_date_unit = unit_str.as_ref().map_or(false, |u| {
9018                                matches!(u.as_str(), "YEAR" | "QUARTER" | "MONTH" | "WEEK" | "DAY")
9019                            });
9020                            match &f.args[1] {
9021                                Expression::Cast(c) => match &c.to {
9022                                    DataType::Time { .. } => Action::DateTruncWrapCast,
9023                                    DataType::Custom { name }
9024                                        if name.eq_ignore_ascii_case("TIMESTAMPTZ")
9025                                            || name.eq_ignore_ascii_case("TIMESTAMPLTZ") =>
9026                                    {
9027                                        Action::DateTruncWrapCast
9028                                    }
9029                                    DataType::Timestamp { timezone: true, .. } => {
9030                                        Action::DateTruncWrapCast
9031                                    }
9032                                    DataType::Date if !is_date_unit => Action::DateTruncWrapCast,
9033                                    DataType::Timestamp {
9034                                        timezone: false, ..
9035                                    } if is_date_unit => Action::DateTruncWrapCast,
9036                                    _ => Action::None,
9037                                },
9038                                _ => Action::None,
9039                            }
9040                        } else if matches!(source, DialectType::Snowflake)
9041                            && matches!(target, DialectType::DuckDB)
9042                            && name == "TO_DATE"
9043                            && f.args.len() == 1
9044                            && !matches!(
9045                                &f.args[0],
9046                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
9047                            )
9048                        {
9049                            Action::ToDateToCast
9050                        } else if !matches!(source, DialectType::Redshift)
9051                            && matches!(target, DialectType::Redshift)
9052                            && name == "CONVERT_TIMEZONE"
9053                            && (f.args.len() == 2 || f.args.len() == 3)
9054                        {
9055                            // Convert Function("CONVERT_TIMEZONE") to Expression::ConvertTimezone
9056                            // so Redshift's transform_expr won't expand 2-arg to 3-arg with 'UTC'.
9057                            // The Redshift parser adds 'UTC' as default source_tz, but when
9058                            // transpiling from other dialects, we should preserve the original form.
9059                            Action::ConvertTimezoneToExpr
9060                        } else if matches!(source, DialectType::Snowflake)
9061                            && matches!(target, DialectType::DuckDB)
9062                            && name == "REGEXP_REPLACE"
9063                            && f.args.len() == 4
9064                            && !matches!(
9065                                &f.args[3],
9066                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
9067                            )
9068                        {
9069                            // Snowflake REGEXP_REPLACE with position arg -> DuckDB needs 'g' flag
9070                            Action::RegexpReplaceSnowflakeToDuckDB
9071                        } else if matches!(source, DialectType::Snowflake)
9072                            && matches!(target, DialectType::DuckDB)
9073                            && name == "REGEXP_REPLACE"
9074                            && f.args.len() == 5
9075                        {
9076                            // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB
9077                            Action::RegexpReplacePositionSnowflakeToDuckDB
9078                        } else if matches!(source, DialectType::Snowflake)
9079                            && matches!(target, DialectType::DuckDB)
9080                            && name == "REGEXP_SUBSTR"
9081                        {
9082                            // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
9083                            Action::RegexpSubstrSnowflakeToDuckDB
9084                        } else if matches!(source, DialectType::Snowflake)
9085                            && matches!(target, DialectType::Snowflake)
9086                            && (name == "REGEXP_SUBSTR" || name == "REGEXP_SUBSTR_ALL")
9087                            && f.args.len() == 6
9088                        {
9089                            // Snowflake identity: strip trailing group=0
9090                            Action::RegexpSubstrSnowflakeIdentity
9091                        } else if matches!(source, DialectType::Snowflake)
9092                            && matches!(target, DialectType::DuckDB)
9093                            && name == "REGEXP_SUBSTR_ALL"
9094                        {
9095                            // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
9096                            Action::RegexpSubstrAllSnowflakeToDuckDB
9097                        } else if matches!(source, DialectType::Snowflake)
9098                            && matches!(target, DialectType::DuckDB)
9099                            && name == "REGEXP_COUNT"
9100                        {
9101                            // Snowflake REGEXP_COUNT -> DuckDB LENGTH(REGEXP_EXTRACT_ALL(...))
9102                            Action::RegexpCountSnowflakeToDuckDB
9103                        } else if matches!(source, DialectType::Snowflake)
9104                            && matches!(target, DialectType::DuckDB)
9105                            && name == "REGEXP_INSTR"
9106                        {
9107                            // Snowflake REGEXP_INSTR -> DuckDB complex CASE expression
9108                            Action::RegexpInstrSnowflakeToDuckDB
9109                        } else if matches!(source, DialectType::BigQuery)
9110                            && matches!(target, DialectType::Snowflake)
9111                            && name == "REGEXP_EXTRACT_ALL"
9112                        {
9113                            // BigQuery REGEXP_EXTRACT_ALL -> Snowflake REGEXP_SUBSTR_ALL
9114                            Action::RegexpExtractAllToSnowflake
9115                        } else if name == "_BQ_TO_HEX" {
9116                            // Internal marker from TO_HEX conversion - bare (no LOWER/UPPER wrapper)
9117                            Action::BigQueryToHexBare
9118                        } else if matches!(source, DialectType::BigQuery)
9119                            && !matches!(target, DialectType::BigQuery)
9120                        {
9121                            // BigQuery-specific functions that need to be converted to standard forms
9122                            match name.as_str() {
9123                                "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF"
9124                                | "DATE_DIFF"
9125                                | "TIMESTAMP_ADD" | "TIMESTAMP_SUB"
9126                                | "DATETIME_ADD" | "DATETIME_SUB"
9127                                | "TIME_ADD" | "TIME_SUB"
9128                                | "DATE_ADD" | "DATE_SUB"
9129                                | "SAFE_DIVIDE"
9130                                | "GENERATE_UUID"
9131                                | "COUNTIF"
9132                                | "EDIT_DISTANCE"
9133                                | "TIMESTAMP_SECONDS" | "TIMESTAMP_MILLIS" | "TIMESTAMP_MICROS"
9134                                | "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" | "DATE_TRUNC"
9135                                | "TO_HEX"
9136                                | "TO_JSON_STRING"
9137                                | "GENERATE_ARRAY" | "GENERATE_TIMESTAMP_ARRAY"
9138                                | "DIV"
9139                                | "UNIX_DATE" | "UNIX_SECONDS" | "UNIX_MILLIS" | "UNIX_MICROS"
9140                                | "LAST_DAY"
9141                                | "TIME" | "DATETIME" | "TIMESTAMP" | "STRING"
9142                                | "REGEXP_CONTAINS"
9143                                | "CONTAINS_SUBSTR"
9144                                | "SAFE_ADD" | "SAFE_SUBTRACT" | "SAFE_MULTIPLY"
9145                                | "SAFE_CAST"
9146                                | "GENERATE_DATE_ARRAY"
9147                                | "PARSE_DATE" | "PARSE_DATETIME" | "PARSE_TIMESTAMP"
9148                                | "FORMAT_DATE" | "FORMAT_DATETIME" | "FORMAT_TIMESTAMP"
9149                                | "ARRAY_CONCAT"
9150                                | "JSON_QUERY" | "JSON_VALUE_ARRAY"
9151                                | "INSTR"
9152                                | "MD5" | "SHA1" | "SHA256" | "SHA512"
9153                                | "GENERATE_UUID()" // just in case
9154                                | "REGEXP_EXTRACT_ALL"
9155                                | "REGEXP_EXTRACT"
9156                                | "INT64"
9157                                | "ARRAY_CONCAT_AGG"
9158                                | "DATE_DIFF(" // just in case
9159                                | "TO_HEX_MD5" // internal
9160                                | "MOD"
9161                                | "CONCAT"
9162                                | "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME"
9163                                | "STRUCT"
9164                                | "ROUND"
9165                                | "MAKE_INTERVAL"
9166                                | "ARRAY_TO_STRING"
9167                                | "PERCENTILE_CONT"
9168                                    => Action::BigQueryFunctionNormalize,
9169                                "ARRAY" if matches!(target, DialectType::Snowflake)
9170                                    && f.args.len() == 1
9171                                    && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"))
9172                                    => Action::BigQueryArraySelectAsStructToSnowflake,
9173                                _ => Action::None,
9174                            }
9175                        } else if matches!(source, DialectType::BigQuery)
9176                            && matches!(target, DialectType::BigQuery)
9177                        {
9178                            // BigQuery -> BigQuery normalizations
9179                            match name.as_str() {
9180                                "TIMESTAMP_DIFF"
9181                                | "DATETIME_DIFF"
9182                                | "TIME_DIFF"
9183                                | "DATE_DIFF"
9184                                | "DATE_ADD"
9185                                | "TO_HEX"
9186                                | "CURRENT_TIMESTAMP"
9187                                | "CURRENT_DATE"
9188                                | "CURRENT_TIME"
9189                                | "CURRENT_DATETIME"
9190                                | "GENERATE_DATE_ARRAY"
9191                                | "INSTR"
9192                                | "FORMAT_DATETIME"
9193                                | "DATETIME"
9194                                | "MAKE_INTERVAL" => Action::BigQueryFunctionNormalize,
9195                                _ => Action::None,
9196                            }
9197                        } else {
9198                            // Generic function normalization for non-BigQuery sources
9199                            match name.as_str() {
9200                                "ARBITRARY" | "AGGREGATE"
9201                                | "REGEXP_MATCHES" | "REGEXP_FULL_MATCH"
9202                                | "STRUCT_EXTRACT"
9203                                | "LIST_FILTER" | "LIST_TRANSFORM" | "LIST_SORT" | "LIST_REVERSE_SORT"
9204                                | "STRING_TO_ARRAY" | "STR_SPLIT" | "STR_SPLIT_REGEX" | "SPLIT_TO_ARRAY"
9205                                | "SUBSTRINGINDEX"
9206                                | "ARRAY_LENGTH" | "SIZE" | "CARDINALITY"
9207                                | "UNICODE"
9208                                | "XOR"
9209                                | "ARRAY_REVERSE_SORT"
9210                                | "ENCODE" | "DECODE"
9211                                | "QUANTILE"
9212                                | "EPOCH" | "EPOCH_MS"
9213                                | "HASHBYTES"
9214                                | "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT"
9215                                | "APPROX_DISTINCT"
9216                                | "DATE_PARSE" | "FORMAT_DATETIME"
9217                                | "REGEXP_EXTRACT" | "REGEXP_SUBSTR" | "TO_DAYS"
9218                                | "RLIKE"
9219                                | "DATEDIFF" | "DATE_DIFF" | "MONTHS_BETWEEN"
9220                                | "ADD_MONTHS" | "DATEADD" | "DATE_ADD" | "DATE_SUB" | "DATETRUNC"
9221                                | "LAST_DAY" | "LAST_DAY_OF_MONTH" | "EOMONTH"
9222                                | "ARRAY_CONSTRUCT" | "ARRAY_CAT" | "ARRAY_COMPACT"
9223                                | "ARRAY_FILTER" | "FILTER" | "REDUCE" | "ARRAY_REVERSE"
9224                                | "MAP" | "MAP_FROM_ENTRIES"
9225                                | "COLLECT_LIST" | "COLLECT_SET"
9226                                | "ISNAN" | "IS_NAN"
9227                                | "TO_UTC_TIMESTAMP" | "FROM_UTC_TIMESTAMP"
9228                                | "FORMAT_NUMBER"
9229                                | "TOMONDAY" | "TOSTARTOFWEEK" | "TOSTARTOFMONTH" | "TOSTARTOFYEAR"
9230                                | "ELEMENT_AT"
9231                                | "EXPLODE" | "EXPLODE_OUTER" | "POSEXPLODE"
9232                                | "SPLIT_PART"
9233                                // GENERATE_SERIES: handled separately below
9234                                | "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR"
9235                                | "JSON_QUERY" | "JSON_VALUE"
9236                                | "JSON_SEARCH"
9237                                | "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
9238                                | "TO_UNIX_TIMESTAMP" | "UNIX_TIMESTAMP"
9239                                | "CURDATE" | "CURTIME"
9240                                | "ARRAY_TO_STRING"
9241                                | "ARRAY_SORT" | "SORT_ARRAY"
9242                                | "LEFT" | "RIGHT"
9243                                | "MAP_FROM_ARRAYS"
9244                                | "LIKE" | "ILIKE"
9245                                | "ARRAY_CONCAT" | "LIST_CONCAT"
9246                                | "QUANTILE_CONT" | "QUANTILE_DISC"
9247                                | "PERCENTILE_CONT" | "PERCENTILE_DISC"
9248                                | "PERCENTILE_APPROX" | "APPROX_PERCENTILE"
9249                                | "LOCATE" | "STRPOS" | "INSTR"
9250                                | "CHAR"
9251                                // CONCAT: handled separately for COALESCE wrapping
9252                                | "ARRAY_JOIN"
9253                                | "ARRAY_CONTAINS" | "HAS" | "CONTAINS"
9254                                | "ISNULL"
9255                                | "MONTHNAME"
9256                                | "TO_TIMESTAMP"
9257                                | "TO_DATE"
9258                                | "TO_JSON"
9259                                | "REGEXP_SPLIT"
9260                                | "SPLIT"
9261                                | "FORMATDATETIME"
9262                                | "ARRAYJOIN"
9263                                | "SPLITBYSTRING" | "SPLITBYREGEXP"
9264                                | "NVL"
9265                                | "TO_CHAR"
9266                                | "DBMS_RANDOM.VALUE"
9267                                | "REGEXP_LIKE"
9268                                | "REPLICATE"
9269                                | "LEN"
9270                                | "COUNT_BIG"
9271                                | "DATEFROMPARTS"
9272                                | "DATETIMEFROMPARTS"
9273                                | "CONVERT" | "TRY_CONVERT"
9274                                | "STRFTIME" | "STRPTIME"
9275                                | "DATE_FORMAT" | "FORMAT_DATE"
9276                                | "PARSE_TIMESTAMP" | "PARSE_DATETIME" | "PARSE_DATE"
9277                                | "FROM_ISO8601_TIMESTAMP" | "FROM_ISO8601_DATE"
9278                                | "FROM_BASE64" | "TO_BASE64"
9279                                | "GETDATE"
9280                                | "TO_HEX" | "FROM_HEX" | "UNHEX" | "HEX"
9281                                | "TO_UTF8" | "FROM_UTF8"
9282                                | "STARTS_WITH" | "STARTSWITH"
9283                                | "APPROX_COUNT_DISTINCT"
9284                                | "JSON_FORMAT"
9285                                | "SYSDATE"
9286                                | "LOGICAL_OR" | "LOGICAL_AND"
9287                                | "MONTHS_ADD"
9288                                | "SCHEMA_NAME"
9289                                | "STRTOL"
9290                                | "EDITDIST3"
9291                                | "FORMAT"
9292                                | "LIST_CONTAINS" | "LIST_HAS"
9293                                | "VARIANCE" | "STDDEV"
9294                                | "ISINF"
9295                                | "TO_UNIXTIME"
9296                                | "FROM_UNIXTIME"
9297                                | "DATEPART" | "DATE_PART"
9298                                | "DATENAME"
9299                                | "STRING_AGG"
9300                                | "JSON_ARRAYAGG"
9301                                | "APPROX_QUANTILE"
9302                                | "MAKE_DATE"
9303                                | "LIST_HAS_ANY" | "ARRAY_HAS_ANY"
9304                                | "RANGE"
9305                                | "TRY_ELEMENT_AT"
9306                                | "STR_TO_MAP"
9307                                | "STRING"
9308                                | "STR_TO_TIME"
9309                                | "CURRENT_SCHEMA"
9310                                | "LTRIM" | "RTRIM"
9311                                | "UUID"
9312                                | "FARM_FINGERPRINT"
9313                                | "JSON_KEYS"
9314                                | "WEEKOFYEAR"
9315                                | "CONCAT_WS"
9316                                | "TRY_DIVIDE"
9317                                | "ARRAY_SLICE"
9318                                | "ARRAY_PREPEND"
9319                                | "ARRAY_REMOVE"
9320                                | "GENERATE_DATE_ARRAY"
9321                                | "PARSE_JSON"
9322                                | "JSON_REMOVE"
9323                                | "JSON_SET"
9324                                | "LEVENSHTEIN"
9325                                | "CURRENT_VERSION"
9326                                | "ARRAY_MAX"
9327                                | "ARRAY_MIN"
9328                                | "JAROWINKLER_SIMILARITY"
9329                                | "CURRENT_SCHEMAS"
9330                                | "TO_VARIANT"
9331                                | "JSON_GROUP_ARRAY" | "JSON_GROUP_OBJECT"
9332                                | "ARRAYS_OVERLAP" | "ARRAY_INTERSECTION"
9333                                    => Action::GenericFunctionNormalize,
9334                                // Canonical date functions -> dialect-specific
9335                                "TS_OR_DS_TO_DATE" => Action::TsOrDsToDateConvert,
9336                                "TS_OR_DS_TO_DATE_STR" if f.args.len() == 1 => Action::TsOrDsToDateStrConvert,
9337                                "DATE_STR_TO_DATE" if f.args.len() == 1 => Action::DateStrToDateConvert,
9338                                "TIME_STR_TO_DATE" if f.args.len() == 1 => Action::TimeStrToDateConvert,
9339                                "TIME_STR_TO_TIME" if f.args.len() <= 2 => Action::TimeStrToTimeConvert,
9340                                "TIME_STR_TO_UNIX" if f.args.len() == 1 => Action::TimeStrToUnixConvert,
9341                                "TIME_TO_TIME_STR" if f.args.len() == 1 => Action::TimeToTimeStrConvert,
9342                                "DATE_TO_DATE_STR" if f.args.len() == 1 => Action::DateToDateStrConvert,
9343                                "DATE_TO_DI" if f.args.len() == 1 => Action::DateToDiConvert,
9344                                "DI_TO_DATE" if f.args.len() == 1 => Action::DiToDateConvert,
9345                                "TS_OR_DI_TO_DI" if f.args.len() == 1 => Action::TsOrDiToDiConvert,
9346                                "UNIX_TO_STR" if f.args.len() == 2 => Action::UnixToStrConvert,
9347                                "UNIX_TO_TIME" if f.args.len() == 1 => Action::UnixToTimeConvert,
9348                                "UNIX_TO_TIME_STR" if f.args.len() == 1 => Action::UnixToTimeStrConvert,
9349                                "TIME_TO_UNIX" if f.args.len() == 1 => Action::TimeToUnixConvert,
9350                                "TIME_TO_STR" if f.args.len() == 2 => Action::TimeToStrConvert,
9351                                "STR_TO_UNIX" if f.args.len() == 2 => Action::StrToUnixConvert,
9352                                // STR_TO_DATE(x, fmt) -> dialect-specific
9353                                "STR_TO_DATE" if f.args.len() == 2
9354                                    && matches!(source, DialectType::Generic) => Action::StrToDateConvert,
9355                                "STR_TO_DATE" => Action::GenericFunctionNormalize,
9356                                // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
9357                                "TS_OR_DS_ADD" if f.args.len() == 3
9358                                    && matches!(source, DialectType::Generic) => Action::TsOrDsAddConvert,
9359                                // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
9360                                "DATE_FROM_UNIX_DATE" if f.args.len() == 1 => Action::DateFromUnixDateConvert,
9361                                // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
9362                                "NVL2" if (f.args.len() == 2 || f.args.len() == 3) => Action::Nvl2Expand,
9363                                // IFNULL(a, b) -> COALESCE(a, b) when coming from Generic source
9364                                "IFNULL" if f.args.len() == 2 => Action::IfnullToCoalesce,
9365                                // IS_ASCII(x) -> dialect-specific
9366                                "IS_ASCII" if f.args.len() == 1 => Action::IsAsciiConvert,
9367                                // STR_POSITION(haystack, needle[, pos[, occ]]) -> dialect-specific
9368                                "STR_POSITION" => Action::StrPositionConvert,
9369                                // ARRAY_SUM -> dialect-specific
9370                                "ARRAY_SUM" => Action::ArraySumConvert,
9371                                // ARRAY_SIZE -> dialect-specific (Drill only)
9372                                "ARRAY_SIZE" if matches!(target, DialectType::Drill) => Action::ArraySizeConvert,
9373                                // ARRAY_ANY -> dialect-specific
9374                                "ARRAY_ANY" if f.args.len() == 2 => Action::ArrayAnyConvert,
9375                                // Functions needing specific cross-dialect transforms
9376                                "MAX_BY" | "MIN_BY" if matches!(target, DialectType::ClickHouse | DialectType::Spark | DialectType::Databricks | DialectType::DuckDB) => Action::MaxByMinByConvert,
9377                                "STRUCT" if matches!(source, DialectType::Spark | DialectType::Databricks)
9378                                    && !matches!(target, DialectType::Spark | DialectType::Databricks | DialectType::Hive) => Action::SparkStructConvert,
9379                                "ARRAY" if matches!(source, DialectType::BigQuery)
9380                                    && matches!(target, DialectType::Snowflake)
9381                                    && f.args.len() == 1
9382                                    && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT")) => Action::BigQueryArraySelectAsStructToSnowflake,
9383                                "ARRAY" if matches!(target, DialectType::Presto | DialectType::Trino | DialectType::Athena | DialectType::BigQuery | DialectType::DuckDB | DialectType::Snowflake | DialectType::ClickHouse | DialectType::StarRocks) => Action::ArraySyntaxConvert,
9384                                "TRUNC" if f.args.len() == 2 && matches!(&f.args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))) && matches!(target, DialectType::Presto | DialectType::Trino | DialectType::ClickHouse) => Action::TruncToDateTrunc,
9385                                "TRUNC" | "TRUNCATE" if f.args.len() <= 2 && !f.args.get(1).map_or(false, |a| matches!(a, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))) => Action::GenericFunctionNormalize,
9386                                // DATE_TRUNC('unit', x) from Generic source -> arg swap for BigQuery/Doris/Spark/MySQL
9387                                "DATE_TRUNC" if f.args.len() == 2
9388                                    && matches!(source, DialectType::Generic)
9389                                    && matches!(target, DialectType::BigQuery | DialectType::Doris | DialectType::StarRocks
9390                                        | DialectType::Spark | DialectType::Databricks | DialectType::MySQL) => Action::DateTruncSwapArgs,
9391                                // TIMESTAMP_TRUNC(x, UNIT) from Generic source -> convert to per-dialect
9392                                "TIMESTAMP_TRUNC" if f.args.len() >= 2
9393                                    && matches!(source, DialectType::Generic) => Action::TimestampTruncConvert,
9394                                "UNIFORM" if matches!(target, DialectType::Snowflake) => Action::GenericFunctionNormalize,
9395                                // GENERATE_SERIES -> SEQUENCE/UNNEST/EXPLODE for target dialects
9396                                "GENERATE_SERIES" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
9397                                    && !matches!(target, DialectType::PostgreSQL | DialectType::Redshift | DialectType::TSQL | DialectType::Fabric) => Action::GenerateSeriesConvert,
9398                                // GENERATE_SERIES with interval normalization for PG target
9399                                "GENERATE_SERIES" if f.args.len() >= 3
9400                                    && matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
9401                                    && matches!(target, DialectType::PostgreSQL | DialectType::Redshift) => Action::GenerateSeriesConvert,
9402                                "GENERATE_SERIES" => Action::None, // passthrough for other cases
9403                                // CONCAT(a, b) -> COALESCE wrapping for Presto/ClickHouse from PostgreSQL
9404                                "CONCAT" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
9405                                    && matches!(target, DialectType::Presto | DialectType::Trino | DialectType::ClickHouse) => Action::ConcatCoalesceWrap,
9406                                "CONCAT" => Action::GenericFunctionNormalize,
9407                                // DIV(a, b) -> target-specific integer division
9408                                "DIV" if f.args.len() == 2
9409                                    && matches!(source, DialectType::PostgreSQL)
9410                                    && matches!(target, DialectType::DuckDB | DialectType::BigQuery | DialectType::SQLite) => Action::DivFuncConvert,
9411                                // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
9412                                "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG" if f.args.len() == 2
9413                                    && matches!(target, DialectType::DuckDB) => Action::JsonObjectAggConvert,
9414                                // JSONB_EXISTS -> JSON_EXISTS for DuckDB
9415                                "JSONB_EXISTS" if f.args.len() == 2
9416                                    && matches!(target, DialectType::DuckDB) => Action::JsonbExistsConvert,
9417                                // DATE_BIN -> TIME_BUCKET for DuckDB
9418                                "DATE_BIN" if matches!(target, DialectType::DuckDB) => Action::DateBinConvert,
9419                                // Multi-arg MIN(a,b,c) -> LEAST, MAX(a,b,c) -> GREATEST
9420                                "MIN" | "MAX" if f.args.len() > 1 && !matches!(target, DialectType::SQLite) => Action::MinMaxToLeastGreatest,
9421                                // ClickHouse uniq -> APPROX_COUNT_DISTINCT for other dialects
9422                                "UNIQ" if matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseUniqToApproxCountDistinct,
9423                                // ClickHouse any -> ANY_VALUE for other dialects
9424                                "ANY" if f.args.len() == 1 && matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseAnyToAnyValue,
9425                                _ => Action::None,
9426                            }
9427                        }
9428                    }
9429                    Expression::AggregateFunction(af) => {
9430                        let name = af.name.to_ascii_uppercase();
9431                        match name.as_str() {
9432                            "ARBITRARY" | "AGGREGATE" => Action::GenericFunctionNormalize,
9433                            "JSON_ARRAYAGG" => Action::GenericFunctionNormalize,
9434                            // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
9435                            "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG"
9436                                if matches!(target, DialectType::DuckDB) =>
9437                            {
9438                                Action::JsonObjectAggConvert
9439                            }
9440                            "ARRAY_AGG"
9441                                if matches!(
9442                                    target,
9443                                    DialectType::Hive
9444                                        | DialectType::Spark
9445                                        | DialectType::Databricks
9446                                ) =>
9447                            {
9448                                Action::ArrayAggToCollectList
9449                            }
9450                            "MAX_BY" | "MIN_BY"
9451                                if matches!(
9452                                    target,
9453                                    DialectType::ClickHouse
9454                                        | DialectType::Spark
9455                                        | DialectType::Databricks
9456                                        | DialectType::DuckDB
9457                                ) =>
9458                            {
9459                                Action::MaxByMinByConvert
9460                            }
9461                            "COLLECT_LIST"
9462                                if matches!(
9463                                    target,
9464                                    DialectType::Presto | DialectType::Trino | DialectType::DuckDB
9465                                ) =>
9466                            {
9467                                Action::CollectListToArrayAgg
9468                            }
9469                            "COLLECT_SET"
9470                                if matches!(
9471                                    target,
9472                                    DialectType::Presto
9473                                        | DialectType::Trino
9474                                        | DialectType::Snowflake
9475                                        | DialectType::DuckDB
9476                                ) =>
9477                            {
9478                                Action::CollectSetConvert
9479                            }
9480                            "PERCENTILE"
9481                                if matches!(
9482                                    target,
9483                                    DialectType::DuckDB | DialectType::Presto | DialectType::Trino
9484                                ) =>
9485                            {
9486                                Action::PercentileConvert
9487                            }
9488                            // CORR -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END for DuckDB
9489                            "CORR"
9490                                if matches!(target, DialectType::DuckDB)
9491                                    && matches!(source, DialectType::Snowflake) =>
9492                            {
9493                                Action::CorrIsnanWrap
9494                            }
9495                            // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
9496                            "APPROX_QUANTILES"
9497                                if matches!(source, DialectType::BigQuery)
9498                                    && matches!(target, DialectType::DuckDB) =>
9499                            {
9500                                Action::BigQueryApproxQuantiles
9501                            }
9502                            // BigQuery PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
9503                            "PERCENTILE_CONT"
9504                                if matches!(source, DialectType::BigQuery)
9505                                    && matches!(target, DialectType::DuckDB)
9506                                    && af.args.len() >= 2 =>
9507                            {
9508                                Action::BigQueryPercentileContToDuckDB
9509                            }
9510                            _ => Action::None,
9511                        }
9512                    }
9513                    Expression::JSONArrayAgg(_) => match target {
9514                        DialectType::PostgreSQL => Action::GenericFunctionNormalize,
9515                        _ => Action::None,
9516                    },
9517                    Expression::ToNumber(tn) => {
9518                        // TO_NUMBER(x) with 1 arg -> CAST(x AS DOUBLE) for most targets
9519                        if tn.format.is_none() && tn.precision.is_none() && tn.scale.is_none() {
9520                            match target {
9521                                DialectType::Oracle
9522                                | DialectType::Snowflake
9523                                | DialectType::Teradata => Action::None,
9524                                _ => Action::GenericFunctionNormalize,
9525                            }
9526                        } else {
9527                            Action::None
9528                        }
9529                    }
9530                    Expression::Nvl2(_) => {
9531                        // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END for most dialects
9532                        // Keep as NVL2 for dialects that support it natively
9533                        match target {
9534                            DialectType::Oracle
9535                            | DialectType::Snowflake
9536                            | DialectType::Teradata
9537                            | DialectType::Spark
9538                            | DialectType::Databricks
9539                            | DialectType::Redshift => Action::None,
9540                            _ => Action::Nvl2Expand,
9541                        }
9542                    }
9543                    Expression::Decode(_) | Expression::DecodeCase(_) => {
9544                        // DECODE(a, b, c[, d, e[, ...]]) -> CASE WHEN with null-safe comparisons
9545                        // Keep as DECODE for Oracle/Snowflake
9546                        match target {
9547                            DialectType::Oracle | DialectType::Snowflake => Action::None,
9548                            _ => Action::DecodeSimplify,
9549                        }
9550                    }
9551                    Expression::Coalesce(ref cf) => {
9552                        // IFNULL(a, b) -> COALESCE(a, b): clear original_name for cross-dialect
9553                        // BigQuery keeps IFNULL natively when source is also BigQuery
9554                        if cf.original_name.as_deref() == Some("IFNULL")
9555                            && !(matches!(source, DialectType::BigQuery)
9556                                && matches!(target, DialectType::BigQuery))
9557                        {
9558                            Action::IfnullToCoalesce
9559                        } else {
9560                            Action::None
9561                        }
9562                    }
9563                    Expression::IfFunc(if_func) => {
9564                        if matches!(source, DialectType::Snowflake)
9565                            && matches!(
9566                                target,
9567                                DialectType::Presto | DialectType::Trino | DialectType::SQLite
9568                            )
9569                            && matches!(if_func.false_value, Some(Expression::Div(_)))
9570                        {
9571                            Action::Div0TypedDivision
9572                        } else {
9573                            Action::None
9574                        }
9575                    }
9576                    Expression::ToJson(_) => match target {
9577                        DialectType::Presto | DialectType::Trino => Action::ToJsonConvert,
9578                        DialectType::BigQuery => Action::ToJsonConvert,
9579                        DialectType::DuckDB => Action::ToJsonConvert,
9580                        _ => Action::None,
9581                    },
9582                    Expression::ArrayAgg(ref agg) => {
9583                        if matches!(target, DialectType::MySQL | DialectType::SingleStore) {
9584                            Action::ArrayAggToGroupConcat
9585                        } else if matches!(
9586                            target,
9587                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
9588                        ) {
9589                            // Any source -> Hive/Spark: convert ARRAY_AGG to COLLECT_LIST
9590                            Action::ArrayAggToCollectList
9591                        } else if matches!(
9592                            source,
9593                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
9594                        ) && matches!(target, DialectType::DuckDB)
9595                            && agg.filter.is_some()
9596                        {
9597                            // Spark/Hive ARRAY_AGG excludes NULLs, DuckDB includes them
9598                            // Need to add NOT x IS NULL to existing filter
9599                            Action::ArrayAggNullFilter
9600                        } else if matches!(target, DialectType::DuckDB)
9601                            && agg.ignore_nulls == Some(true)
9602                            && !agg.order_by.is_empty()
9603                        {
9604                            // BigQuery ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> DuckDB ARRAY_AGG(x ORDER BY a NULLS FIRST, ...)
9605                            Action::ArrayAggIgnoreNullsDuckDB
9606                        } else if !matches!(source, DialectType::Snowflake) {
9607                            Action::None
9608                        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
9609                            let is_array_agg = agg.name.as_deref().map_or(false, |n| n.eq_ignore_ascii_case("ARRAY_AGG"))
9610                                || agg.name.is_none();
9611                            if is_array_agg {
9612                                Action::ArrayAggCollectList
9613                            } else {
9614                                Action::None
9615                            }
9616                        } else if matches!(
9617                            target,
9618                            DialectType::DuckDB | DialectType::Presto | DialectType::Trino
9619                        ) && agg.filter.is_none()
9620                        {
9621                            Action::ArrayAggFilter
9622                        } else {
9623                            Action::None
9624                        }
9625                    }
9626                    Expression::WithinGroup(wg) => {
9627                        if matches!(source, DialectType::Snowflake)
9628                            && matches!(
9629                                target,
9630                                DialectType::DuckDB | DialectType::Presto | DialectType::Trino
9631                            )
9632                            && matches!(wg.this, Expression::ArrayAgg(_))
9633                        {
9634                            Action::ArrayAggWithinGroupFilter
9635                        } else if matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("STRING_AGG"))
9636                            || matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("STRING_AGG"))
9637                            || matches!(&wg.this, Expression::StringAgg(_))
9638                        {
9639                            Action::StringAggConvert
9640                        } else if matches!(
9641                            target,
9642                            DialectType::Presto
9643                                | DialectType::Trino
9644                                | DialectType::Athena
9645                                | DialectType::Spark
9646                                | DialectType::Databricks
9647                        ) && (matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("PERCENTILE_CONT") || f.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
9648                            || matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("PERCENTILE_CONT") || af.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
9649                            || matches!(&wg.this, Expression::PercentileCont(_)))
9650                        {
9651                            Action::PercentileContConvert
9652                        } else {
9653                            Action::None
9654                        }
9655                    }
9656                    // For BigQuery: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
9657                    // because BigQuery's TIMESTAMP is really TIMESTAMPTZ, and
9658                    // DATETIME is the timezone-unaware type
9659                    Expression::Cast(ref c) => {
9660                        if c.format.is_some()
9661                            && (matches!(source, DialectType::BigQuery)
9662                                || matches!(source, DialectType::Teradata))
9663                        {
9664                            Action::BigQueryCastFormat
9665                        } else if matches!(target, DialectType::BigQuery)
9666                            && !matches!(source, DialectType::BigQuery)
9667                            && matches!(
9668                                c.to,
9669                                DataType::Timestamp {
9670                                    timezone: false,
9671                                    ..
9672                                }
9673                            )
9674                        {
9675                            Action::CastTimestampToDatetime
9676                        } else if matches!(target, DialectType::MySQL | DialectType::StarRocks)
9677                            && !matches!(source, DialectType::MySQL | DialectType::StarRocks)
9678                            && matches!(
9679                                c.to,
9680                                DataType::Timestamp {
9681                                    timezone: false,
9682                                    ..
9683                                }
9684                            )
9685                        {
9686                            // Generic/other -> MySQL/StarRocks: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
9687                            // but MySQL-native CAST(x AS TIMESTAMP) stays as TIMESTAMP(x) via transform_cast
9688                            Action::CastTimestampToDatetime
9689                        } else if matches!(
9690                            source,
9691                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
9692                        ) && matches!(
9693                            target,
9694                            DialectType::Presto
9695                                | DialectType::Trino
9696                                | DialectType::Athena
9697                                | DialectType::DuckDB
9698                                | DialectType::Snowflake
9699                                | DialectType::BigQuery
9700                                | DialectType::Databricks
9701                                | DialectType::TSQL
9702                        ) {
9703                            Action::HiveCastToTryCast
9704                        } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
9705                            && matches!(target, DialectType::MySQL | DialectType::StarRocks)
9706                        {
9707                            // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
9708                            Action::CastTimestamptzToFunc
9709                        } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
9710                            && matches!(
9711                                target,
9712                                DialectType::Hive
9713                                    | DialectType::Spark
9714                                    | DialectType::Databricks
9715                                    | DialectType::BigQuery
9716                            )
9717                        {
9718                            // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
9719                            Action::CastTimestampStripTz
9720                        } else if matches!(&c.to, DataType::Json)
9721                            && matches!(source, DialectType::DuckDB)
9722                            && matches!(target, DialectType::Snowflake)
9723                        {
9724                            Action::DuckDBCastJsonToVariant
9725                        } else if matches!(&c.to, DataType::Json)
9726                            && matches!(&c.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
9727                            && matches!(
9728                                target,
9729                                DialectType::Presto
9730                                    | DialectType::Trino
9731                                    | DialectType::Athena
9732                                    | DialectType::Snowflake
9733                            )
9734                        {
9735                            // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
9736                            // Only when the input is a string literal (JSON 'value' syntax)
9737                            Action::JsonLiteralToJsonParse
9738                        } else if matches!(&c.to, DataType::Json)
9739                            && matches!(source, DialectType::DuckDB)
9740                            && matches!(
9741                                target,
9742                                DialectType::Presto | DialectType::Trino | DialectType::Athena
9743                            )
9744                        {
9745                            // DuckDB's CAST(x AS JSON) parses the string value into a JSON value.
9746                            // Trino/Presto/Athena's CAST(x AS JSON) instead wraps the value as a
9747                            // JSON string (no parsing) — different semantics. Use JSON_PARSE(x)
9748                            // in the target to preserve DuckDB's parse semantics.
9749                            Action::JsonLiteralToJsonParse
9750                        } else if matches!(&c.to, DataType::Json | DataType::JsonB)
9751                            && matches!(target, DialectType::Spark | DialectType::Databricks)
9752                        {
9753                            // CAST(x AS JSON) -> TO_JSON(x) for Spark
9754                            Action::CastToJsonForSpark
9755                        } else if (matches!(
9756                            &c.to,
9757                            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
9758                        )) && matches!(
9759                            target,
9760                            DialectType::Spark | DialectType::Databricks
9761                        ) && (matches!(&c.this, Expression::ParseJson(_))
9762                            || matches!(
9763                                &c.this,
9764                                Expression::Function(f)
9765                                    if f.name.eq_ignore_ascii_case("JSON_EXTRACT")
9766                                        || f.name.eq_ignore_ascii_case("JSON_EXTRACT_SCALAR")
9767                                        || f.name.eq_ignore_ascii_case("GET_JSON_OBJECT")
9768                            ))
9769                        {
9770                            // CAST(JSON_PARSE(...) AS ARRAY/MAP) or CAST(JSON_EXTRACT/GET_JSON_OBJECT(...) AS ARRAY/MAP)
9771                            // -> FROM_JSON(..., type_string) for Spark
9772                            Action::CastJsonToFromJson
9773                        } else if matches!(target, DialectType::Spark | DialectType::Databricks)
9774                            && matches!(
9775                                c.to,
9776                                DataType::Timestamp {
9777                                    timezone: false,
9778                                    ..
9779                                }
9780                            )
9781                            && matches!(source, DialectType::DuckDB)
9782                        {
9783                            Action::StrftimeCastTimestamp
9784                        } else if matches!(source, DialectType::DuckDB)
9785                            && matches!(
9786                                c.to,
9787                                DataType::Decimal {
9788                                    precision: None,
9789                                    ..
9790                                }
9791                            )
9792                        {
9793                            Action::DecimalDefaultPrecision
9794                        } else if matches!(source, DialectType::MySQL | DialectType::SingleStore)
9795                            && matches!(c.to, DataType::Char { length: None })
9796                            && !matches!(target, DialectType::MySQL | DialectType::SingleStore)
9797                        {
9798                            // MySQL CAST(x AS CHAR) was originally TEXT - convert to target text type
9799                            Action::MysqlCastCharToText
9800                        } else if matches!(
9801                            source,
9802                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
9803                        ) && matches!(
9804                            target,
9805                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
9806                        ) && Self::has_varchar_char_type(&c.to)
9807                        {
9808                            // Spark parses VARCHAR(n)/CHAR(n) as TEXT, so normalize back to STRING
9809                            Action::SparkCastVarcharToString
9810                        } else {
9811                            Action::None
9812                        }
9813                    }
9814                    Expression::SafeCast(ref c) => {
9815                        if c.format.is_some()
9816                            && matches!(source, DialectType::BigQuery)
9817                            && !matches!(target, DialectType::BigQuery)
9818                        {
9819                            Action::BigQueryCastFormat
9820                        } else {
9821                            Action::None
9822                        }
9823                    }
9824                    Expression::TryCast(ref c) => {
9825                        if matches!(&c.to, DataType::Json)
9826                            && matches!(source, DialectType::DuckDB)
9827                            && matches!(
9828                                target,
9829                                DialectType::Presto | DialectType::Trino | DialectType::Athena
9830                            )
9831                        {
9832                            // DuckDB's TRY_CAST(x AS JSON) tries to parse x as JSON, returning
9833                            // NULL on parse failure. Trino/Presto/Athena's TRY_CAST(x AS JSON)
9834                            // wraps the value as a JSON string (no parse). Emit TRY(JSON_PARSE(x))
9835                            // to preserve DuckDB's parse-or-null semantics.
9836                            Action::DuckDBTryCastJsonToTryJsonParse
9837                        } else {
9838                            Action::None
9839                        }
9840                    }
9841                    Expression::JSONArray(ref ja)
9842                        if matches!(target, DialectType::Snowflake)
9843                            && ja.null_handling.is_none()
9844                            && ja.return_type.is_none()
9845                            && ja.strict.is_none() =>
9846                    {
9847                        Action::GenericFunctionNormalize
9848                    }
9849                    Expression::JsonArray(_) if matches!(target, DialectType::Snowflake) => {
9850                        Action::GenericFunctionNormalize
9851                    }
9852                    // For DuckDB: DATE_TRUNC should preserve the input type
9853                    Expression::DateTrunc(_) | Expression::TimestampTrunc(_) => {
9854                        if matches!(source, DialectType::Snowflake)
9855                            && matches!(target, DialectType::DuckDB)
9856                        {
9857                            Action::DateTruncWrapCast
9858                        } else {
9859                            Action::None
9860                        }
9861                    }
9862                    // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
9863                    Expression::SetStatement(s) => {
9864                        if matches!(target, DialectType::DuckDB)
9865                            && !matches!(source, DialectType::TSQL | DialectType::Fabric)
9866                            && s.items.iter().any(|item| item.kind.is_none())
9867                        {
9868                            Action::SetToVariable
9869                        } else {
9870                            Action::None
9871                        }
9872                    }
9873                    // Cross-dialect NULL ordering normalization.
9874                    // When nulls_first is not specified, fill in the source dialect's implied
9875                    // default so the target generator can correctly add/strip NULLS FIRST/LAST.
9876                    Expression::Ordered(o) => {
9877                        // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
9878                        if matches!(target, DialectType::MySQL) && o.nulls_first.is_some() {
9879                            Action::MysqlNullsOrdering
9880                        } else {
9881                            // Skip targets that don't support NULLS FIRST/LAST syntax unless
9882                            // the generator can preserve semantics with a CASE sort key.
9883                            let target_rewrites_nulls =
9884                                matches!(target, DialectType::TSQL | DialectType::Fabric);
9885                            let target_supports_nulls = !matches!(
9886                                target,
9887                                DialectType::MySQL
9888                                    | DialectType::TSQL
9889                                    | DialectType::Fabric
9890                                    | DialectType::StarRocks
9891                                    | DialectType::Doris
9892                            );
9893                            if o.nulls_first.is_none()
9894                                && source != target
9895                                && (target_supports_nulls || target_rewrites_nulls)
9896                            {
9897                                Action::NullsOrdering
9898                            } else {
9899                                Action::None
9900                            }
9901                        }
9902                    }
9903                    // BigQuery data types: convert INT64, BYTES, NUMERIC etc. to standard types
9904                    Expression::DataType(dt) => {
9905                        if matches!(source, DialectType::BigQuery)
9906                            && !matches!(target, DialectType::BigQuery)
9907                        {
9908                            match dt {
9909                                DataType::Custom { ref name }
9910                                    if name.eq_ignore_ascii_case("INT64")
9911                                        || name.eq_ignore_ascii_case("FLOAT64")
9912                                        || name.eq_ignore_ascii_case("BOOL")
9913                                        || name.eq_ignore_ascii_case("BYTES")
9914                                        || name.eq_ignore_ascii_case("NUMERIC")
9915                                        || name.eq_ignore_ascii_case("STRING")
9916                                        || name.eq_ignore_ascii_case("DATETIME") =>
9917                                {
9918                                    Action::BigQueryCastType
9919                                }
9920                                _ => Action::None,
9921                            }
9922                        } else if matches!(source, DialectType::TSQL) {
9923                            // For TSQL source -> any target (including TSQL itself for REAL)
9924                            match dt {
9925                                // REAL -> FLOAT even for TSQL->TSQL
9926                                DataType::Custom { ref name }
9927                                    if name.eq_ignore_ascii_case("REAL") =>
9928                                {
9929                                    Action::TSQLTypeNormalize
9930                                }
9931                                DataType::Float {
9932                                    real_spelling: true,
9933                                    ..
9934                                } => Action::TSQLTypeNormalize,
9935                                // Other TSQL type normalizations only for non-TSQL targets
9936                                DataType::Custom { ref name }
9937                                    if !matches!(target, DialectType::TSQL)
9938                                        && (name.eq_ignore_ascii_case("MONEY")
9939                                            || name.eq_ignore_ascii_case("SMALLMONEY")
9940                                            || name.eq_ignore_ascii_case("DATETIME2")
9941                                            || name.eq_ignore_ascii_case("IMAGE")
9942                                            || name.eq_ignore_ascii_case("BIT")
9943                                            || name.eq_ignore_ascii_case("ROWVERSION")
9944                                            || name.eq_ignore_ascii_case("UNIQUEIDENTIFIER")
9945                                            || name.eq_ignore_ascii_case("DATETIMEOFFSET")
9946                                            || (name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC"))
9947                                            || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("DATETIME2("))
9948                                            || (name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME("))) =>
9949                                {
9950                                    Action::TSQLTypeNormalize
9951                                }
9952                                DataType::Float {
9953                                    precision: Some(_), ..
9954                                } if !matches!(target, DialectType::TSQL) => {
9955                                    Action::TSQLTypeNormalize
9956                                }
9957                                DataType::TinyInt { .. }
9958                                    if !matches!(target, DialectType::TSQL) =>
9959                                {
9960                                    Action::TSQLTypeNormalize
9961                                }
9962                                // INTEGER -> INT for Databricks/Spark targets
9963                                DataType::Int {
9964                                    integer_spelling: true,
9965                                    ..
9966                                } if matches!(
9967                                    target,
9968                                    DialectType::Databricks | DialectType::Spark
9969                                ) =>
9970                                {
9971                                    Action::TSQLTypeNormalize
9972                                }
9973                                _ => Action::None,
9974                            }
9975                        } else if (matches!(source, DialectType::Oracle)
9976                            || matches!(source, DialectType::Generic))
9977                            && !matches!(target, DialectType::Oracle)
9978                        {
9979                            match dt {
9980                                DataType::Custom { ref name }
9981                                    if (name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2("))
9982                                        || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2("))
9983                                        || name.eq_ignore_ascii_case("VARCHAR2")
9984                                        || name.eq_ignore_ascii_case("NVARCHAR2") =>
9985                                {
9986                                    Action::OracleVarchar2ToVarchar
9987                                }
9988                                _ => Action::None,
9989                            }
9990                        } else if matches!(target, DialectType::Snowflake)
9991                            && !matches!(source, DialectType::Snowflake)
9992                        {
9993                            // When target is Snowflake but source is NOT Snowflake,
9994                            // protect FLOAT from being converted to DOUBLE by Snowflake's transform.
9995                            // Snowflake treats FLOAT=DOUBLE internally, but non-Snowflake sources
9996                            // should keep their FLOAT spelling.
9997                            match dt {
9998                                DataType::Float { .. } => Action::SnowflakeFloatProtect,
9999                                _ => Action::None,
10000                            }
10001                        } else {
10002                            Action::None
10003                        }
10004                    }
10005                    // LOWER patterns from BigQuery TO_HEX conversions:
10006                    // - LOWER(LOWER(HEX(x))) from non-BQ targets: flatten
10007                    // - LOWER(Function("TO_HEX")) for BQ->BQ: strip LOWER
10008                    Expression::Lower(uf) => {
10009                        if matches!(source, DialectType::BigQuery) {
10010                            match &uf.this {
10011                                Expression::Lower(_) => Action::BigQueryToHexLower,
10012                                Expression::Function(f)
10013                                    if f.name == "TO_HEX"
10014                                        && matches!(target, DialectType::BigQuery) =>
10015                                {
10016                                    // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
10017                                    Action::BigQueryToHexLower
10018                                }
10019                                _ => Action::None,
10020                            }
10021                        } else {
10022                            Action::None
10023                        }
10024                    }
10025                    // UPPER patterns from BigQuery TO_HEX conversions:
10026                    // - UPPER(LOWER(HEX(x))) from non-BQ targets: extract inner
10027                    // - UPPER(Function("TO_HEX")) for BQ->BQ: keep as UPPER(TO_HEX(x))
10028                    Expression::Upper(uf) => {
10029                        if matches!(source, DialectType::BigQuery) {
10030                            match &uf.this {
10031                                Expression::Lower(_) => Action::BigQueryToHexUpper,
10032                                _ => Action::None,
10033                            }
10034                        } else {
10035                            Action::None
10036                        }
10037                    }
10038                    // BigQuery LAST_DAY(date, unit) -> strip unit for non-BigQuery targets
10039                    // Snowflake supports LAST_DAY with unit, so keep it there
10040                    Expression::LastDay(ld) => {
10041                        if matches!(source, DialectType::BigQuery)
10042                            && !matches!(target, DialectType::BigQuery | DialectType::Snowflake)
10043                            && ld.unit.is_some()
10044                        {
10045                            Action::BigQueryLastDayStripUnit
10046                        } else {
10047                            Action::None
10048                        }
10049                    }
10050                    // BigQuery SafeDivide expressions (already parsed as SafeDivide)
10051                    Expression::SafeDivide(_) => {
10052                        if matches!(source, DialectType::BigQuery)
10053                            && !matches!(target, DialectType::BigQuery)
10054                        {
10055                            Action::BigQuerySafeDivide
10056                        } else {
10057                            Action::None
10058                        }
10059                    }
10060                    // BigQuery ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
10061                    // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
10062                    Expression::AnyValue(ref agg) => {
10063                        if matches!(source, DialectType::BigQuery)
10064                            && matches!(target, DialectType::DuckDB)
10065                            && agg.having_max.is_some()
10066                        {
10067                            Action::BigQueryAnyValueHaving
10068                        } else if matches!(target, DialectType::Spark | DialectType::Databricks)
10069                            && !matches!(source, DialectType::Spark | DialectType::Databricks)
10070                            && agg.ignore_nulls.is_none()
10071                        {
10072                            Action::AnyValueIgnoreNulls
10073                        } else {
10074                            Action::None
10075                        }
10076                    }
10077                    Expression::Any(ref q) => {
10078                        if matches!(source, DialectType::PostgreSQL)
10079                            && matches!(
10080                                target,
10081                                DialectType::Spark | DialectType::Databricks | DialectType::Hive
10082                            )
10083                            && q.op.is_some()
10084                            && !matches!(
10085                                q.subquery,
10086                                Expression::Select(_) | Expression::Subquery(_)
10087                            )
10088                        {
10089                            Action::AnyToExists
10090                        } else {
10091                            Action::None
10092                        }
10093                    }
10094                    // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
10095                    // Snowflake RLIKE does full-string match; DuckDB REGEXP_FULL_MATCH also does full-string match
10096                    Expression::RegexpLike(_)
10097                        if matches!(source, DialectType::Snowflake)
10098                            && matches!(target, DialectType::DuckDB) =>
10099                    {
10100                        Action::RlikeSnowflakeToDuckDB
10101                    }
10102                    // RegexpLike from non-DuckDB/non-Snowflake sources -> REGEXP_MATCHES for DuckDB target
10103                    Expression::RegexpLike(_)
10104                        if !matches!(source, DialectType::DuckDB)
10105                            && matches!(target, DialectType::DuckDB) =>
10106                    {
10107                        Action::RegexpLikeToDuckDB
10108                    }
10109                    // RegexpLike -> Exasol: anchor pattern with .*...*
10110                    Expression::RegexpLike(_)
10111                        if matches!(target, DialectType::Exasol) =>
10112                    {
10113                        Action::RegexpLikeExasolAnchor
10114                    }
10115                    // Safe-division source -> non-safe target: NULLIF wrapping and/or CAST
10116                    // Safe-division dialects: MySQL, DuckDB, SingleStore, TiDB, ClickHouse, Doris
10117                    Expression::Div(ref op)
10118                        if matches!(
10119                            source,
10120                            DialectType::MySQL
10121                                | DialectType::DuckDB
10122                                | DialectType::SingleStore
10123                                | DialectType::TiDB
10124                                | DialectType::ClickHouse
10125                                | DialectType::Doris
10126                        ) && matches!(
10127                            target,
10128                            DialectType::PostgreSQL
10129                                | DialectType::Redshift
10130                                | DialectType::Drill
10131                                | DialectType::Trino
10132                                | DialectType::Presto
10133                                | DialectType::Athena
10134                                | DialectType::TSQL
10135                                | DialectType::Teradata
10136                                | DialectType::SQLite
10137                                | DialectType::BigQuery
10138                                | DialectType::Snowflake
10139                                | DialectType::Databricks
10140                                | DialectType::Oracle
10141                                | DialectType::Materialize
10142                                | DialectType::RisingWave
10143                        ) =>
10144                    {
10145                        // Only wrap if RHS is not already NULLIF
10146                        if !matches!(&op.right, Expression::Function(f) if f.name.eq_ignore_ascii_case("NULLIF"))
10147                        {
10148                            Action::MySQLSafeDivide
10149                        } else {
10150                            Action::None
10151                        }
10152                    }
10153                    // ALTER TABLE ... RENAME TO <schema>.<table> -> strip schema for most targets
10154                    // For TSQL/Fabric, convert to sp_rename instead
10155                    Expression::AlterTable(ref at) if !at.actions.is_empty() => {
10156                        if let Some(crate::expressions::AlterTableAction::RenameTable(
10157                            ref new_tbl,
10158                        )) = at.actions.first()
10159                        {
10160                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
10161                                // TSQL: ALTER TABLE RENAME -> EXEC sp_rename
10162                                Action::AlterTableToSpRename
10163                            } else if new_tbl.schema.is_some()
10164                                && matches!(
10165                                    target,
10166                                    DialectType::BigQuery
10167                                        | DialectType::Doris
10168                                        | DialectType::StarRocks
10169                                        | DialectType::DuckDB
10170                                        | DialectType::PostgreSQL
10171                                        | DialectType::Redshift
10172                                )
10173                            {
10174                                Action::AlterTableRenameStripSchema
10175                            } else {
10176                                Action::None
10177                            }
10178                        } else {
10179                            Action::None
10180                        }
10181                    }
10182                    // EPOCH(x) expression -> target-specific epoch conversion
10183                    Expression::Epoch(_) if !matches!(target, DialectType::DuckDB) => {
10184                        Action::EpochConvert
10185                    }
10186                    // EPOCH_MS(x) expression -> target-specific epoch ms conversion
10187                    Expression::EpochMs(_) if !matches!(target, DialectType::DuckDB) => {
10188                        Action::EpochMsConvert
10189                    }
10190                    // STRING_AGG -> GROUP_CONCAT for MySQL/SQLite
10191                    Expression::StringAgg(_) => {
10192                        if matches!(
10193                            target,
10194                            DialectType::MySQL
10195                                | DialectType::SingleStore
10196                                | DialectType::Doris
10197                                | DialectType::StarRocks
10198                                | DialectType::SQLite
10199                        ) {
10200                            Action::StringAggConvert
10201                        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
10202                            Action::StringAggConvert
10203                        } else {
10204                            Action::None
10205                        }
10206                    }
10207                    Expression::CombinedParameterizedAgg(_) => Action::GenericFunctionNormalize,
10208                    // GROUP_CONCAT -> STRING_AGG for PostgreSQL/Presto/etc.
10209                    // Also handles GROUP_CONCAT normalization for MySQL/SQLite targets
10210                    Expression::GroupConcat(_) => Action::GroupConcatConvert,
10211                    // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific array length
10212                    // DuckDB CARDINALITY -> keep as CARDINALITY for DuckDB target (used for maps)
10213                    Expression::Cardinality(_)
10214                        if matches!(source, DialectType::DuckDB)
10215                            && matches!(target, DialectType::DuckDB) =>
10216                    {
10217                        Action::None
10218                    }
10219                    Expression::Cardinality(_) | Expression::ArrayLength(_) => {
10220                        Action::ArrayLengthConvert
10221                    }
10222                    Expression::ArraySize(_) => {
10223                        if matches!(target, DialectType::Drill) {
10224                            Action::ArraySizeDrill
10225                        } else {
10226                            Action::ArrayLengthConvert
10227                        }
10228                    }
10229                    // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
10230                    Expression::ArrayRemove(_) => match target {
10231                        DialectType::DuckDB | DialectType::ClickHouse | DialectType::BigQuery => {
10232                            Action::ArrayRemoveConvert
10233                        }
10234                        _ => Action::None,
10235                    },
10236                    // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse
10237                    Expression::ArrayReverse(_) => match target {
10238                        DialectType::ClickHouse => Action::ArrayReverseConvert,
10239                        _ => Action::None,
10240                    },
10241                    // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS for Spark/Databricks/Snowflake
10242                    Expression::JsonKeys(_) => match target {
10243                        DialectType::Spark | DialectType::Databricks | DialectType::Snowflake => {
10244                            Action::JsonKeysConvert
10245                        }
10246                        _ => Action::None,
10247                    },
10248                    // PARSE_JSON(x) -> strip for SQLite/Doris/MySQL/StarRocks
10249                    Expression::ParseJson(_) => match target {
10250                        DialectType::SQLite
10251                        | DialectType::Doris
10252                        | DialectType::MySQL
10253                        | DialectType::StarRocks => Action::ParseJsonStrip,
10254                        _ => Action::None,
10255                    },
10256                    // WeekOfYear -> WEEKISO for Snowflake (cross-dialect only)
10257                    Expression::WeekOfYear(_)
10258                        if matches!(target, DialectType::Snowflake)
10259                            && !matches!(source, DialectType::Snowflake) =>
10260                    {
10261                        Action::WeekOfYearToWeekIso
10262                    }
10263                    // NVL: clear original_name so generator uses dialect-specific function names
10264                    Expression::Nvl(f) if f.original_name.is_some() => Action::NvlClearOriginal,
10265                    // XOR: expand for dialects that don't support the XOR keyword
10266                    Expression::Xor(_) => {
10267                        let target_supports_xor = matches!(
10268                            target,
10269                            DialectType::MySQL
10270                                | DialectType::SingleStore
10271                                | DialectType::Doris
10272                                | DialectType::StarRocks
10273                        );
10274                        if !target_supports_xor {
10275                            Action::XorExpand
10276                        } else {
10277                            Action::None
10278                        }
10279                    }
10280                    // TSQL #table -> temp table normalization (CREATE TABLE)
10281                    Expression::CreateTable(ct)
10282                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
10283                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
10284                            && ct.name.name.name.starts_with('#') =>
10285                    {
10286                        Action::TempTableHash
10287                    }
10288                    // TSQL #table -> strip # from table references in SELECT/etc.
10289                    Expression::Table(tr)
10290                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
10291                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
10292                            && tr.name.name.starts_with('#') =>
10293                    {
10294                        Action::TempTableHash
10295                    }
10296                    // TSQL #table -> strip # from DROP TABLE names
10297                    Expression::DropTable(ref dt)
10298                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
10299                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
10300                            && dt.names.iter().any(|n| n.name.name.starts_with('#')) =>
10301                    {
10302                        Action::TempTableHash
10303                    }
10304                    // JSON_EXTRACT -> ISNULL(JSON_QUERY, JSON_VALUE) for TSQL
10305                    Expression::JsonExtract(_)
10306                        if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
10307                    {
10308                        Action::JsonExtractToTsql
10309                    }
10310                    // JSON_EXTRACT_SCALAR -> ISNULL(JSON_QUERY, JSON_VALUE) for TSQL
10311                    Expression::JsonExtractScalar(_)
10312                        if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
10313                    {
10314                        Action::JsonExtractToTsql
10315                    }
10316                    // JSON_EXTRACT -> JSONExtractString for ClickHouse
10317                    Expression::JsonExtract(_) if matches!(target, DialectType::ClickHouse) => {
10318                        Action::JsonExtractToClickHouse
10319                    }
10320                    // JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
10321                    Expression::JsonExtractScalar(_)
10322                        if matches!(target, DialectType::ClickHouse) =>
10323                    {
10324                        Action::JsonExtractToClickHouse
10325                    }
10326                    // JSON_EXTRACT -> arrow syntax for SQLite/DuckDB
10327                    Expression::JsonExtract(ref f)
10328                        if !f.arrow_syntax
10329                            && matches!(target, DialectType::SQLite | DialectType::DuckDB) =>
10330                    {
10331                        Action::JsonExtractToArrow
10332                    }
10333                    // JSON_EXTRACT with JSONPath -> JSON_EXTRACT_PATH for PostgreSQL (non-PG sources only)
10334                    Expression::JsonExtract(ref f)
10335                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift)
10336                            && !matches!(
10337                                source,
10338                                DialectType::PostgreSQL
10339                                    | DialectType::Redshift
10340                                    | DialectType::Materialize
10341                            )
10342                            && matches!(&f.path, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$'))) =>
10343                    {
10344                        Action::JsonExtractToGetJsonObject
10345                    }
10346                    // JSON_EXTRACT -> GET_JSON_OBJECT for Hive/Spark
10347                    Expression::JsonExtract(_)
10348                        if matches!(
10349                            target,
10350                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
10351                        ) =>
10352                    {
10353                        Action::JsonExtractToGetJsonObject
10354                    }
10355                    // JSON_EXTRACT_SCALAR -> target-specific for PostgreSQL, Snowflake, SQLite
10356                    // Skip if already in arrow/hash_arrow syntax (same-dialect identity case)
10357                    Expression::JsonExtractScalar(ref f)
10358                        if !f.arrow_syntax
10359                            && !f.hash_arrow_syntax
10360                            && matches!(
10361                                target,
10362                                DialectType::PostgreSQL
10363                                    | DialectType::Redshift
10364                                    | DialectType::Snowflake
10365                                    | DialectType::SQLite
10366                                    | DialectType::DuckDB
10367                            ) =>
10368                    {
10369                        Action::JsonExtractScalarConvert
10370                    }
10371                    // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
10372                    Expression::JsonExtractScalar(_)
10373                        if matches!(
10374                            target,
10375                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
10376                        ) =>
10377                    {
10378                        Action::JsonExtractScalarToGetJsonObject
10379                    }
10380                    // JSON_EXTRACT path normalization for BigQuery, MySQL (bracket/wildcard handling)
10381                    Expression::JsonExtract(ref f)
10382                        if !f.arrow_syntax
10383                            && matches!(target, DialectType::BigQuery | DialectType::MySQL) =>
10384                    {
10385                        Action::JsonPathNormalize
10386                    }
10387                    // JsonQuery (parsed JSON_QUERY) -> target-specific
10388                    Expression::JsonQuery(_) => Action::JsonQueryValueConvert,
10389                    // JsonValue (parsed JSON_VALUE) -> target-specific
10390                    Expression::JsonValue(_) => Action::JsonQueryValueConvert,
10391                    // AT TIME ZONE -> AT_TIMEZONE for Presto, FROM_UTC_TIMESTAMP for Spark,
10392                    // TIMESTAMP(DATETIME(...)) for BigQuery, CONVERT_TIMEZONE for Snowflake
10393                    Expression::AtTimeZone(_)
10394                        if matches!(
10395                            target,
10396                            DialectType::Presto
10397                                | DialectType::Trino
10398                                | DialectType::Athena
10399                                | DialectType::Spark
10400                                | DialectType::Databricks
10401                                | DialectType::BigQuery
10402                                | DialectType::Snowflake
10403                        ) =>
10404                    {
10405                        Action::AtTimeZoneConvert
10406                    }
10407                    // DAY_OF_WEEK -> dialect-specific
10408                    Expression::DayOfWeek(_)
10409                        if matches!(
10410                            target,
10411                            DialectType::DuckDB | DialectType::Spark | DialectType::Databricks
10412                        ) =>
10413                    {
10414                        Action::DayOfWeekConvert
10415                    }
10416                    // CURRENT_USER -> CURRENT_USER() for Snowflake
10417                    Expression::CurrentUser(_) if matches!(target, DialectType::Snowflake) => {
10418                        Action::CurrentUserParens
10419                    }
10420                    // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
10421                    Expression::ElementAt(_)
10422                        if matches!(target, DialectType::PostgreSQL | DialectType::BigQuery) =>
10423                    {
10424                        Action::ElementAtConvert
10425                    }
10426                    // ARRAY[...] (ArrayFunc bracket_notation=false) -> convert for target dialect
10427                    Expression::ArrayFunc(ref arr)
10428                        if !arr.bracket_notation
10429                            && matches!(
10430                                target,
10431                                DialectType::Spark
10432                                    | DialectType::Databricks
10433                                    | DialectType::Hive
10434                                    | DialectType::BigQuery
10435                                    | DialectType::DuckDB
10436                                    | DialectType::Snowflake
10437                                    | DialectType::Presto
10438                                    | DialectType::Trino
10439                                    | DialectType::Athena
10440                                    | DialectType::ClickHouse
10441                                    | DialectType::StarRocks
10442                            ) =>
10443                    {
10444                        Action::ArraySyntaxConvert
10445                    }
10446                    // VARIANCE expression -> varSamp for ClickHouse
10447                    Expression::Variance(_) if matches!(target, DialectType::ClickHouse) => {
10448                        Action::VarianceToClickHouse
10449                    }
10450                    // STDDEV expression -> stddevSamp for ClickHouse
10451                    Expression::Stddev(_) if matches!(target, DialectType::ClickHouse) => {
10452                        Action::StddevToClickHouse
10453                    }
10454                    // ApproxQuantile -> APPROX_PERCENTILE for Snowflake
10455                    Expression::ApproxQuantile(_) if matches!(target, DialectType::Snowflake) => {
10456                        Action::ApproxQuantileConvert
10457                    }
10458                    // MonthsBetween -> target-specific
10459                    Expression::MonthsBetween(_)
10460                        if !matches!(
10461                            target,
10462                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
10463                        ) =>
10464                    {
10465                        Action::MonthsBetweenConvert
10466                    }
10467                    // AddMonths -> target-specific DATEADD/DATE_ADD
10468                    Expression::AddMonths(_) => Action::AddMonthsConvert,
10469                    // MapFromArrays -> target-specific (MAP, OBJECT_CONSTRUCT, MAP_FROM_ARRAYS)
10470                    Expression::MapFromArrays(_)
10471                        if !matches!(target, DialectType::Spark | DialectType::Databricks) =>
10472                    {
10473                        Action::MapFromArraysConvert
10474                    }
10475                    // CURRENT_USER -> CURRENT_USER() for Spark
10476                    Expression::CurrentUser(_)
10477                        if matches!(target, DialectType::Spark | DialectType::Databricks) =>
10478                    {
10479                        Action::CurrentUserSparkParens
10480                    }
10481                    // MONTH/YEAR/DAY('string') from Spark -> cast string to DATE for DuckDB/Presto
10482                    Expression::Month(ref f) | Expression::Year(ref f) | Expression::Day(ref f)
10483                        if matches!(
10484                            source,
10485                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
10486                        ) && matches!(&f.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
10487                            && matches!(
10488                                target,
10489                                DialectType::DuckDB
10490                                    | DialectType::Presto
10491                                    | DialectType::Trino
10492                                    | DialectType::Athena
10493                                    | DialectType::PostgreSQL
10494                                    | DialectType::Redshift
10495                            ) =>
10496                    {
10497                        Action::SparkDateFuncCast
10498                    }
10499                    // $parameter -> @parameter for BigQuery
10500                    Expression::Parameter(ref p)
10501                        if matches!(target, DialectType::BigQuery)
10502                            && matches!(source, DialectType::DuckDB)
10503                            && (p.style == crate::expressions::ParameterStyle::Dollar
10504                                || p.style == crate::expressions::ParameterStyle::DoubleDollar) =>
10505                    {
10506                        Action::DollarParamConvert
10507                    }
10508                    // EscapeString literal: normalize literal newlines to \n
10509                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::EscapeString(ref s) if s.contains('\n') || s.contains('\r') || s.contains('\t'))
10510                        =>
10511                    {
10512                        Action::EscapeStringNormalize
10513                    }
10514                    // straight_join: keep lowercase for DuckDB, quote for MySQL
10515                    Expression::Column(ref col)
10516                        if col.name.name == "STRAIGHT_JOIN"
10517                            && col.table.is_none()
10518                            && matches!(source, DialectType::DuckDB)
10519                            && matches!(target, DialectType::DuckDB | DialectType::MySQL) =>
10520                    {
10521                        Action::StraightJoinCase
10522                    }
10523                    // DATE and TIMESTAMP literal type conversions are now handled in the generator directly
10524                    // Snowflake INTERVAL format: INTERVAL '2' HOUR -> INTERVAL '2 HOUR'
10525                    Expression::Interval(ref iv)
10526                        if matches!(
10527                            target,
10528                            DialectType::Snowflake
10529                                | DialectType::PostgreSQL
10530                                | DialectType::Redshift
10531                        ) && iv.unit.is_some()
10532                            && iv.this.as_ref().map_or(false, |t| matches!(t, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))) =>
10533                    {
10534                        Action::SnowflakeIntervalFormat
10535                    }
10536                    // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB target
10537                    Expression::TableSample(ref ts) if matches!(target, DialectType::DuckDB) => {
10538                        if let Some(ref sample) = ts.sample {
10539                            if !sample.explicit_method {
10540                                Action::TablesampleReservoir
10541                            } else {
10542                                Action::None
10543                            }
10544                        } else {
10545                            Action::None
10546                        }
10547                    }
10548                    // TABLESAMPLE from non-Snowflake source to Snowflake: strip method and PERCENT
10549                    // Handles both Expression::TableSample wrapper and Expression::Table with table_sample
10550                    Expression::TableSample(ref ts)
10551                        if matches!(target, DialectType::Snowflake)
10552                            && !matches!(source, DialectType::Snowflake)
10553                            && ts.sample.is_some() =>
10554                    {
10555                        if let Some(ref sample) = ts.sample {
10556                            if !sample.explicit_method {
10557                                Action::TablesampleSnowflakeStrip
10558                            } else {
10559                                Action::None
10560                            }
10561                        } else {
10562                            Action::None
10563                        }
10564                    }
10565                    Expression::Table(ref t)
10566                        if matches!(target, DialectType::Snowflake)
10567                            && !matches!(source, DialectType::Snowflake)
10568                            && t.table_sample.is_some() =>
10569                    {
10570                        if let Some(ref sample) = t.table_sample {
10571                            if !sample.explicit_method {
10572                                Action::TablesampleSnowflakeStrip
10573                            } else {
10574                                Action::None
10575                            }
10576                        } else {
10577                            Action::None
10578                        }
10579                    }
10580                    // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
10581                    Expression::AlterTable(ref at)
10582                        if matches!(target, DialectType::TSQL | DialectType::Fabric)
10583                            && !at.actions.is_empty()
10584                            && matches!(
10585                                at.actions.first(),
10586                                Some(crate::expressions::AlterTableAction::RenameTable(_))
10587                            ) =>
10588                    {
10589                        Action::AlterTableToSpRename
10590                    }
10591                    // Subscript index: 1-based to 0-based for BigQuery/Hive/Spark
10592                    Expression::Subscript(ref sub)
10593                        if matches!(
10594                            target,
10595                            DialectType::BigQuery
10596                                | DialectType::Hive
10597                                | DialectType::Spark
10598                                | DialectType::Databricks
10599                        ) && matches!(
10600                            source,
10601                            DialectType::DuckDB
10602                                | DialectType::PostgreSQL
10603                                | DialectType::Presto
10604                                | DialectType::Trino
10605                                | DialectType::Redshift
10606                                | DialectType::ClickHouse
10607                        ) && matches!(&sub.index, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(ref n) if n.parse::<i64>().unwrap_or(0) > 0)) =>
10608                    {
10609                        Action::ArrayIndexConvert
10610                    }
10611                    // ANY_VALUE IGNORE NULLS detection moved to the AnyValue arm above
10612                    // MysqlNullsOrdering for Ordered is now handled in the Ordered arm above
10613                    // RESPECT NULLS handling for SQLite (strip it, add NULLS LAST to ORDER BY)
10614                    // and for MySQL (rewrite ORDER BY with CASE WHEN for null ordering)
10615                    Expression::WindowFunction(ref wf) => {
10616                        // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
10617                        // EXCEPT for ROW_NUMBER which keeps NULLS LAST
10618                        let is_row_number = matches!(wf.this, Expression::RowNumber(_));
10619                        if matches!(target, DialectType::BigQuery)
10620                            && !is_row_number
10621                            && !wf.over.order_by.is_empty()
10622                            && wf.over.order_by.iter().any(|o| o.nulls_first.is_some())
10623                        {
10624                            Action::BigQueryNullsOrdering
10625                        // DuckDB -> MySQL: Add CASE WHEN for NULLS LAST simulation in window ORDER BY
10626                        // But NOT when frame is RANGE/GROUPS, since adding CASE WHEN would break value-based frames
10627                        } else {
10628                            let source_nulls_last = matches!(source, DialectType::DuckDB);
10629                            let has_range_frame = wf.over.frame.as_ref().map_or(false, |f| {
10630                                matches!(
10631                                    f.kind,
10632                                    crate::expressions::WindowFrameKind::Range
10633                                        | crate::expressions::WindowFrameKind::Groups
10634                                )
10635                            });
10636                            if source_nulls_last
10637                                && matches!(target, DialectType::MySQL)
10638                                && !wf.over.order_by.is_empty()
10639                                && wf.over.order_by.iter().any(|o| !o.desc)
10640                                && !has_range_frame
10641                            {
10642                                Action::MysqlNullsLastRewrite
10643                            } else {
10644                                // Check for Snowflake window frame handling for FIRST_VALUE/LAST_VALUE/NTH_VALUE
10645                                let is_ranking_window_func = matches!(
10646                                    &wf.this,
10647                                    Expression::FirstValue(_)
10648                                        | Expression::LastValue(_)
10649                                        | Expression::NthValue(_)
10650                                );
10651                                let has_full_unbounded_frame = wf.over.frame.as_ref().map_or(false, |f| {
10652                                    matches!(f.kind, crate::expressions::WindowFrameKind::Rows)
10653                                        && matches!(f.start, crate::expressions::WindowFrameBound::UnboundedPreceding)
10654                                        && matches!(f.end, Some(crate::expressions::WindowFrameBound::UnboundedFollowing))
10655                                        && f.exclude.is_none()
10656                                });
10657                                if is_ranking_window_func && matches!(source, DialectType::Snowflake) {
10658                                    if has_full_unbounded_frame && matches!(target, DialectType::Snowflake) {
10659                                        // Strip the default frame for Snowflake target
10660                                        Action::SnowflakeWindowFrameStrip
10661                                    } else if !has_full_unbounded_frame && wf.over.frame.is_none() && !matches!(target, DialectType::Snowflake) {
10662                                        // Add default frame for non-Snowflake target
10663                                        Action::SnowflakeWindowFrameAdd
10664                                    } else {
10665                                        match &wf.this {
10666                                            Expression::FirstValue(ref vf)
10667                                            | Expression::LastValue(ref vf)
10668                                                if vf.ignore_nulls == Some(false) =>
10669                                            {
10670                                                match target {
10671                                                    DialectType::SQLite => Action::RespectNullsConvert,
10672                                                    _ => Action::None,
10673                                                }
10674                                            }
10675                                            _ => Action::None,
10676                                        }
10677                                    }
10678                                } else {
10679                                    match &wf.this {
10680                                        Expression::FirstValue(ref vf)
10681                                        | Expression::LastValue(ref vf)
10682                                            if vf.ignore_nulls == Some(false) =>
10683                                        {
10684                                            // RESPECT NULLS
10685                                            match target {
10686                                                DialectType::SQLite | DialectType::PostgreSQL => {
10687                                                    Action::RespectNullsConvert
10688                                                }
10689                                                _ => Action::None,
10690                                            }
10691                                        }
10692                                        _ => Action::None,
10693                                    }
10694                                }
10695                            }
10696                        }
10697                    }
10698                    // CREATE TABLE a LIKE b -> dialect-specific transformations
10699                    Expression::CreateTable(ref ct)
10700                        if ct.columns.is_empty()
10701                            && ct.constraints.iter().any(|c| {
10702                                matches!(c, crate::expressions::TableConstraint::Like { .. })
10703                            })
10704                            && matches!(
10705                                target,
10706                                DialectType::DuckDB | DialectType::SQLite | DialectType::Drill
10707                            ) =>
10708                    {
10709                        Action::CreateTableLikeToCtas
10710                    }
10711                    Expression::CreateTable(ref ct)
10712                        if ct.columns.is_empty()
10713                            && ct.constraints.iter().any(|c| {
10714                                matches!(c, crate::expressions::TableConstraint::Like { .. })
10715                            })
10716                            && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
10717                    {
10718                        Action::CreateTableLikeToSelectInto
10719                    }
10720                    Expression::CreateTable(ref ct)
10721                        if ct.columns.is_empty()
10722                            && ct.constraints.iter().any(|c| {
10723                                matches!(c, crate::expressions::TableConstraint::Like { .. })
10724                            })
10725                            && matches!(target, DialectType::ClickHouse) =>
10726                    {
10727                        Action::CreateTableLikeToAs
10728                    }
10729                    // CREATE TABLE: strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
10730                    Expression::CreateTable(ref ct)
10731                        if matches!(target, DialectType::DuckDB)
10732                            && matches!(
10733                                source,
10734                                DialectType::DuckDB
10735                                    | DialectType::Spark
10736                                    | DialectType::Databricks
10737                                    | DialectType::Hive
10738                            ) =>
10739                    {
10740                        let has_comment = ct.columns.iter().any(|c| {
10741                            c.comment.is_some()
10742                                || c.constraints.iter().any(|con| {
10743                                    matches!(con, crate::expressions::ColumnConstraint::Comment(_))
10744                                })
10745                        });
10746                        let has_props = !ct.properties.is_empty();
10747                        if has_comment || has_props {
10748                            Action::CreateTableStripComment
10749                        } else {
10750                            Action::None
10751                        }
10752                    }
10753                    // Array conversion: Expression::Array -> Expression::ArrayFunc for PostgreSQL
10754                    Expression::Array(_)
10755                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) =>
10756                    {
10757                        Action::ArrayConcatBracketConvert
10758                    }
10759                    // ArrayFunc (bracket notation) -> Function("ARRAY") for Redshift (from BigQuery source)
10760                    Expression::ArrayFunc(ref arr)
10761                        if arr.bracket_notation
10762                            && matches!(source, DialectType::BigQuery)
10763                            && matches!(target, DialectType::Redshift) =>
10764                    {
10765                        Action::ArrayConcatBracketConvert
10766                    }
10767                    // BIT_OR/BIT_AND/BIT_XOR: float/decimal arg cast for DuckDB, or rename for Snowflake
10768                    Expression::BitwiseOrAgg(ref f)
10769                    | Expression::BitwiseAndAgg(ref f)
10770                    | Expression::BitwiseXorAgg(ref f) => {
10771                        if matches!(target, DialectType::DuckDB) {
10772                            // Check if the arg is CAST(val AS FLOAT/DOUBLE/DECIMAL/REAL)
10773                            if let Expression::Cast(ref c) = f.this {
10774                                match &c.to {
10775                                    DataType::Float { .. }
10776                                    | DataType::Double { .. }
10777                                    | DataType::Decimal { .. } => Action::BitAggFloatCast,
10778                                    DataType::Custom { ref name }
10779                                        if name.eq_ignore_ascii_case("REAL") =>
10780                                    {
10781                                        Action::BitAggFloatCast
10782                                    }
10783                                    _ => Action::None,
10784                                }
10785                            } else {
10786                                Action::None
10787                            }
10788                        } else if matches!(target, DialectType::Snowflake) {
10789                            Action::BitAggSnowflakeRename
10790                        } else {
10791                            Action::None
10792                        }
10793                    }
10794                    // FILTER -> IFF for Snowflake (aggregate functions with FILTER clause)
10795                    Expression::Filter(ref _f) if matches!(target, DialectType::Snowflake) => {
10796                        Action::FilterToIff
10797                    }
10798                    // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
10799                    Expression::Avg(ref f)
10800                    | Expression::Sum(ref f)
10801                    | Expression::Min(ref f)
10802                    | Expression::Max(ref f)
10803                    | Expression::CountIf(ref f)
10804                    | Expression::Stddev(ref f)
10805                    | Expression::StddevPop(ref f)
10806                    | Expression::StddevSamp(ref f)
10807                    | Expression::Variance(ref f)
10808                    | Expression::VarPop(ref f)
10809                    | Expression::VarSamp(ref f)
10810                    | Expression::Median(ref f)
10811                    | Expression::Mode(ref f)
10812                    | Expression::First(ref f)
10813                    | Expression::Last(ref f)
10814                    | Expression::ApproxDistinct(ref f)
10815                        if f.filter.is_some() && matches!(target, DialectType::Snowflake) =>
10816                    {
10817                        Action::AggFilterToIff
10818                    }
10819                    Expression::Count(ref c)
10820                        if c.filter.is_some() && matches!(target, DialectType::Snowflake) =>
10821                    {
10822                        Action::AggFilterToIff
10823                    }
10824                    // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END) for dialects that don't support multi-arg DISTINCT
10825                    Expression::Count(ref c)
10826                        if c.distinct
10827                            && matches!(&c.this, Some(Expression::Tuple(_)))
10828                            && matches!(
10829                                target,
10830                                DialectType::Presto
10831                                    | DialectType::Trino
10832                                    | DialectType::DuckDB
10833                                    | DialectType::PostgreSQL
10834                            ) =>
10835                    {
10836                        Action::CountDistinctMultiArg
10837                    }
10838                    // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
10839                    Expression::JsonExtract(_) if matches!(target, DialectType::Snowflake) => {
10840                        Action::JsonToGetPath
10841                    }
10842                    // DuckDB struct/dict -> BigQuery STRUCT / Presto ROW
10843                    Expression::Struct(_)
10844                        if matches!(
10845                            target,
10846                            DialectType::BigQuery | DialectType::Presto | DialectType::Trino
10847                        ) && matches!(source, DialectType::DuckDB) =>
10848                    {
10849                        Action::StructToRow
10850                    }
10851                    // DuckDB curly-brace dict {'key': value} -> BigQuery STRUCT / Presto ROW
10852                    Expression::MapFunc(ref m)
10853                        if m.curly_brace_syntax
10854                            && matches!(
10855                                target,
10856                                DialectType::BigQuery | DialectType::Presto | DialectType::Trino
10857                            )
10858                            && matches!(source, DialectType::DuckDB) =>
10859                    {
10860                        Action::StructToRow
10861                    }
10862                    // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
10863                    Expression::ApproxCountDistinct(_)
10864                        if matches!(
10865                            target,
10866                            DialectType::Presto | DialectType::Trino | DialectType::Athena
10867                        ) =>
10868                    {
10869                        Action::ApproxCountDistinctToApproxDistinct
10870                    }
10871                    // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val) for Presto, ARRAY_CONTAINS(CAST(val AS VARIANT), arr) for Snowflake
10872                    Expression::ArrayContains(_)
10873                        if matches!(
10874                            target,
10875                            DialectType::Presto | DialectType::Trino | DialectType::Snowflake
10876                        ) && !(matches!(source, DialectType::Snowflake) && matches!(target, DialectType::Snowflake)) =>
10877                    {
10878                        Action::ArrayContainsConvert
10879                    }
10880                    // ARRAY_CONTAINS -> DuckDB NULL-aware CASE (from Snowflake source with check_null semantics)
10881                    Expression::ArrayContains(_)
10882                        if matches!(target, DialectType::DuckDB)
10883                            && matches!(source, DialectType::Snowflake) =>
10884                    {
10885                        Action::ArrayContainsDuckDBConvert
10886                    }
10887                    // ARRAY_EXCEPT -> target-specific conversion
10888                    Expression::ArrayExcept(_)
10889                        if matches!(
10890                            target,
10891                            DialectType::DuckDB | DialectType::Snowflake | DialectType::Presto | DialectType::Trino | DialectType::Athena
10892                        ) =>
10893                    {
10894                        Action::ArrayExceptConvert
10895                    }
10896                    // ARRAY_POSITION -> swap args for Snowflake target (only when source is not Snowflake)
10897                    Expression::ArrayPosition(_)
10898                        if matches!(target, DialectType::Snowflake)
10899                            && !matches!(source, DialectType::Snowflake) =>
10900                    {
10901                        Action::ArrayPositionSnowflakeSwap
10902                    }
10903                    // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB from Snowflake source
10904                    Expression::ArrayPosition(_)
10905                        if matches!(target, DialectType::DuckDB)
10906                            && matches!(source, DialectType::Snowflake) =>
10907                    {
10908                        Action::SnowflakeArrayPositionToDuckDB
10909                    }
10910                    // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
10911                    Expression::ArrayDistinct(_)
10912                        if matches!(target, DialectType::ClickHouse) =>
10913                    {
10914                        Action::ArrayDistinctClickHouse
10915                    }
10916                    // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
10917                    Expression::ArrayDistinct(_)
10918                        if matches!(target, DialectType::DuckDB)
10919                            && matches!(source, DialectType::Snowflake) =>
10920                    {
10921                        Action::ArrayDistinctConvert
10922                    }
10923                    // StrPosition with position -> complex expansion for Presto/DuckDB
10924                    // STRPOS doesn't support a position arg in these dialects
10925                    Expression::StrPosition(ref sp)
10926                        if sp.position.is_some()
10927                            && matches!(
10928                                target,
10929                                DialectType::Presto
10930                                    | DialectType::Trino
10931                                    | DialectType::Athena
10932                                    | DialectType::DuckDB
10933                            ) =>
10934                    {
10935                        Action::StrPositionExpand
10936                    }
10937                    // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
10938                    Expression::First(ref f)
10939                        if f.ignore_nulls == Some(true)
10940                            && matches!(target, DialectType::DuckDB) =>
10941                    {
10942                        Action::FirstToAnyValue
10943                    }
10944                    // BEGIN -> START TRANSACTION for Presto/Trino
10945                    Expression::Command(ref cmd)
10946                        if cmd.this.eq_ignore_ascii_case("BEGIN")
10947                            && matches!(
10948                                target,
10949                                DialectType::Presto | DialectType::Trino | DialectType::Athena
10950                            ) =>
10951                    {
10952                        // Handled inline below
10953                        Action::None // We'll handle it directly
10954                    }
10955                    // Note: PostgreSQL ^ is now parsed as Power directly (not BitwiseXor).
10956                    // PostgreSQL # is parsed as BitwiseXor (which is correct).
10957                    // a || b (Concat operator) -> CONCAT function for Presto/Trino
10958                    Expression::Concat(ref _op)
10959                        if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10960                            && matches!(target, DialectType::Presto | DialectType::Trino) =>
10961                    {
10962                        Action::PipeConcatToConcat
10963                    }
10964                    _ => Action::None,
10965                }
10966            };
10967
10968            match action {
10969                Action::None => {
10970                    // Handle inline transforms that don't need a dedicated action
10971                    if matches!(target, DialectType::TSQL | DialectType::Fabric) {
10972                        if let Some(rewritten) = Self::rewrite_tsql_interval_arithmetic(&e) {
10973                            return Ok(rewritten);
10974                        }
10975                    }
10976
10977                    // BETWEEN SYMMETRIC/ASYMMETRIC expansion for non-PostgreSQL/Dremio targets
10978                    if let Expression::Between(ref b) = e {
10979                        if let Some(sym) = b.symmetric {
10980                            let keeps_symmetric =
10981                                matches!(target, DialectType::PostgreSQL | DialectType::Dremio);
10982                            if !keeps_symmetric {
10983                                if sym {
10984                                    // SYMMETRIC: expand to (x BETWEEN a AND b OR x BETWEEN b AND a)
10985                                    let b = if let Expression::Between(b) = e {
10986                                        *b
10987                                    } else {
10988                                        unreachable!()
10989                                    };
10990                                    let between1 = Expression::Between(Box::new(
10991                                        crate::expressions::Between {
10992                                            this: b.this.clone(),
10993                                            low: b.low.clone(),
10994                                            high: b.high.clone(),
10995                                            not: b.not,
10996                                            symmetric: None,
10997                                        },
10998                                    ));
10999                                    let between2 = Expression::Between(Box::new(
11000                                        crate::expressions::Between {
11001                                            this: b.this,
11002                                            low: b.high,
11003                                            high: b.low,
11004                                            not: b.not,
11005                                            symmetric: None,
11006                                        },
11007                                    ));
11008                                    return Ok(Expression::Paren(Box::new(
11009                                        crate::expressions::Paren {
11010                                            this: Expression::Or(Box::new(
11011                                                crate::expressions::BinaryOp::new(
11012                                                    between1, between2,
11013                                                ),
11014                                            )),
11015                                            trailing_comments: vec![],
11016                                        },
11017                                    )));
11018                                } else {
11019                                    // ASYMMETRIC: strip qualifier, keep as regular BETWEEN
11020                                    let b = if let Expression::Between(b) = e {
11021                                        *b
11022                                    } else {
11023                                        unreachable!()
11024                                    };
11025                                    return Ok(Expression::Between(Box::new(
11026                                        crate::expressions::Between {
11027                                            this: b.this,
11028                                            low: b.low,
11029                                            high: b.high,
11030                                            not: b.not,
11031                                            symmetric: None,
11032                                        },
11033                                    )));
11034                                }
11035                            }
11036                        }
11037                    }
11038
11039                    // ILIKE -> LOWER(x) LIKE LOWER(y) for StarRocks/Doris
11040                    if let Expression::ILike(ref _like) = e {
11041                        if matches!(target, DialectType::StarRocks | DialectType::Doris) {
11042                            let like = if let Expression::ILike(l) = e {
11043                                *l
11044                            } else {
11045                                unreachable!()
11046                            };
11047                            let lower_left = Expression::Function(Box::new(Function::new(
11048                                "LOWER".to_string(),
11049                                vec![like.left],
11050                            )));
11051                            let lower_right = Expression::Function(Box::new(Function::new(
11052                                "LOWER".to_string(),
11053                                vec![like.right],
11054                            )));
11055                            return Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
11056                                left: lower_left,
11057                                right: lower_right,
11058                                escape: like.escape,
11059                                quantifier: like.quantifier,
11060                                inferred_type: None,
11061                            })));
11062                        }
11063                    }
11064
11065                    // Oracle DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL, RAND() for others
11066                    if let Expression::MethodCall(ref mc) = e {
11067                        if matches!(source, DialectType::Oracle)
11068                            && mc.method.name.eq_ignore_ascii_case("VALUE")
11069                            && mc.args.is_empty()
11070                        {
11071                            let is_dbms_random = match &mc.this {
11072                                Expression::Identifier(id) => {
11073                                    id.name.eq_ignore_ascii_case("DBMS_RANDOM")
11074                                }
11075                                Expression::Column(col) => {
11076                                    col.table.is_none()
11077                                        && col.name.name.eq_ignore_ascii_case("DBMS_RANDOM")
11078                                }
11079                                _ => false,
11080                            };
11081                            if is_dbms_random {
11082                                let func_name = match target {
11083                                    DialectType::PostgreSQL
11084                                    | DialectType::Redshift
11085                                    | DialectType::DuckDB
11086                                    | DialectType::SQLite => "RANDOM",
11087                                    DialectType::Oracle => "DBMS_RANDOM.VALUE",
11088                                    _ => "RAND",
11089                                };
11090                                return Ok(Expression::Function(Box::new(Function::new(
11091                                    func_name.to_string(),
11092                                    vec![],
11093                                ))));
11094                            }
11095                        }
11096                    }
11097                    // TRIM without explicit position -> add BOTH for ClickHouse
11098                    if let Expression::Trim(ref trim) = e {
11099                        if matches!(target, DialectType::ClickHouse)
11100                            && trim.sql_standard_syntax
11101                            && trim.characters.is_some()
11102                            && !trim.position_explicit
11103                        {
11104                            let mut new_trim = (**trim).clone();
11105                            new_trim.position_explicit = true;
11106                            return Ok(Expression::Trim(Box::new(new_trim)));
11107                        }
11108                    }
11109                    // BEGIN -> START TRANSACTION for Presto/Trino
11110                    if let Expression::Transaction(ref txn) = e {
11111                        if matches!(
11112                            target,
11113                            DialectType::Presto | DialectType::Trino | DialectType::Athena
11114                        ) {
11115                            // Convert BEGIN to START TRANSACTION by setting mark to "START"
11116                            let mut txn = txn.clone();
11117                            txn.mark = Some(Box::new(Expression::Identifier(Identifier::new(
11118                                "START".to_string(),
11119                            ))));
11120                            return Ok(Expression::Transaction(Box::new(*txn)));
11121                        }
11122                    }
11123                    // IS TRUE/FALSE -> simplified forms for Presto/Trino
11124                    if matches!(
11125                        target,
11126                        DialectType::Presto | DialectType::Trino | DialectType::Athena
11127                    ) {
11128                        match &e {
11129                            Expression::IsTrue(itf) if !itf.not => {
11130                                // x IS TRUE -> x
11131                                return Ok(itf.this.clone());
11132                            }
11133                            Expression::IsTrue(itf) if itf.not => {
11134                                // x IS NOT TRUE -> NOT x
11135                                return Ok(Expression::Not(Box::new(
11136                                    crate::expressions::UnaryOp {
11137                                        this: itf.this.clone(),
11138                                        inferred_type: None,
11139                                    },
11140                                )));
11141                            }
11142                            Expression::IsFalse(itf) if !itf.not => {
11143                                // x IS FALSE -> NOT x
11144                                return Ok(Expression::Not(Box::new(
11145                                    crate::expressions::UnaryOp {
11146                                        this: itf.this.clone(),
11147                                        inferred_type: None,
11148                                    },
11149                                )));
11150                            }
11151                            Expression::IsFalse(itf) if itf.not => {
11152                                // x IS NOT FALSE -> NOT NOT x
11153                                let not_x =
11154                                    Expression::Not(Box::new(crate::expressions::UnaryOp {
11155                                        this: itf.this.clone(),
11156                                        inferred_type: None,
11157                                    }));
11158                                return Ok(Expression::Not(Box::new(
11159                                    crate::expressions::UnaryOp {
11160                                        this: not_x,
11161                                        inferred_type: None,
11162                                    },
11163                                )));
11164                            }
11165                            _ => {}
11166                        }
11167                    }
11168                    // x IS NOT FALSE -> NOT x IS FALSE for Redshift
11169                    if matches!(target, DialectType::Redshift) {
11170                        if let Expression::IsFalse(ref itf) = e {
11171                            if itf.not {
11172                                return Ok(Expression::Not(Box::new(
11173                                    crate::expressions::UnaryOp {
11174                                        this: Expression::IsFalse(Box::new(
11175                                            crate::expressions::IsTrueFalse {
11176                                                this: itf.this.clone(),
11177                                                not: false,
11178                                            },
11179                                        )),
11180                                        inferred_type: None,
11181                                    },
11182                                )));
11183                            }
11184                        }
11185                    }
11186                    // REGEXP_REPLACE: add 'g' flag when source defaults to global replacement
11187                    // Snowflake default is global, PostgreSQL/DuckDB default is first-match-only
11188                    if let Expression::Function(ref f) = e {
11189                        if f.name.eq_ignore_ascii_case("REGEXP_REPLACE")
11190                            && matches!(source, DialectType::Snowflake)
11191                            && matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
11192                        {
11193                            if f.args.len() == 3 {
11194                                let mut args = f.args.clone();
11195                                args.push(Expression::string("g"));
11196                                return Ok(Expression::Function(Box::new(Function::new(
11197                                    "REGEXP_REPLACE".to_string(),
11198                                    args,
11199                                ))));
11200                            } else if f.args.len() == 4 {
11201                                // 4th arg might be position, add 'g' as 5th
11202                                let mut args = f.args.clone();
11203                                args.push(Expression::string("g"));
11204                                return Ok(Expression::Function(Box::new(Function::new(
11205                                    "REGEXP_REPLACE".to_string(),
11206                                    args,
11207                                ))));
11208                            }
11209                        }
11210                    }
11211                    Ok(e)
11212                }
11213
11214                Action::GreatestLeastNull => {
11215                    let f = if let Expression::Function(f) = e {
11216                        *f
11217                    } else {
11218                        unreachable!("action only triggered for Function expressions")
11219                    };
11220                    let mut null_checks: Vec<Expression> = f
11221                        .args
11222                        .iter()
11223                        .map(|a| {
11224                            Expression::IsNull(Box::new(IsNull {
11225                                this: a.clone(),
11226                                not: false,
11227                                postfix_form: false,
11228                            }))
11229                        })
11230                        .collect();
11231                    let condition = if null_checks.len() == 1 {
11232                        null_checks.remove(0)
11233                    } else {
11234                        let first = null_checks.remove(0);
11235                        null_checks.into_iter().fold(first, |acc, check| {
11236                            Expression::Or(Box::new(BinaryOp::new(acc, check)))
11237                        })
11238                    };
11239                    Ok(Expression::Case(Box::new(Case {
11240                        operand: None,
11241                        whens: vec![(condition, Expression::Null(Null))],
11242                        else_: Some(Expression::Function(Box::new(Function::new(
11243                            f.name, f.args,
11244                        )))),
11245                        comments: Vec::new(),
11246                        inferred_type: None,
11247                    })))
11248                }
11249
11250                Action::ArrayGenerateRange => {
11251                    let f = if let Expression::Function(f) = e {
11252                        *f
11253                    } else {
11254                        unreachable!("action only triggered for Function expressions")
11255                    };
11256                    let start = f.args[0].clone();
11257                    let end = f.args[1].clone();
11258                    let step = f.args.get(2).cloned();
11259
11260                    // Helper: compute end - 1 for converting exclusive→inclusive end.
11261                    // When end is a literal number, simplify to a computed literal.
11262                    fn exclusive_to_inclusive_end(end: &Expression) -> Expression {
11263                        // Try to simplify literal numbers
11264                        match end {
11265                            Expression::Literal(lit)
11266                                if matches!(lit.as_ref(), Literal::Number(_)) =>
11267                            {
11268                                let Literal::Number(n) = lit.as_ref() else {
11269                                    unreachable!()
11270                                };
11271                                if let Ok(val) = n.parse::<i64>() {
11272                                    return Expression::number(val - 1);
11273                                }
11274                            }
11275                            Expression::Neg(u) => {
11276                                if let Expression::Literal(lit) = &u.this {
11277                                    if let Literal::Number(n) = lit.as_ref() {
11278                                        if let Ok(val) = n.parse::<i64>() {
11279                                            return Expression::number(-val - 1);
11280                                        }
11281                                    }
11282                                }
11283                            }
11284                            _ => {}
11285                        }
11286                        // Non-literal: produce end - 1 expression
11287                        Expression::Sub(Box::new(BinaryOp::new(end.clone(), Expression::number(1))))
11288                    }
11289
11290                    match target {
11291                        // Snowflake ARRAY_GENERATE_RANGE and DuckDB RANGE both use exclusive end,
11292                        // so no adjustment needed — just rename the function.
11293                        DialectType::Snowflake => {
11294                            let mut args = vec![start, end];
11295                            if let Some(s) = step {
11296                                args.push(s);
11297                            }
11298                            Ok(Expression::Function(Box::new(Function::new(
11299                                "ARRAY_GENERATE_RANGE".to_string(),
11300                                args,
11301                            ))))
11302                        }
11303                        DialectType::DuckDB => {
11304                            let mut args = vec![start, end];
11305                            if let Some(s) = step {
11306                                args.push(s);
11307                            }
11308                            Ok(Expression::Function(Box::new(Function::new(
11309                                "RANGE".to_string(),
11310                                args,
11311                            ))))
11312                        }
11313                        // These dialects use inclusive end, so convert exclusive→inclusive.
11314                        // Presto/Trino: simplify literal numbers (3 → 2).
11315                        DialectType::Presto | DialectType::Trino => {
11316                            let end_inclusive = exclusive_to_inclusive_end(&end);
11317                            let mut args = vec![start, end_inclusive];
11318                            if let Some(s) = step {
11319                                args.push(s);
11320                            }
11321                            Ok(Expression::Function(Box::new(Function::new(
11322                                "SEQUENCE".to_string(),
11323                                args,
11324                            ))))
11325                        }
11326                        // PostgreSQL, Redshift, BigQuery: keep as end - 1 expression form.
11327                        DialectType::PostgreSQL | DialectType::Redshift => {
11328                            let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
11329                                end.clone(),
11330                                Expression::number(1),
11331                            )));
11332                            let mut args = vec![start, end_minus_1];
11333                            if let Some(s) = step {
11334                                args.push(s);
11335                            }
11336                            Ok(Expression::Function(Box::new(Function::new(
11337                                "GENERATE_SERIES".to_string(),
11338                                args,
11339                            ))))
11340                        }
11341                        DialectType::BigQuery => {
11342                            let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
11343                                end.clone(),
11344                                Expression::number(1),
11345                            )));
11346                            let mut args = vec![start, end_minus_1];
11347                            if let Some(s) = step {
11348                                args.push(s);
11349                            }
11350                            Ok(Expression::Function(Box::new(Function::new(
11351                                "GENERATE_ARRAY".to_string(),
11352                                args,
11353                            ))))
11354                        }
11355                        _ => Ok(Expression::Function(Box::new(Function::new(
11356                            f.name, f.args,
11357                        )))),
11358                    }
11359                }
11360
11361                Action::Div0TypedDivision => {
11362                    let if_func = if let Expression::IfFunc(f) = e {
11363                        *f
11364                    } else {
11365                        unreachable!("action only triggered for IfFunc expressions")
11366                    };
11367                    if let Some(Expression::Div(div)) = if_func.false_value {
11368                        let cast_type = if matches!(target, DialectType::SQLite) {
11369                            DataType::Float {
11370                                precision: None,
11371                                scale: None,
11372                                real_spelling: true,
11373                            }
11374                        } else {
11375                            DataType::Double {
11376                                precision: None,
11377                                scale: None,
11378                            }
11379                        };
11380                        let casted_left = Expression::Cast(Box::new(Cast {
11381                            this: div.left,
11382                            to: cast_type,
11383                            trailing_comments: vec![],
11384                            double_colon_syntax: false,
11385                            format: None,
11386                            default: None,
11387                            inferred_type: None,
11388                        }));
11389                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
11390                            condition: if_func.condition,
11391                            true_value: if_func.true_value,
11392                            false_value: Some(Expression::Div(Box::new(BinaryOp::new(
11393                                casted_left,
11394                                div.right,
11395                            )))),
11396                            original_name: if_func.original_name,
11397                            inferred_type: None,
11398                        })))
11399                    } else {
11400                        // Not actually a Div, reconstruct
11401                        Ok(Expression::IfFunc(Box::new(if_func)))
11402                    }
11403                }
11404
11405                Action::ArrayAggCollectList => {
11406                    let agg = if let Expression::ArrayAgg(a) = e {
11407                        *a
11408                    } else {
11409                        unreachable!("action only triggered for ArrayAgg expressions")
11410                    };
11411                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
11412                        name: Some("COLLECT_LIST".to_string()),
11413                        ..agg
11414                    })))
11415                }
11416
11417                Action::ArrayAggToGroupConcat => {
11418                    let agg = if let Expression::ArrayAgg(a) = e {
11419                        *a
11420                    } else {
11421                        unreachable!("action only triggered for ArrayAgg expressions")
11422                    };
11423                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
11424                        name: Some("GROUP_CONCAT".to_string()),
11425                        ..agg
11426                    })))
11427                }
11428
11429                Action::ArrayAggWithinGroupFilter => {
11430                    let wg = if let Expression::WithinGroup(w) = e {
11431                        *w
11432                    } else {
11433                        unreachable!("action only triggered for WithinGroup expressions")
11434                    };
11435                    if let Expression::ArrayAgg(inner_agg) = wg.this {
11436                        let col = inner_agg.this.clone();
11437                        let filter = Expression::IsNull(Box::new(IsNull {
11438                            this: col,
11439                            not: true,
11440                            postfix_form: false,
11441                        }));
11442                        // For DuckDB, add explicit NULLS FIRST for DESC ordering
11443                        let order_by = if matches!(target, DialectType::DuckDB) {
11444                            wg.order_by
11445                                .into_iter()
11446                                .map(|mut o| {
11447                                    if o.desc && o.nulls_first.is_none() {
11448                                        o.nulls_first = Some(true);
11449                                    }
11450                                    o
11451                                })
11452                                .collect()
11453                        } else {
11454                            wg.order_by
11455                        };
11456                        Ok(Expression::ArrayAgg(Box::new(AggFunc {
11457                            this: inner_agg.this,
11458                            distinct: inner_agg.distinct,
11459                            filter: Some(filter),
11460                            order_by,
11461                            name: inner_agg.name,
11462                            ignore_nulls: inner_agg.ignore_nulls,
11463                            having_max: inner_agg.having_max,
11464                            limit: inner_agg.limit,
11465                            inferred_type: None,
11466                        })))
11467                    } else {
11468                        Ok(Expression::WithinGroup(Box::new(wg)))
11469                    }
11470                }
11471
11472                Action::ArrayAggFilter => {
11473                    let agg = if let Expression::ArrayAgg(a) = e {
11474                        *a
11475                    } else {
11476                        unreachable!("action only triggered for ArrayAgg expressions")
11477                    };
11478                    let col = agg.this.clone();
11479                    let filter = Expression::IsNull(Box::new(IsNull {
11480                        this: col,
11481                        not: true,
11482                        postfix_form: false,
11483                    }));
11484                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
11485                        filter: Some(filter),
11486                        ..agg
11487                    })))
11488                }
11489
11490                Action::ArrayAggNullFilter => {
11491                    // ARRAY_AGG(x) FILTER(WHERE cond) -> ARRAY_AGG(x) FILTER(WHERE cond AND NOT x IS NULL)
11492                    // For source dialects that exclude NULLs (Spark/Hive) targeting DuckDB which includes them
11493                    let agg = if let Expression::ArrayAgg(a) = e {
11494                        *a
11495                    } else {
11496                        unreachable!("action only triggered for ArrayAgg expressions")
11497                    };
11498                    let col = agg.this.clone();
11499                    let not_null = Expression::IsNull(Box::new(IsNull {
11500                        this: col,
11501                        not: true,
11502                        postfix_form: true, // Use "NOT x IS NULL" form (prefix NOT)
11503                    }));
11504                    let new_filter = if let Some(existing_filter) = agg.filter {
11505                        // AND the NOT IS NULL with existing filter
11506                        Expression::And(Box::new(crate::expressions::BinaryOp::new(
11507                            existing_filter,
11508                            not_null,
11509                        )))
11510                    } else {
11511                        not_null
11512                    };
11513                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
11514                        filter: Some(new_filter),
11515                        ..agg
11516                    })))
11517                }
11518
11519                Action::BigQueryArraySelectAsStructToSnowflake => {
11520                    // ARRAY(SELECT AS STRUCT x1 AS x1, x2 AS x2 FROM t)
11521                    // -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('x1', x1, 'x2', x2)) FROM t)
11522                    if let Expression::Function(mut f) = e {
11523                        let is_match = f.args.len() == 1
11524                            && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"));
11525                        if is_match {
11526                            let inner_select = match f.args.remove(0) {
11527                                Expression::Select(s) => *s,
11528                                _ => unreachable!(
11529                                    "argument already verified to be a Select expression"
11530                                ),
11531                            };
11532                            // Build OBJECT_CONSTRUCT args from SELECT expressions
11533                            let mut oc_args = Vec::new();
11534                            for expr in &inner_select.expressions {
11535                                match expr {
11536                                    Expression::Alias(a) => {
11537                                        let key = Expression::Literal(Box::new(Literal::String(
11538                                            a.alias.name.clone(),
11539                                        )));
11540                                        let value = a.this.clone();
11541                                        oc_args.push(key);
11542                                        oc_args.push(value);
11543                                    }
11544                                    Expression::Column(c) => {
11545                                        let key = Expression::Literal(Box::new(Literal::String(
11546                                            c.name.name.clone(),
11547                                        )));
11548                                        oc_args.push(key);
11549                                        oc_args.push(expr.clone());
11550                                    }
11551                                    _ => {
11552                                        oc_args.push(expr.clone());
11553                                    }
11554                                }
11555                            }
11556                            let object_construct = Expression::Function(Box::new(Function::new(
11557                                "OBJECT_CONSTRUCT".to_string(),
11558                                oc_args,
11559                            )));
11560                            let array_agg = Expression::Function(Box::new(Function::new(
11561                                "ARRAY_AGG".to_string(),
11562                                vec![object_construct],
11563                            )));
11564                            let mut new_select = crate::expressions::Select::new();
11565                            new_select.expressions = vec![array_agg];
11566                            new_select.from = inner_select.from.clone();
11567                            new_select.where_clause = inner_select.where_clause.clone();
11568                            new_select.group_by = inner_select.group_by.clone();
11569                            new_select.having = inner_select.having.clone();
11570                            new_select.joins = inner_select.joins.clone();
11571                            Ok(Expression::Subquery(Box::new(
11572                                crate::expressions::Subquery {
11573                                    this: Expression::Select(Box::new(new_select)),
11574                                    alias: None,
11575                                    column_aliases: Vec::new(),
11576                                    alias_explicit_as: false,
11577                                    alias_keyword: None,
11578                                    order_by: None,
11579                                    limit: None,
11580                                    offset: None,
11581                                    distribute_by: None,
11582                                    sort_by: None,
11583                                    cluster_by: None,
11584                                    lateral: false,
11585                                    modifiers_inside: false,
11586                                    trailing_comments: Vec::new(),
11587                                    inferred_type: None,
11588                                },
11589                            )))
11590                        } else {
11591                            Ok(Expression::Function(f))
11592                        }
11593                    } else {
11594                        Ok(e)
11595                    }
11596                }
11597
11598                Action::BigQueryPercentileContToDuckDB => {
11599                    // PERCENTILE_CONT(x, frac [RESPECT NULLS]) -> QUANTILE_CONT(x, frac) for DuckDB
11600                    if let Expression::AggregateFunction(mut af) = e {
11601                        af.name = "QUANTILE_CONT".to_string();
11602                        af.ignore_nulls = None; // Strip RESPECT/IGNORE NULLS
11603                                                // Keep only first 2 args
11604                        if af.args.len() > 2 {
11605                            af.args.truncate(2);
11606                        }
11607                        Ok(Expression::AggregateFunction(af))
11608                    } else {
11609                        Ok(e)
11610                    }
11611                }
11612
11613                Action::ArrayAggIgnoreNullsDuckDB => {
11614                    // ARRAY_AGG(x IGNORE NULLS ORDER BY a, b DESC) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, b DESC)
11615                    // Strip IGNORE NULLS, add NULLS FIRST to first ORDER BY column
11616                    let mut agg = if let Expression::ArrayAgg(a) = e {
11617                        *a
11618                    } else {
11619                        unreachable!("action only triggered for ArrayAgg expressions")
11620                    };
11621                    agg.ignore_nulls = None; // Strip IGNORE NULLS
11622                    if !agg.order_by.is_empty() {
11623                        agg.order_by[0].nulls_first = Some(true);
11624                    }
11625                    Ok(Expression::ArrayAgg(Box::new(agg)))
11626                }
11627
11628                Action::CountDistinctMultiArg => {
11629                    // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
11630                    if let Expression::Count(c) = e {
11631                        if let Some(Expression::Tuple(t)) = c.this {
11632                            let args = t.expressions;
11633                            // Build CASE expression:
11634                            // WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
11635                            let mut whens = Vec::new();
11636                            for arg in &args {
11637                                whens.push((
11638                                    Expression::IsNull(Box::new(IsNull {
11639                                        this: arg.clone(),
11640                                        not: false,
11641                                        postfix_form: false,
11642                                    })),
11643                                    Expression::Null(crate::expressions::Null),
11644                                ));
11645                            }
11646                            // Build the tuple for ELSE
11647                            let tuple_expr =
11648                                Expression::Tuple(Box::new(crate::expressions::Tuple {
11649                                    expressions: args,
11650                                }));
11651                            let case_expr = Expression::Case(Box::new(crate::expressions::Case {
11652                                operand: None,
11653                                whens,
11654                                else_: Some(tuple_expr),
11655                                comments: Vec::new(),
11656                                inferred_type: None,
11657                            }));
11658                            Ok(Expression::Count(Box::new(crate::expressions::CountFunc {
11659                                this: Some(case_expr),
11660                                star: false,
11661                                distinct: true,
11662                                filter: c.filter,
11663                                ignore_nulls: c.ignore_nulls,
11664                                original_name: c.original_name,
11665                                inferred_type: None,
11666                            })))
11667                        } else {
11668                            Ok(Expression::Count(c))
11669                        }
11670                    } else {
11671                        Ok(e)
11672                    }
11673                }
11674
11675                Action::CastTimestampToDatetime => {
11676                    let c = if let Expression::Cast(c) = e {
11677                        *c
11678                    } else {
11679                        unreachable!("action only triggered for Cast expressions")
11680                    };
11681                    Ok(Expression::Cast(Box::new(Cast {
11682                        to: DataType::Custom {
11683                            name: "DATETIME".to_string(),
11684                        },
11685                        ..c
11686                    })))
11687                }
11688
11689                Action::CastTimestampStripTz => {
11690                    // CAST(x AS TIMESTAMP(n) WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
11691                    let c = if let Expression::Cast(c) = e {
11692                        *c
11693                    } else {
11694                        unreachable!("action only triggered for Cast expressions")
11695                    };
11696                    Ok(Expression::Cast(Box::new(Cast {
11697                        to: DataType::Timestamp {
11698                            precision: None,
11699                            timezone: false,
11700                        },
11701                        ..c
11702                    })))
11703                }
11704
11705                Action::CastTimestamptzToFunc => {
11706                    // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
11707                    let c = if let Expression::Cast(c) = e {
11708                        *c
11709                    } else {
11710                        unreachable!("action only triggered for Cast expressions")
11711                    };
11712                    Ok(Expression::Function(Box::new(Function::new(
11713                        "TIMESTAMP".to_string(),
11714                        vec![c.this],
11715                    ))))
11716                }
11717
11718                Action::ToDateToCast => {
11719                    // Convert TO_DATE(x) -> CAST(x AS DATE) for DuckDB
11720                    if let Expression::Function(f) = e {
11721                        let arg = f.args.into_iter().next().unwrap();
11722                        Ok(Expression::Cast(Box::new(Cast {
11723                            this: arg,
11724                            to: DataType::Date,
11725                            double_colon_syntax: false,
11726                            trailing_comments: vec![],
11727                            format: None,
11728                            default: None,
11729                            inferred_type: None,
11730                        })))
11731                    } else {
11732                        Ok(e)
11733                    }
11734                }
11735                Action::DateTruncWrapCast => {
11736                    // Handle both Expression::DateTrunc/TimestampTrunc and
11737                    // Expression::Function("DATE_TRUNC", [unit, expr])
11738                    match e {
11739                        Expression::DateTrunc(d) | Expression::TimestampTrunc(d) => {
11740                            let input_type = match &d.this {
11741                                Expression::Cast(c) => Some(c.to.clone()),
11742                                _ => None,
11743                            };
11744                            if let Some(cast_type) = input_type {
11745                                let is_time = matches!(cast_type, DataType::Time { .. });
11746                                if is_time {
11747                                    let date_expr = Expression::Cast(Box::new(Cast {
11748                                        this: Expression::Literal(Box::new(
11749                                            crate::expressions::Literal::String(
11750                                                "1970-01-01".to_string(),
11751                                            ),
11752                                        )),
11753                                        to: DataType::Date,
11754                                        double_colon_syntax: false,
11755                                        trailing_comments: vec![],
11756                                        format: None,
11757                                        default: None,
11758                                        inferred_type: None,
11759                                    }));
11760                                    let add_expr =
11761                                        Expression::Add(Box::new(BinaryOp::new(date_expr, d.this)));
11762                                    let inner = Expression::DateTrunc(Box::new(DateTruncFunc {
11763                                        this: add_expr,
11764                                        unit: d.unit,
11765                                    }));
11766                                    Ok(Expression::Cast(Box::new(Cast {
11767                                        this: inner,
11768                                        to: cast_type,
11769                                        double_colon_syntax: false,
11770                                        trailing_comments: vec![],
11771                                        format: None,
11772                                        default: None,
11773                                        inferred_type: None,
11774                                    })))
11775                                } else {
11776                                    let inner = Expression::DateTrunc(Box::new(*d));
11777                                    Ok(Expression::Cast(Box::new(Cast {
11778                                        this: inner,
11779                                        to: cast_type,
11780                                        double_colon_syntax: false,
11781                                        trailing_comments: vec![],
11782                                        format: None,
11783                                        default: None,
11784                                        inferred_type: None,
11785                                    })))
11786                                }
11787                            } else {
11788                                Ok(Expression::DateTrunc(d))
11789                            }
11790                        }
11791                        Expression::Function(f) if f.args.len() == 2 => {
11792                            // Function-based DATE_TRUNC(unit, expr)
11793                            let input_type = match &f.args[1] {
11794                                Expression::Cast(c) => Some(c.to.clone()),
11795                                _ => None,
11796                            };
11797                            if let Some(cast_type) = input_type {
11798                                let is_time = matches!(cast_type, DataType::Time { .. });
11799                                if is_time {
11800                                    let date_expr = Expression::Cast(Box::new(Cast {
11801                                        this: Expression::Literal(Box::new(
11802                                            crate::expressions::Literal::String(
11803                                                "1970-01-01".to_string(),
11804                                            ),
11805                                        )),
11806                                        to: DataType::Date,
11807                                        double_colon_syntax: false,
11808                                        trailing_comments: vec![],
11809                                        format: None,
11810                                        default: None,
11811                                        inferred_type: None,
11812                                    }));
11813                                    let mut args = f.args;
11814                                    let unit_arg = args.remove(0);
11815                                    let time_expr = args.remove(0);
11816                                    let add_expr = Expression::Add(Box::new(BinaryOp::new(
11817                                        date_expr, time_expr,
11818                                    )));
11819                                    let inner = Expression::Function(Box::new(Function::new(
11820                                        "DATE_TRUNC".to_string(),
11821                                        vec![unit_arg, add_expr],
11822                                    )));
11823                                    Ok(Expression::Cast(Box::new(Cast {
11824                                        this: inner,
11825                                        to: cast_type,
11826                                        double_colon_syntax: false,
11827                                        trailing_comments: vec![],
11828                                        format: None,
11829                                        default: None,
11830                                        inferred_type: None,
11831                                    })))
11832                                } else {
11833                                    // Wrap the function in CAST
11834                                    Ok(Expression::Cast(Box::new(Cast {
11835                                        this: Expression::Function(f),
11836                                        to: cast_type,
11837                                        double_colon_syntax: false,
11838                                        trailing_comments: vec![],
11839                                        format: None,
11840                                        default: None,
11841                                        inferred_type: None,
11842                                    })))
11843                                }
11844                            } else {
11845                                Ok(Expression::Function(f))
11846                            }
11847                        }
11848                        other => Ok(other),
11849                    }
11850                }
11851
11852                Action::RegexpReplaceSnowflakeToDuckDB => {
11853                    // Snowflake REGEXP_REPLACE(s, p, r, position) -> REGEXP_REPLACE(s, p, r, 'g')
11854                    if let Expression::Function(f) = e {
11855                        let mut args = f.args;
11856                        let subject = args.remove(0);
11857                        let pattern = args.remove(0);
11858                        let replacement = args.remove(0);
11859                        Ok(Expression::Function(Box::new(Function::new(
11860                            "REGEXP_REPLACE".to_string(),
11861                            vec![
11862                                subject,
11863                                pattern,
11864                                replacement,
11865                                Expression::Literal(Box::new(crate::expressions::Literal::String(
11866                                    "g".to_string(),
11867                                ))),
11868                            ],
11869                        ))))
11870                    } else {
11871                        Ok(e)
11872                    }
11873                }
11874
11875                Action::RegexpReplacePositionSnowflakeToDuckDB => {
11876                    // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
11877                    // pos=1, occ=1 -> REGEXP_REPLACE(s, p, r) (single replace, no 'g')
11878                    // pos>1, occ=0 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r, 'g')
11879                    // pos>1, occ=1 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r)
11880                    // pos=1, occ=0 -> REGEXP_REPLACE(s, p, r, 'g') (replace all)
11881                    if let Expression::Function(f) = e {
11882                        let mut args = f.args;
11883                        let subject = args.remove(0);
11884                        let pattern = args.remove(0);
11885                        let replacement = args.remove(0);
11886                        let position = args.remove(0);
11887                        let occurrence = args.remove(0);
11888
11889                        let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
11890                        let is_occ_0 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
11891                        let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
11892
11893                        if is_pos_1 && is_occ_1 {
11894                            // REGEXP_REPLACE(s, p, r) - single replace, no flags
11895                            Ok(Expression::Function(Box::new(Function::new(
11896                                "REGEXP_REPLACE".to_string(),
11897                                vec![subject, pattern, replacement],
11898                            ))))
11899                        } else if is_pos_1 && is_occ_0 {
11900                            // REGEXP_REPLACE(s, p, r, 'g') - global replace
11901                            Ok(Expression::Function(Box::new(Function::new(
11902                                "REGEXP_REPLACE".to_string(),
11903                                vec![
11904                                    subject,
11905                                    pattern,
11906                                    replacement,
11907                                    Expression::Literal(Box::new(Literal::String("g".to_string()))),
11908                                ],
11909                            ))))
11910                        } else {
11911                            // pos>1: SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r[, 'g'])
11912                            // Pre-compute pos-1 when position is a numeric literal
11913                            let pos_minus_1 = if let Expression::Literal(ref lit) = position {
11914                                if let Literal::Number(ref n) = lit.as_ref() {
11915                                    if let Ok(val) = n.parse::<i64>() {
11916                                        Expression::number(val - 1)
11917                                    } else {
11918                                        Expression::Sub(Box::new(BinaryOp::new(
11919                                            position.clone(),
11920                                            Expression::number(1),
11921                                        )))
11922                                    }
11923                                } else {
11924                                    position.clone()
11925                                }
11926                            } else {
11927                                Expression::Sub(Box::new(BinaryOp::new(
11928                                    position.clone(),
11929                                    Expression::number(1),
11930                                )))
11931                            };
11932                            let prefix = Expression::Function(Box::new(Function::new(
11933                                "SUBSTRING".to_string(),
11934                                vec![subject.clone(), Expression::number(1), pos_minus_1],
11935                            )));
11936                            let suffix_subject = Expression::Function(Box::new(Function::new(
11937                                "SUBSTRING".to_string(),
11938                                vec![subject, position],
11939                            )));
11940                            let mut replace_args = vec![suffix_subject, pattern, replacement];
11941                            if is_occ_0 {
11942                                replace_args.push(Expression::Literal(Box::new(Literal::String(
11943                                    "g".to_string(),
11944                                ))));
11945                            }
11946                            let replace_expr = Expression::Function(Box::new(Function::new(
11947                                "REGEXP_REPLACE".to_string(),
11948                                replace_args,
11949                            )));
11950                            Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
11951                                this: Box::new(prefix),
11952                                expression: Box::new(replace_expr),
11953                                safe: None,
11954                            })))
11955                        }
11956                    } else {
11957                        Ok(e)
11958                    }
11959                }
11960
11961                Action::RegexpSubstrSnowflakeToDuckDB => {
11962                    // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
11963                    if let Expression::Function(f) = e {
11964                        let mut args = f.args;
11965                        let arg_count = args.len();
11966                        match arg_count {
11967                            // REGEXP_SUBSTR(s, p) -> REGEXP_EXTRACT(s, p)
11968                            0..=2 => Ok(Expression::Function(Box::new(Function::new(
11969                                "REGEXP_EXTRACT".to_string(),
11970                                args,
11971                            )))),
11972                            // REGEXP_SUBSTR(s, p, pos) -> REGEXP_EXTRACT(NULLIF(SUBSTRING(s, pos), ''), p)
11973                            3 => {
11974                                let subject = args.remove(0);
11975                                let pattern = args.remove(0);
11976                                let position = args.remove(0);
11977                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
11978                                if is_pos_1 {
11979                                    Ok(Expression::Function(Box::new(Function::new(
11980                                        "REGEXP_EXTRACT".to_string(),
11981                                        vec![subject, pattern],
11982                                    ))))
11983                                } else {
11984                                    let substring_expr =
11985                                        Expression::Function(Box::new(Function::new(
11986                                            "SUBSTRING".to_string(),
11987                                            vec![subject, position],
11988                                        )));
11989                                    let nullif_expr =
11990                                        Expression::Function(Box::new(Function::new(
11991                                            "NULLIF".to_string(),
11992                                            vec![
11993                                                substring_expr,
11994                                                Expression::Literal(Box::new(Literal::String(
11995                                                    String::new(),
11996                                                ))),
11997                                            ],
11998                                        )));
11999                                    Ok(Expression::Function(Box::new(Function::new(
12000                                        "REGEXP_EXTRACT".to_string(),
12001                                        vec![nullif_expr, pattern],
12002                                    ))))
12003                                }
12004                            }
12005                            // REGEXP_SUBSTR(s, p, pos, occ) -> depends on pos and occ
12006                            4 => {
12007                                let subject = args.remove(0);
12008                                let pattern = args.remove(0);
12009                                let position = args.remove(0);
12010                                let occurrence = args.remove(0);
12011                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
12012                                let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
12013
12014                                let effective_subject = if is_pos_1 {
12015                                    subject
12016                                } else {
12017                                    let substring_expr =
12018                                        Expression::Function(Box::new(Function::new(
12019                                            "SUBSTRING".to_string(),
12020                                            vec![subject, position],
12021                                        )));
12022                                    Expression::Function(Box::new(Function::new(
12023                                        "NULLIF".to_string(),
12024                                        vec![
12025                                            substring_expr,
12026                                            Expression::Literal(Box::new(Literal::String(
12027                                                String::new(),
12028                                            ))),
12029                                        ],
12030                                    )))
12031                                };
12032
12033                                if is_occ_1 {
12034                                    Ok(Expression::Function(Box::new(Function::new(
12035                                        "REGEXP_EXTRACT".to_string(),
12036                                        vec![effective_subject, pattern],
12037                                    ))))
12038                                } else {
12039                                    // ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, p), occ)
12040                                    let extract_all =
12041                                        Expression::Function(Box::new(Function::new(
12042                                            "REGEXP_EXTRACT_ALL".to_string(),
12043                                            vec![effective_subject, pattern],
12044                                        )));
12045                                    Ok(Expression::Function(Box::new(Function::new(
12046                                        "ARRAY_EXTRACT".to_string(),
12047                                        vec![extract_all, occurrence],
12048                                    ))))
12049                                }
12050                            }
12051                            // REGEXP_SUBSTR(s, p, 1, 1, 'e') -> REGEXP_EXTRACT(s, p)
12052                            5 => {
12053                                let subject = args.remove(0);
12054                                let pattern = args.remove(0);
12055                                let _position = args.remove(0);
12056                                let _occurrence = args.remove(0);
12057                                let _flags = args.remove(0);
12058                                // Strip 'e' flag, convert to REGEXP_EXTRACT
12059                                Ok(Expression::Function(Box::new(Function::new(
12060                                    "REGEXP_EXTRACT".to_string(),
12061                                    vec![subject, pattern],
12062                                ))))
12063                            }
12064                            // REGEXP_SUBSTR(s, p, 1, 1, 'e', group) -> REGEXP_EXTRACT(s, p[, group])
12065                            _ => {
12066                                let subject = args.remove(0);
12067                                let pattern = args.remove(0);
12068                                let _position = args.remove(0);
12069                                let _occurrence = args.remove(0);
12070                                let _flags = args.remove(0);
12071                                let group = args.remove(0);
12072                                let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
12073                                if is_group_0 {
12074                                    // Strip group=0 (default)
12075                                    Ok(Expression::Function(Box::new(Function::new(
12076                                        "REGEXP_EXTRACT".to_string(),
12077                                        vec![subject, pattern],
12078                                    ))))
12079                                } else {
12080                                    Ok(Expression::Function(Box::new(Function::new(
12081                                        "REGEXP_EXTRACT".to_string(),
12082                                        vec![subject, pattern, group],
12083                                    ))))
12084                                }
12085                            }
12086                        }
12087                    } else {
12088                        Ok(e)
12089                    }
12090                }
12091
12092                Action::RegexpSubstrSnowflakeIdentity => {
12093                    // Snowflake→Snowflake: REGEXP_SUBSTR/REGEXP_SUBSTR_ALL with 6 args
12094                    // Strip trailing group=0
12095                    if let Expression::Function(f) = e {
12096                        let func_name = f.name.clone();
12097                        let mut args = f.args;
12098                        if args.len() == 6 {
12099                            let is_group_0 = matches!(&args[5], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
12100                            if is_group_0 {
12101                                args.truncate(5);
12102                            }
12103                        }
12104                        Ok(Expression::Function(Box::new(Function::new(
12105                            func_name, args,
12106                        ))))
12107                    } else {
12108                        Ok(e)
12109                    }
12110                }
12111
12112                Action::RegexpSubstrAllSnowflakeToDuckDB => {
12113                    // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
12114                    if let Expression::Function(f) = e {
12115                        let mut args = f.args;
12116                        let arg_count = args.len();
12117                        match arg_count {
12118                            // REGEXP_SUBSTR_ALL(s, p) -> REGEXP_EXTRACT_ALL(s, p)
12119                            0..=2 => Ok(Expression::Function(Box::new(Function::new(
12120                                "REGEXP_EXTRACT_ALL".to_string(),
12121                                args,
12122                            )))),
12123                            // REGEXP_SUBSTR_ALL(s, p, pos) -> REGEXP_EXTRACT_ALL(SUBSTRING(s, pos), p)
12124                            3 => {
12125                                let subject = args.remove(0);
12126                                let pattern = args.remove(0);
12127                                let position = args.remove(0);
12128                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
12129                                if is_pos_1 {
12130                                    Ok(Expression::Function(Box::new(Function::new(
12131                                        "REGEXP_EXTRACT_ALL".to_string(),
12132                                        vec![subject, pattern],
12133                                    ))))
12134                                } else {
12135                                    let substring_expr =
12136                                        Expression::Function(Box::new(Function::new(
12137                                            "SUBSTRING".to_string(),
12138                                            vec![subject, position],
12139                                        )));
12140                                    Ok(Expression::Function(Box::new(Function::new(
12141                                        "REGEXP_EXTRACT_ALL".to_string(),
12142                                        vec![substring_expr, pattern],
12143                                    ))))
12144                                }
12145                            }
12146                            // REGEXP_SUBSTR_ALL(s, p, 1, occ) -> REGEXP_EXTRACT_ALL(s, p)[occ:]
12147                            4 => {
12148                                let subject = args.remove(0);
12149                                let pattern = args.remove(0);
12150                                let position = args.remove(0);
12151                                let occurrence = args.remove(0);
12152                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
12153                                let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
12154
12155                                let effective_subject = if is_pos_1 {
12156                                    subject
12157                                } else {
12158                                    Expression::Function(Box::new(Function::new(
12159                                        "SUBSTRING".to_string(),
12160                                        vec![subject, position],
12161                                    )))
12162                                };
12163
12164                                if is_occ_1 {
12165                                    Ok(Expression::Function(Box::new(Function::new(
12166                                        "REGEXP_EXTRACT_ALL".to_string(),
12167                                        vec![effective_subject, pattern],
12168                                    ))))
12169                                } else {
12170                                    // REGEXP_EXTRACT_ALL(s, p)[occ:]
12171                                    let extract_all =
12172                                        Expression::Function(Box::new(Function::new(
12173                                            "REGEXP_EXTRACT_ALL".to_string(),
12174                                            vec![effective_subject, pattern],
12175                                        )));
12176                                    Ok(Expression::ArraySlice(Box::new(
12177                                        crate::expressions::ArraySlice {
12178                                            this: extract_all,
12179                                            start: Some(occurrence),
12180                                            end: None,
12181                                        },
12182                                    )))
12183                                }
12184                            }
12185                            // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e') -> REGEXP_EXTRACT_ALL(s, p)
12186                            5 => {
12187                                let subject = args.remove(0);
12188                                let pattern = args.remove(0);
12189                                let _position = args.remove(0);
12190                                let _occurrence = args.remove(0);
12191                                let _flags = args.remove(0);
12192                                Ok(Expression::Function(Box::new(Function::new(
12193                                    "REGEXP_EXTRACT_ALL".to_string(),
12194                                    vec![subject, pattern],
12195                                ))))
12196                            }
12197                            // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e', 0) -> REGEXP_EXTRACT_ALL(s, p)
12198                            _ => {
12199                                let subject = args.remove(0);
12200                                let pattern = args.remove(0);
12201                                let _position = args.remove(0);
12202                                let _occurrence = args.remove(0);
12203                                let _flags = args.remove(0);
12204                                let group = args.remove(0);
12205                                let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
12206                                if is_group_0 {
12207                                    Ok(Expression::Function(Box::new(Function::new(
12208                                        "REGEXP_EXTRACT_ALL".to_string(),
12209                                        vec![subject, pattern],
12210                                    ))))
12211                                } else {
12212                                    Ok(Expression::Function(Box::new(Function::new(
12213                                        "REGEXP_EXTRACT_ALL".to_string(),
12214                                        vec![subject, pattern, group],
12215                                    ))))
12216                                }
12217                            }
12218                        }
12219                    } else {
12220                        Ok(e)
12221                    }
12222                }
12223
12224                Action::RegexpCountSnowflakeToDuckDB => {
12225                    // Snowflake REGEXP_COUNT(s, p[, pos[, flags]]) ->
12226                    // DuckDB: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
12227                    if let Expression::Function(f) = e {
12228                        let mut args = f.args;
12229                        let arg_count = args.len();
12230                        let subject = args.remove(0);
12231                        let pattern = args.remove(0);
12232
12233                        // Handle position arg
12234                        let effective_subject = if arg_count >= 3 {
12235                            let position = args.remove(0);
12236                            Expression::Function(Box::new(Function::new(
12237                                "SUBSTRING".to_string(),
12238                                vec![subject, position],
12239                            )))
12240                        } else {
12241                            subject
12242                        };
12243
12244                        // Handle flags arg -> embed as (?flags) prefix in pattern
12245                        let effective_pattern = if arg_count >= 4 {
12246                            let flags = args.remove(0);
12247                            match &flags {
12248                                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(f_str) if !f_str.is_empty()) =>
12249                                {
12250                                    let Literal::String(f_str) = lit.as_ref() else {
12251                                        unreachable!()
12252                                    };
12253                                    // Always use concatenation: '(?flags)' || pattern
12254                                    let prefix = Expression::Literal(Box::new(Literal::String(
12255                                        format!("(?{})", f_str),
12256                                    )));
12257                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
12258                                        this: Box::new(prefix),
12259                                        expression: Box::new(pattern.clone()),
12260                                        safe: None,
12261                                    }))
12262                                }
12263                                _ => pattern.clone(),
12264                            }
12265                        } else {
12266                            pattern.clone()
12267                        };
12268
12269                        // Build: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
12270                        let extract_all = Expression::Function(Box::new(Function::new(
12271                            "REGEXP_EXTRACT_ALL".to_string(),
12272                            vec![effective_subject, effective_pattern.clone()],
12273                        )));
12274                        let length_expr =
12275                            Expression::Length(Box::new(crate::expressions::UnaryFunc {
12276                                this: extract_all,
12277                                original_name: None,
12278                                inferred_type: None,
12279                            }));
12280                        let condition = Expression::Eq(Box::new(BinaryOp::new(
12281                            effective_pattern,
12282                            Expression::Literal(Box::new(Literal::String(String::new()))),
12283                        )));
12284                        Ok(Expression::Case(Box::new(Case {
12285                            operand: None,
12286                            whens: vec![(condition, Expression::number(0))],
12287                            else_: Some(length_expr),
12288                            comments: vec![],
12289                            inferred_type: None,
12290                        })))
12291                    } else {
12292                        Ok(e)
12293                    }
12294                }
12295
12296                Action::RegexpInstrSnowflakeToDuckDB => {
12297                    // Snowflake REGEXP_INSTR(s, p[, pos[, occ[, option[, flags[, group]]]]]) ->
12298                    // DuckDB: CASE WHEN s IS NULL OR p IS NULL [OR ...] THEN NULL
12299                    //              WHEN p = '' THEN 0
12300                    //              WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
12301                    //              ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(eff_s, eff_p)[1:occ], x -> LENGTH(x))), 0)
12302                    //                     + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(eff_s, eff_p)[1:occ - 1], x -> LENGTH(x))), 0)
12303                    //                     + pos_offset
12304                    //         END
12305                    if let Expression::Function(f) = e {
12306                        let mut args = f.args;
12307                        let subject = args.remove(0);
12308                        let pattern = if !args.is_empty() {
12309                            args.remove(0)
12310                        } else {
12311                            Expression::Literal(Box::new(Literal::String(String::new())))
12312                        };
12313
12314                        // Collect all original args for NULL checks
12315                        let position = if !args.is_empty() {
12316                            Some(args.remove(0))
12317                        } else {
12318                            None
12319                        };
12320                        let occurrence = if !args.is_empty() {
12321                            Some(args.remove(0))
12322                        } else {
12323                            None
12324                        };
12325                        let option = if !args.is_empty() {
12326                            Some(args.remove(0))
12327                        } else {
12328                            None
12329                        };
12330                        let flags = if !args.is_empty() {
12331                            Some(args.remove(0))
12332                        } else {
12333                            None
12334                        };
12335                        let _group = if !args.is_empty() {
12336                            Some(args.remove(0))
12337                        } else {
12338                            None
12339                        };
12340
12341                        let is_pos_1 = position.as_ref().map_or(true, |p| matches!(p, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1")));
12342                        let occurrence_expr = occurrence.clone().unwrap_or(Expression::number(1));
12343
12344                        // Build NULL check: subject IS NULL OR pattern IS NULL [OR pos IS NULL ...]
12345                        let mut null_checks: Vec<Expression> = vec![
12346                            Expression::Is(Box::new(BinaryOp::new(
12347                                subject.clone(),
12348                                Expression::Null(Null),
12349                            ))),
12350                            Expression::Is(Box::new(BinaryOp::new(
12351                                pattern.clone(),
12352                                Expression::Null(Null),
12353                            ))),
12354                        ];
12355                        // Add NULL checks for all provided optional args
12356                        for opt_arg in [&position, &occurrence, &option, &flags].iter() {
12357                            if let Some(arg) = opt_arg {
12358                                null_checks.push(Expression::Is(Box::new(BinaryOp::new(
12359                                    (*arg).clone(),
12360                                    Expression::Null(Null),
12361                                ))));
12362                            }
12363                        }
12364                        // Chain with OR
12365                        let null_condition = null_checks
12366                            .into_iter()
12367                            .reduce(|a, b| Expression::Or(Box::new(BinaryOp::new(a, b))))
12368                            .unwrap();
12369
12370                        // Effective subject (apply position offset)
12371                        let effective_subject = if is_pos_1 {
12372                            subject.clone()
12373                        } else {
12374                            let pos = position.clone().unwrap_or(Expression::number(1));
12375                            Expression::Function(Box::new(Function::new(
12376                                "SUBSTRING".to_string(),
12377                                vec![subject.clone(), pos],
12378                            )))
12379                        };
12380
12381                        // Effective pattern (apply flags if present)
12382                        let effective_pattern = if let Some(ref fl) = flags {
12383                            if let Expression::Literal(lit) = fl {
12384                                if let Literal::String(f_str) = lit.as_ref() {
12385                                    if !f_str.is_empty() {
12386                                        let prefix = Expression::Literal(Box::new(
12387                                            Literal::String(format!("(?{})", f_str)),
12388                                        ));
12389                                        Expression::DPipe(Box::new(crate::expressions::DPipe {
12390                                            this: Box::new(prefix),
12391                                            expression: Box::new(pattern.clone()),
12392                                            safe: None,
12393                                        }))
12394                                    } else {
12395                                        pattern.clone()
12396                                    }
12397                                } else {
12398                                    fl.clone()
12399                                }
12400                            } else {
12401                                pattern.clone()
12402                            }
12403                        } else {
12404                            pattern.clone()
12405                        };
12406
12407                        // WHEN pattern = '' THEN 0
12408                        let empty_pattern_check = Expression::Eq(Box::new(BinaryOp::new(
12409                            effective_pattern.clone(),
12410                            Expression::Literal(Box::new(Literal::String(String::new()))),
12411                        )));
12412
12413                        // WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
12414                        let match_count_check = Expression::Lt(Box::new(BinaryOp::new(
12415                            Expression::Length(Box::new(crate::expressions::UnaryFunc {
12416                                this: Expression::Function(Box::new(Function::new(
12417                                    "REGEXP_EXTRACT_ALL".to_string(),
12418                                    vec![effective_subject.clone(), effective_pattern.clone()],
12419                                ))),
12420                                original_name: None,
12421                                inferred_type: None,
12422                            })),
12423                            occurrence_expr.clone(),
12424                        )));
12425
12426                        // Helper: build LENGTH lambda for LIST_TRANSFORM
12427                        let make_len_lambda = || {
12428                            Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
12429                                parameters: vec![crate::expressions::Identifier::new("x")],
12430                                body: Expression::Length(Box::new(crate::expressions::UnaryFunc {
12431                                    this: Expression::Identifier(
12432                                        crate::expressions::Identifier::new("x"),
12433                                    ),
12434                                    original_name: None,
12435                                    inferred_type: None,
12436                                })),
12437                                colon: false,
12438                                parameter_types: vec![],
12439                            }))
12440                        };
12441
12442                        // COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(s, p)[1:occ], x -> LENGTH(x))), 0)
12443                        let split_sliced =
12444                            Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
12445                                this: Expression::Function(Box::new(Function::new(
12446                                    "STRING_SPLIT_REGEX".to_string(),
12447                                    vec![effective_subject.clone(), effective_pattern.clone()],
12448                                ))),
12449                                start: Some(Expression::number(1)),
12450                                end: Some(occurrence_expr.clone()),
12451                            }));
12452                        let split_sum = Expression::Function(Box::new(Function::new(
12453                            "COALESCE".to_string(),
12454                            vec![
12455                                Expression::Function(Box::new(Function::new(
12456                                    "LIST_SUM".to_string(),
12457                                    vec![Expression::Function(Box::new(Function::new(
12458                                        "LIST_TRANSFORM".to_string(),
12459                                        vec![split_sliced, make_len_lambda()],
12460                                    )))],
12461                                ))),
12462                                Expression::number(0),
12463                            ],
12464                        )));
12465
12466                        // COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(s, p)[1:occ - 1], x -> LENGTH(x))), 0)
12467                        let extract_sliced =
12468                            Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
12469                                this: Expression::Function(Box::new(Function::new(
12470                                    "REGEXP_EXTRACT_ALL".to_string(),
12471                                    vec![effective_subject.clone(), effective_pattern.clone()],
12472                                ))),
12473                                start: Some(Expression::number(1)),
12474                                end: Some(Expression::Sub(Box::new(BinaryOp::new(
12475                                    occurrence_expr.clone(),
12476                                    Expression::number(1),
12477                                )))),
12478                            }));
12479                        let extract_sum = Expression::Function(Box::new(Function::new(
12480                            "COALESCE".to_string(),
12481                            vec![
12482                                Expression::Function(Box::new(Function::new(
12483                                    "LIST_SUM".to_string(),
12484                                    vec![Expression::Function(Box::new(Function::new(
12485                                        "LIST_TRANSFORM".to_string(),
12486                                        vec![extract_sliced, make_len_lambda()],
12487                                    )))],
12488                                ))),
12489                                Expression::number(0),
12490                            ],
12491                        )));
12492
12493                        // Position offset: pos - 1 when pos > 1, else 0
12494                        let pos_offset: Expression = if !is_pos_1 {
12495                            let pos = position.clone().unwrap_or(Expression::number(1));
12496                            Expression::Sub(Box::new(BinaryOp::new(pos, Expression::number(1))))
12497                        } else {
12498                            Expression::number(0)
12499                        };
12500
12501                        // ELSE: 1 + split_sum + extract_sum + pos_offset
12502                        let else_expr = Expression::Add(Box::new(BinaryOp::new(
12503                            Expression::Add(Box::new(BinaryOp::new(
12504                                Expression::Add(Box::new(BinaryOp::new(
12505                                    Expression::number(1),
12506                                    split_sum,
12507                                ))),
12508                                extract_sum,
12509                            ))),
12510                            pos_offset,
12511                        )));
12512
12513                        Ok(Expression::Case(Box::new(Case {
12514                            operand: None,
12515                            whens: vec![
12516                                (null_condition, Expression::Null(Null)),
12517                                (empty_pattern_check, Expression::number(0)),
12518                                (match_count_check, Expression::number(0)),
12519                            ],
12520                            else_: Some(else_expr),
12521                            comments: vec![],
12522                            inferred_type: None,
12523                        })))
12524                    } else {
12525                        Ok(e)
12526                    }
12527                }
12528
12529                Action::RlikeSnowflakeToDuckDB => {
12530                    // Snowflake RLIKE(a, b[, flags]) -> DuckDB REGEXP_FULL_MATCH(a, b[, flags])
12531                    // Both do full-string matching, so no anchoring needed
12532                    let (subject, pattern, flags) = match e {
12533                        Expression::RegexpLike(ref rl) => {
12534                            (rl.this.clone(), rl.pattern.clone(), rl.flags.clone())
12535                        }
12536                        Expression::Function(ref f) if f.args.len() >= 2 => {
12537                            let s = f.args[0].clone();
12538                            let p = f.args[1].clone();
12539                            let fl = f.args.get(2).cloned();
12540                            (s, p, fl)
12541                        }
12542                        _ => return Ok(e),
12543                    };
12544
12545                    let mut result_args = vec![subject, pattern];
12546                    if let Some(fl) = flags {
12547                        result_args.push(fl);
12548                    }
12549                    Ok(Expression::Function(Box::new(Function::new(
12550                        "REGEXP_FULL_MATCH".to_string(),
12551                        result_args,
12552                    ))))
12553                }
12554
12555                Action::RegexpExtractAllToSnowflake => {
12556                    // BigQuery REGEXP_EXTRACT_ALL(s, p) -> Snowflake REGEXP_SUBSTR_ALL(s, p)
12557                    // With capture group: REGEXP_SUBSTR_ALL(s, p, 1, 1, 'c', 1)
12558                    if let Expression::Function(f) = e {
12559                        let mut args = f.args;
12560                        if args.len() >= 2 {
12561                            let str_expr = args.remove(0);
12562                            let pattern = args.remove(0);
12563
12564                            let has_groups = match &pattern {
12565                                Expression::Literal(lit)
12566                                    if matches!(lit.as_ref(), Literal::String(_)) =>
12567                                {
12568                                    let Literal::String(s) = lit.as_ref() else {
12569                                        unreachable!()
12570                                    };
12571                                    s.contains('(') && s.contains(')')
12572                                }
12573                                _ => false,
12574                            };
12575
12576                            if has_groups {
12577                                Ok(Expression::Function(Box::new(Function::new(
12578                                    "REGEXP_SUBSTR_ALL".to_string(),
12579                                    vec![
12580                                        str_expr,
12581                                        pattern,
12582                                        Expression::number(1),
12583                                        Expression::number(1),
12584                                        Expression::Literal(Box::new(Literal::String(
12585                                            "c".to_string(),
12586                                        ))),
12587                                        Expression::number(1),
12588                                    ],
12589                                ))))
12590                            } else {
12591                                Ok(Expression::Function(Box::new(Function::new(
12592                                    "REGEXP_SUBSTR_ALL".to_string(),
12593                                    vec![str_expr, pattern],
12594                                ))))
12595                            }
12596                        } else {
12597                            Ok(Expression::Function(Box::new(Function::new(
12598                                "REGEXP_SUBSTR_ALL".to_string(),
12599                                args,
12600                            ))))
12601                        }
12602                    } else {
12603                        Ok(e)
12604                    }
12605                }
12606
12607                Action::SetToVariable => {
12608                    // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
12609                    if let Expression::SetStatement(mut s) = e {
12610                        for item in &mut s.items {
12611                            if item.kind.is_none() {
12612                                // Check if name already has VARIABLE prefix (from DuckDB source parsing)
12613                                let already_variable = match &item.name {
12614                                    Expression::Identifier(id) => id.name.starts_with("VARIABLE "),
12615                                    _ => false,
12616                                };
12617                                if already_variable {
12618                                    // Extract the actual name and set kind
12619                                    if let Expression::Identifier(ref mut id) = item.name {
12620                                        let actual_name = id.name["VARIABLE ".len()..].to_string();
12621                                        id.name = actual_name;
12622                                    }
12623                                }
12624                                item.kind = Some("VARIABLE".to_string());
12625                            }
12626                        }
12627                        Ok(Expression::SetStatement(s))
12628                    } else {
12629                        Ok(e)
12630                    }
12631                }
12632
12633                Action::ConvertTimezoneToExpr => {
12634                    // Convert Function("CONVERT_TIMEZONE", args) to Expression::ConvertTimezone
12635                    // This prevents Redshift's transform_expr from expanding 2-arg to 3-arg with 'UTC'
12636                    if let Expression::Function(f) = e {
12637                        if f.args.len() == 2 {
12638                            let mut args = f.args;
12639                            let target_tz = args.remove(0);
12640                            let timestamp = args.remove(0);
12641                            Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
12642                                source_tz: None,
12643                                target_tz: Some(Box::new(target_tz)),
12644                                timestamp: Some(Box::new(timestamp)),
12645                                options: vec![],
12646                            })))
12647                        } else if f.args.len() == 3 {
12648                            let mut args = f.args;
12649                            let source_tz = args.remove(0);
12650                            let target_tz = args.remove(0);
12651                            let timestamp = args.remove(0);
12652                            Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
12653                                source_tz: Some(Box::new(source_tz)),
12654                                target_tz: Some(Box::new(target_tz)),
12655                                timestamp: Some(Box::new(timestamp)),
12656                                options: vec![],
12657                            })))
12658                        } else {
12659                            Ok(Expression::Function(f))
12660                        }
12661                    } else {
12662                        Ok(e)
12663                    }
12664                }
12665
12666                Action::BigQueryCastType => {
12667                    // Convert BigQuery types to standard SQL types
12668                    if let Expression::DataType(dt) = e {
12669                        match dt {
12670                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("INT64") => {
12671                                Ok(Expression::DataType(DataType::BigInt { length: None }))
12672                            }
12673                            DataType::Custom { ref name }
12674                                if name.eq_ignore_ascii_case("FLOAT64") =>
12675                            {
12676                                Ok(Expression::DataType(DataType::Double {
12677                                    precision: None,
12678                                    scale: None,
12679                                }))
12680                            }
12681                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("BOOL") => {
12682                                Ok(Expression::DataType(DataType::Boolean))
12683                            }
12684                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("BYTES") => {
12685                                Ok(Expression::DataType(DataType::VarBinary { length: None }))
12686                            }
12687                            DataType::Custom { ref name }
12688                                if name.eq_ignore_ascii_case("NUMERIC") =>
12689                            {
12690                                // For DuckDB target, use Custom("DECIMAL") to avoid DuckDB's
12691                                // default precision (18, 3) being added to bare DECIMAL
12692                                if matches!(target, DialectType::DuckDB) {
12693                                    Ok(Expression::DataType(DataType::Custom {
12694                                        name: "DECIMAL".to_string(),
12695                                    }))
12696                                } else {
12697                                    Ok(Expression::DataType(DataType::Decimal {
12698                                        precision: None,
12699                                        scale: None,
12700                                    }))
12701                                }
12702                            }
12703                            DataType::Custom { ref name }
12704                                if name.eq_ignore_ascii_case("STRING") =>
12705                            {
12706                                Ok(Expression::DataType(DataType::String { length: None }))
12707                            }
12708                            DataType::Custom { ref name }
12709                                if name.eq_ignore_ascii_case("DATETIME") =>
12710                            {
12711                                Ok(Expression::DataType(DataType::Timestamp {
12712                                    precision: None,
12713                                    timezone: false,
12714                                }))
12715                            }
12716                            _ => Ok(Expression::DataType(dt)),
12717                        }
12718                    } else {
12719                        Ok(e)
12720                    }
12721                }
12722
12723                Action::BigQuerySafeDivide => {
12724                    // Convert SafeDivide expression to IF/CASE form for most targets
12725                    if let Expression::SafeDivide(sd) = e {
12726                        let x = *sd.this;
12727                        let y = *sd.expression;
12728                        // Wrap x and y in parens if they're complex expressions
12729                        let y_ref = match &y {
12730                            Expression::Column(_)
12731                            | Expression::Literal(_)
12732                            | Expression::Identifier(_) => y.clone(),
12733                            _ => Expression::Paren(Box::new(Paren {
12734                                this: y.clone(),
12735                                trailing_comments: vec![],
12736                            })),
12737                        };
12738                        let x_ref = match &x {
12739                            Expression::Column(_)
12740                            | Expression::Literal(_)
12741                            | Expression::Identifier(_) => x.clone(),
12742                            _ => Expression::Paren(Box::new(Paren {
12743                                this: x.clone(),
12744                                trailing_comments: vec![],
12745                            })),
12746                        };
12747                        let condition = Expression::Neq(Box::new(BinaryOp::new(
12748                            y_ref.clone(),
12749                            Expression::number(0),
12750                        )));
12751                        let div_expr = Expression::Div(Box::new(BinaryOp::new(x_ref, y_ref)));
12752
12753                        if matches!(target, DialectType::Spark | DialectType::Databricks) {
12754                            Ok(Expression::Function(Box::new(Function::new(
12755                                "TRY_DIVIDE".to_string(),
12756                                vec![x, y],
12757                            ))))
12758                        } else if matches!(target, DialectType::Presto | DialectType::Trino) {
12759                            // Presto/Trino: IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
12760                            let cast_x = Expression::Cast(Box::new(Cast {
12761                                this: match &x {
12762                                    Expression::Column(_)
12763                                    | Expression::Literal(_)
12764                                    | Expression::Identifier(_) => x,
12765                                    _ => Expression::Paren(Box::new(Paren {
12766                                        this: x,
12767                                        trailing_comments: vec![],
12768                                    })),
12769                                },
12770                                to: DataType::Double {
12771                                    precision: None,
12772                                    scale: None,
12773                                },
12774                                trailing_comments: vec![],
12775                                double_colon_syntax: false,
12776                                format: None,
12777                                default: None,
12778                                inferred_type: None,
12779                            }));
12780                            let cast_div = Expression::Div(Box::new(BinaryOp::new(
12781                                cast_x,
12782                                match &y {
12783                                    Expression::Column(_)
12784                                    | Expression::Literal(_)
12785                                    | Expression::Identifier(_) => y,
12786                                    _ => Expression::Paren(Box::new(Paren {
12787                                        this: y,
12788                                        trailing_comments: vec![],
12789                                    })),
12790                                },
12791                            )));
12792                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
12793                                condition,
12794                                true_value: cast_div,
12795                                false_value: Some(Expression::Null(Null)),
12796                                original_name: None,
12797                                inferred_type: None,
12798                            })))
12799                        } else if matches!(target, DialectType::PostgreSQL) {
12800                            // PostgreSQL: CASE WHEN y <> 0 THEN CAST(x AS DOUBLE PRECISION) / y ELSE NULL END
12801                            let cast_x = Expression::Cast(Box::new(Cast {
12802                                this: match &x {
12803                                    Expression::Column(_)
12804                                    | Expression::Literal(_)
12805                                    | Expression::Identifier(_) => x,
12806                                    _ => Expression::Paren(Box::new(Paren {
12807                                        this: x,
12808                                        trailing_comments: vec![],
12809                                    })),
12810                                },
12811                                to: DataType::Custom {
12812                                    name: "DOUBLE PRECISION".to_string(),
12813                                },
12814                                trailing_comments: vec![],
12815                                double_colon_syntax: false,
12816                                format: None,
12817                                default: None,
12818                                inferred_type: None,
12819                            }));
12820                            let y_paren = match &y {
12821                                Expression::Column(_)
12822                                | Expression::Literal(_)
12823                                | Expression::Identifier(_) => y,
12824                                _ => Expression::Paren(Box::new(Paren {
12825                                    this: y,
12826                                    trailing_comments: vec![],
12827                                })),
12828                            };
12829                            let cast_div =
12830                                Expression::Div(Box::new(BinaryOp::new(cast_x, y_paren)));
12831                            Ok(Expression::Case(Box::new(Case {
12832                                operand: None,
12833                                whens: vec![(condition, cast_div)],
12834                                else_: Some(Expression::Null(Null)),
12835                                comments: Vec::new(),
12836                                inferred_type: None,
12837                            })))
12838                        } else if matches!(target, DialectType::DuckDB) {
12839                            // DuckDB: CASE WHEN y <> 0 THEN x / y ELSE NULL END
12840                            Ok(Expression::Case(Box::new(Case {
12841                                operand: None,
12842                                whens: vec![(condition, div_expr)],
12843                                else_: Some(Expression::Null(Null)),
12844                                comments: Vec::new(),
12845                                inferred_type: None,
12846                            })))
12847                        } else if matches!(target, DialectType::Snowflake) {
12848                            // Snowflake: IFF(y <> 0, x / y, NULL)
12849                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
12850                                condition,
12851                                true_value: div_expr,
12852                                false_value: Some(Expression::Null(Null)),
12853                                original_name: Some("IFF".to_string()),
12854                                inferred_type: None,
12855                            })))
12856                        } else {
12857                            // All others: IF(y <> 0, x / y, NULL)
12858                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
12859                                condition,
12860                                true_value: div_expr,
12861                                false_value: Some(Expression::Null(Null)),
12862                                original_name: None,
12863                                inferred_type: None,
12864                            })))
12865                        }
12866                    } else {
12867                        Ok(e)
12868                    }
12869                }
12870
12871                Action::BigQueryLastDayStripUnit => {
12872                    if let Expression::LastDay(mut ld) = e {
12873                        ld.unit = None; // Strip the unit (MONTH is default)
12874                        match target {
12875                            DialectType::PostgreSQL => {
12876                                // LAST_DAY(date) -> CAST(DATE_TRUNC('MONTH', date) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
12877                                let date_trunc = Expression::Function(Box::new(Function::new(
12878                                    "DATE_TRUNC".to_string(),
12879                                    vec![
12880                                        Expression::Literal(Box::new(
12881                                            crate::expressions::Literal::String(
12882                                                "MONTH".to_string(),
12883                                            ),
12884                                        )),
12885                                        ld.this.clone(),
12886                                    ],
12887                                )));
12888                                let plus_month =
12889                                    Expression::Add(Box::new(crate::expressions::BinaryOp::new(
12890                                        date_trunc,
12891                                        Expression::Interval(Box::new(
12892                                            crate::expressions::Interval {
12893                                                this: Some(Expression::Literal(Box::new(
12894                                                    crate::expressions::Literal::String(
12895                                                        "1 MONTH".to_string(),
12896                                                    ),
12897                                                ))),
12898                                                unit: None,
12899                                            },
12900                                        )),
12901                                    )));
12902                                let minus_day =
12903                                    Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
12904                                        plus_month,
12905                                        Expression::Interval(Box::new(
12906                                            crate::expressions::Interval {
12907                                                this: Some(Expression::Literal(Box::new(
12908                                                    crate::expressions::Literal::String(
12909                                                        "1 DAY".to_string(),
12910                                                    ),
12911                                                ))),
12912                                                unit: None,
12913                                            },
12914                                        )),
12915                                    )));
12916                                Ok(Expression::Cast(Box::new(Cast {
12917                                    this: minus_day,
12918                                    to: DataType::Date,
12919                                    trailing_comments: vec![],
12920                                    double_colon_syntax: false,
12921                                    format: None,
12922                                    default: None,
12923                                    inferred_type: None,
12924                                })))
12925                            }
12926                            DialectType::Presto => {
12927                                // LAST_DAY(date) -> LAST_DAY_OF_MONTH(date)
12928                                Ok(Expression::Function(Box::new(Function::new(
12929                                    "LAST_DAY_OF_MONTH".to_string(),
12930                                    vec![ld.this],
12931                                ))))
12932                            }
12933                            DialectType::ClickHouse => {
12934                                // ClickHouse LAST_DAY(CAST(x AS Nullable(DATE)))
12935                                // Need to wrap the DATE type in Nullable
12936                                let nullable_date = match ld.this {
12937                                    Expression::Cast(mut c) => {
12938                                        c.to = DataType::Nullable {
12939                                            inner: Box::new(DataType::Date),
12940                                        };
12941                                        Expression::Cast(c)
12942                                    }
12943                                    other => other,
12944                                };
12945                                ld.this = nullable_date;
12946                                Ok(Expression::LastDay(ld))
12947                            }
12948                            _ => Ok(Expression::LastDay(ld)),
12949                        }
12950                    } else {
12951                        Ok(e)
12952                    }
12953                }
12954
12955                Action::BigQueryCastFormat => {
12956                    // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE('%m/%d/%Y', x) for BigQuery
12957                    // CAST(x AS TIMESTAMP FORMAT 'fmt') -> PARSE_TIMESTAMP(...) for BigQuery
12958                    // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, ...) AS DATE) for DuckDB
12959                    let (this, to, format_expr, is_safe) = match e {
12960                        Expression::Cast(ref c) if c.format.is_some() => (
12961                            c.this.clone(),
12962                            c.to.clone(),
12963                            c.format.as_ref().unwrap().as_ref().clone(),
12964                            false,
12965                        ),
12966                        Expression::SafeCast(ref c) if c.format.is_some() => (
12967                            c.this.clone(),
12968                            c.to.clone(),
12969                            c.format.as_ref().unwrap().as_ref().clone(),
12970                            true,
12971                        ),
12972                        _ => return Ok(e),
12973                    };
12974                    // For CAST(x AS STRING FORMAT ...) when target is BigQuery, keep as-is
12975                    if matches!(target, DialectType::BigQuery) {
12976                        match &to {
12977                            DataType::String { .. } | DataType::VarChar { .. } | DataType::Text => {
12978                                // CAST(x AS STRING FORMAT 'fmt') stays as CAST expression for BigQuery
12979                                return Ok(e);
12980                            }
12981                            _ => {}
12982                        }
12983                    }
12984                    // Extract timezone from format if AT TIME ZONE is present
12985                    let (actual_format_expr, timezone) = match &format_expr {
12986                        Expression::AtTimeZone(ref atz) => {
12987                            (atz.this.clone(), Some(atz.zone.clone()))
12988                        }
12989                        _ => (format_expr.clone(), None),
12990                    };
12991                    let strftime_fmt = Self::bq_cast_format_to_strftime(&actual_format_expr);
12992                    match target {
12993                        DialectType::BigQuery => {
12994                            // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE(strftime_fmt, x)
12995                            // CAST(x AS TIMESTAMP FORMAT 'fmt' AT TIME ZONE 'tz') -> PARSE_TIMESTAMP(strftime_fmt, x, tz)
12996                            let func_name = match &to {
12997                                DataType::Date => "PARSE_DATE",
12998                                DataType::Timestamp { .. } => "PARSE_TIMESTAMP",
12999                                DataType::Time { .. } => "PARSE_TIMESTAMP",
13000                                _ => "PARSE_TIMESTAMP",
13001                            };
13002                            let mut func_args = vec![strftime_fmt, this];
13003                            if let Some(tz) = timezone {
13004                                func_args.push(tz);
13005                            }
13006                            Ok(Expression::Function(Box::new(Function::new(
13007                                func_name.to_string(),
13008                                func_args,
13009                            ))))
13010                        }
13011                        DialectType::DuckDB => {
13012                            // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, fmt) AS DATE)
13013                            // CAST(x AS DATE FORMAT 'fmt') -> CAST(STRPTIME(x, fmt) AS DATE)
13014                            let duck_fmt = Self::bq_format_to_duckdb(&strftime_fmt);
13015                            let parse_fn_name = if is_safe { "TRY_STRPTIME" } else { "STRPTIME" };
13016                            let parse_call = Expression::Function(Box::new(Function::new(
13017                                parse_fn_name.to_string(),
13018                                vec![this, duck_fmt],
13019                            )));
13020                            Ok(Expression::Cast(Box::new(Cast {
13021                                this: parse_call,
13022                                to,
13023                                trailing_comments: vec![],
13024                                double_colon_syntax: false,
13025                                format: None,
13026                                default: None,
13027                                inferred_type: None,
13028                            })))
13029                        }
13030                        _ => Ok(e),
13031                    }
13032                }
13033
13034                Action::BigQueryFunctionNormalize => {
13035                    Self::normalize_bigquery_function(e, source, target)
13036                }
13037
13038                Action::BigQueryToHexBare => {
13039                    // Not used anymore - handled directly in normalize_bigquery_function
13040                    Ok(e)
13041                }
13042
13043                Action::BigQueryToHexLower => {
13044                    if let Expression::Lower(uf) = e {
13045                        match uf.this {
13046                            // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
13047                            Expression::Function(f)
13048                                if matches!(target, DialectType::BigQuery)
13049                                    && f.name == "TO_HEX" =>
13050                            {
13051                                Ok(Expression::Function(f))
13052                            }
13053                            // LOWER(LOWER(HEX/TO_HEX(x))) patterns
13054                            Expression::Lower(inner_uf) => {
13055                                if matches!(target, DialectType::BigQuery) {
13056                                    // BQ->BQ: extract TO_HEX
13057                                    if let Expression::Function(f) = inner_uf.this {
13058                                        Ok(Expression::Function(Box::new(Function::new(
13059                                            "TO_HEX".to_string(),
13060                                            f.args,
13061                                        ))))
13062                                    } else {
13063                                        Ok(Expression::Lower(inner_uf))
13064                                    }
13065                                } else {
13066                                    // Flatten: LOWER(LOWER(x)) -> LOWER(x)
13067                                    Ok(Expression::Lower(inner_uf))
13068                                }
13069                            }
13070                            other => {
13071                                Ok(Expression::Lower(Box::new(crate::expressions::UnaryFunc {
13072                                    this: other,
13073                                    original_name: None,
13074                                    inferred_type: None,
13075                                })))
13076                            }
13077                        }
13078                    } else {
13079                        Ok(e)
13080                    }
13081                }
13082
13083                Action::BigQueryToHexUpper => {
13084                    // UPPER(LOWER(HEX(x))) -> HEX(x) (UPPER cancels LOWER, HEX is already uppercase)
13085                    // UPPER(LOWER(TO_HEX(x))) -> TO_HEX(x) for Presto/Trino
13086                    if let Expression::Upper(uf) = e {
13087                        if let Expression::Lower(inner_uf) = uf.this {
13088                            // For BQ->BQ: UPPER(TO_HEX(x)) should stay as UPPER(TO_HEX(x))
13089                            if matches!(target, DialectType::BigQuery) {
13090                                // Restore TO_HEX name in inner function
13091                                if let Expression::Function(f) = inner_uf.this {
13092                                    let restored = Expression::Function(Box::new(Function::new(
13093                                        "TO_HEX".to_string(),
13094                                        f.args,
13095                                    )));
13096                                    Ok(Expression::Upper(Box::new(
13097                                        crate::expressions::UnaryFunc::new(restored),
13098                                    )))
13099                                } else {
13100                                    Ok(Expression::Upper(inner_uf))
13101                                }
13102                            } else {
13103                                // Extract the inner HEX/TO_HEX function (UPPER(LOWER(x)) = x when HEX is uppercase)
13104                                Ok(inner_uf.this)
13105                            }
13106                        } else {
13107                            Ok(Expression::Upper(uf))
13108                        }
13109                    } else {
13110                        Ok(e)
13111                    }
13112                }
13113
13114                Action::BigQueryAnyValueHaving => {
13115                    // ANY_VALUE(x HAVING MAX y) -> ARG_MAX_NULL(x, y)
13116                    // ANY_VALUE(x HAVING MIN y) -> ARG_MIN_NULL(x, y)
13117                    if let Expression::AnyValue(agg) = e {
13118                        if let Some((having_expr, is_max)) = agg.having_max {
13119                            let func_name = if is_max {
13120                                "ARG_MAX_NULL"
13121                            } else {
13122                                "ARG_MIN_NULL"
13123                            };
13124                            Ok(Expression::Function(Box::new(Function::new(
13125                                func_name.to_string(),
13126                                vec![agg.this, *having_expr],
13127                            ))))
13128                        } else {
13129                            Ok(Expression::AnyValue(agg))
13130                        }
13131                    } else {
13132                        Ok(e)
13133                    }
13134                }
13135
13136                Action::BigQueryApproxQuantiles => {
13137                    // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [0, 1/n, 2/n, ..., 1])
13138                    // APPROX_QUANTILES(DISTINCT x, n) -> APPROX_QUANTILE(DISTINCT x, [0, 1/n, ..., 1])
13139                    if let Expression::AggregateFunction(agg) = e {
13140                        if agg.args.len() >= 2 {
13141                            let x_expr = agg.args[0].clone();
13142                            let n_expr = &agg.args[1];
13143
13144                            // Extract the numeric value from n_expr
13145                            let n = match n_expr {
13146                                Expression::Literal(lit)
13147                                    if matches!(
13148                                        lit.as_ref(),
13149                                        crate::expressions::Literal::Number(_)
13150                                    ) =>
13151                                {
13152                                    let crate::expressions::Literal::Number(s) = lit.as_ref()
13153                                    else {
13154                                        unreachable!()
13155                                    };
13156                                    s.parse::<usize>().unwrap_or(2)
13157                                }
13158                                _ => 2,
13159                            };
13160
13161                            // Generate quantile array: [0, 1/n, 2/n, ..., 1]
13162                            let mut quantiles = Vec::new();
13163                            for i in 0..=n {
13164                                let q = i as f64 / n as f64;
13165                                // Format nicely: 0 -> 0, 0.25 -> 0.25, 1 -> 1
13166                                if q == 0.0 {
13167                                    quantiles.push(Expression::number(0));
13168                                } else if q == 1.0 {
13169                                    quantiles.push(Expression::number(1));
13170                                } else {
13171                                    quantiles.push(Expression::Literal(Box::new(
13172                                        crate::expressions::Literal::Number(format!("{}", q)),
13173                                    )));
13174                                }
13175                            }
13176
13177                            let array_expr =
13178                                Expression::Array(Box::new(crate::expressions::Array {
13179                                    expressions: quantiles,
13180                                }));
13181
13182                            // Preserve DISTINCT modifier
13183                            let mut new_func = Function::new(
13184                                "APPROX_QUANTILE".to_string(),
13185                                vec![x_expr, array_expr],
13186                            );
13187                            new_func.distinct = agg.distinct;
13188                            Ok(Expression::Function(Box::new(new_func)))
13189                        } else {
13190                            Ok(Expression::AggregateFunction(agg))
13191                        }
13192                    } else {
13193                        Ok(e)
13194                    }
13195                }
13196
13197                Action::GenericFunctionNormalize => {
13198                    // Helper closure to convert ARBITRARY to target-specific function
13199                    fn convert_arbitrary(arg: Expression, target: DialectType) -> Expression {
13200                        let name = match target {
13201                            DialectType::ClickHouse => "any",
13202                            DialectType::TSQL | DialectType::SQLite => "MAX",
13203                            DialectType::Hive => "FIRST",
13204                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
13205                                "ARBITRARY"
13206                            }
13207                            _ => "ANY_VALUE",
13208                        };
13209                        Expression::Function(Box::new(Function::new(name.to_string(), vec![arg])))
13210                    }
13211
13212                    if let Expression::Function(f) = e {
13213                        let name = f.name.to_ascii_uppercase();
13214                        match name.as_str() {
13215                            "ARBITRARY" if f.args.len() == 1 => {
13216                                let arg = f.args.into_iter().next().unwrap();
13217                                Ok(convert_arbitrary(arg, target))
13218                            }
13219                            "TO_NUMBER" if f.args.len() == 1 => {
13220                                let arg = f.args.into_iter().next().unwrap();
13221                                match target {
13222                                    DialectType::Oracle | DialectType::Snowflake => {
13223                                        Ok(Expression::Function(Box::new(Function::new(
13224                                            "TO_NUMBER".to_string(),
13225                                            vec![arg],
13226                                        ))))
13227                                    }
13228                                    _ => Ok(Expression::Cast(Box::new(crate::expressions::Cast {
13229                                        this: arg,
13230                                        to: crate::expressions::DataType::Double {
13231                                            precision: None,
13232                                            scale: None,
13233                                        },
13234                                        double_colon_syntax: false,
13235                                        trailing_comments: Vec::new(),
13236                                        format: None,
13237                                        default: None,
13238                                        inferred_type: None,
13239                                    }))),
13240                                }
13241                            }
13242                            "AGGREGATE" if f.args.len() >= 3 => match target {
13243                                DialectType::DuckDB
13244                                | DialectType::Hive
13245                                | DialectType::Presto
13246                                | DialectType::Trino => Ok(Expression::Function(Box::new(
13247                                    Function::new("REDUCE".to_string(), f.args),
13248                                ))),
13249                                _ => Ok(Expression::Function(f)),
13250                            },
13251                            // REGEXP_MATCHES(x, y) -> RegexpLike for most targets, keep as-is for DuckDB
13252                            "REGEXP_MATCHES" if f.args.len() >= 2 => {
13253                                if matches!(target, DialectType::DuckDB) {
13254                                    Ok(Expression::Function(f))
13255                                } else {
13256                                    let mut args = f.args;
13257                                    let this = args.remove(0);
13258                                    let pattern = args.remove(0);
13259                                    let flags = if args.is_empty() {
13260                                        None
13261                                    } else {
13262                                        Some(args.remove(0))
13263                                    };
13264                                    Ok(Expression::RegexpLike(Box::new(
13265                                        crate::expressions::RegexpFunc {
13266                                            this,
13267                                            pattern,
13268                                            flags,
13269                                        },
13270                                    )))
13271                                }
13272                            }
13273                            // REGEXP_FULL_MATCH (Hive REGEXP) -> RegexpLike
13274                            "REGEXP_FULL_MATCH" if f.args.len() >= 2 => {
13275                                if matches!(target, DialectType::DuckDB) {
13276                                    Ok(Expression::Function(f))
13277                                } else {
13278                                    let mut args = f.args;
13279                                    let this = args.remove(0);
13280                                    let pattern = args.remove(0);
13281                                    let flags = if args.is_empty() {
13282                                        None
13283                                    } else {
13284                                        Some(args.remove(0))
13285                                    };
13286                                    Ok(Expression::RegexpLike(Box::new(
13287                                        crate::expressions::RegexpFunc {
13288                                            this,
13289                                            pattern,
13290                                            flags,
13291                                        },
13292                                    )))
13293                                }
13294                            }
13295                            // STRUCT_EXTRACT(x, 'field') -> x.field (StructExtract expression)
13296                            "STRUCT_EXTRACT" if f.args.len() == 2 => {
13297                                let mut args = f.args;
13298                                let this = args.remove(0);
13299                                let field_expr = args.remove(0);
13300                                // Extract string literal to get field name
13301                                let field_name = match &field_expr {
13302                                    Expression::Literal(lit)
13303                                        if matches!(
13304                                            lit.as_ref(),
13305                                            crate::expressions::Literal::String(_)
13306                                        ) =>
13307                                    {
13308                                        let crate::expressions::Literal::String(s) = lit.as_ref()
13309                                        else {
13310                                            unreachable!()
13311                                        };
13312                                        s.clone()
13313                                    }
13314                                    Expression::Identifier(id) => id.name.clone(),
13315                                    _ => {
13316                                        return Ok(Expression::Function(Box::new(Function::new(
13317                                            "STRUCT_EXTRACT".to_string(),
13318                                            vec![this, field_expr],
13319                                        ))))
13320                                    }
13321                                };
13322                                Ok(Expression::StructExtract(Box::new(
13323                                    crate::expressions::StructExtractFunc {
13324                                        this,
13325                                        field: crate::expressions::Identifier::new(field_name),
13326                                    },
13327                                )))
13328                            }
13329                            // LIST_FILTER([4,5,6], x -> x > 4) -> FILTER(ARRAY(4,5,6), x -> x > 4)
13330                            "LIST_FILTER" if f.args.len() == 2 => {
13331                                let name = match target {
13332                                    DialectType::DuckDB => "LIST_FILTER",
13333                                    _ => "FILTER",
13334                                };
13335                                Ok(Expression::Function(Box::new(Function::new(
13336                                    name.to_string(),
13337                                    f.args,
13338                                ))))
13339                            }
13340                            // LIST_TRANSFORM(x, y -> y + 1) -> TRANSFORM(x, y -> y + 1)
13341                            "LIST_TRANSFORM" if f.args.len() == 2 => {
13342                                let name = match target {
13343                                    DialectType::DuckDB => "LIST_TRANSFORM",
13344                                    _ => "TRANSFORM",
13345                                };
13346                                Ok(Expression::Function(Box::new(Function::new(
13347                                    name.to_string(),
13348                                    f.args,
13349                                ))))
13350                            }
13351                            // LIST_SORT(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, SORT_ARRAY(x) for others
13352                            "LIST_SORT" if f.args.len() >= 1 => {
13353                                let name = match target {
13354                                    DialectType::DuckDB => "LIST_SORT",
13355                                    DialectType::Presto | DialectType::Trino => "ARRAY_SORT",
13356                                    _ => "SORT_ARRAY",
13357                                };
13358                                Ok(Expression::Function(Box::new(Function::new(
13359                                    name.to_string(),
13360                                    f.args,
13361                                ))))
13362                            }
13363                            // LIST_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
13364                            "LIST_REVERSE_SORT" if f.args.len() >= 1 => {
13365                                match target {
13366                                    DialectType::DuckDB => Ok(Expression::Function(Box::new(
13367                                        Function::new("ARRAY_REVERSE_SORT".to_string(), f.args),
13368                                    ))),
13369                                    DialectType::Spark
13370                                    | DialectType::Databricks
13371                                    | DialectType::Hive => {
13372                                        let mut args = f.args;
13373                                        args.push(Expression::Identifier(
13374                                            crate::expressions::Identifier::new("FALSE"),
13375                                        ));
13376                                        Ok(Expression::Function(Box::new(Function::new(
13377                                            "SORT_ARRAY".to_string(),
13378                                            args,
13379                                        ))))
13380                                    }
13381                                    DialectType::Presto
13382                                    | DialectType::Trino
13383                                    | DialectType::Athena => {
13384                                        // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
13385                                        let arr = f.args.into_iter().next().unwrap();
13386                                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
13387                                            parameters: vec![
13388                                                crate::expressions::Identifier::new("a"),
13389                                                crate::expressions::Identifier::new("b"),
13390                                            ],
13391                                            body: Expression::Case(Box::new(Case {
13392                                                operand: None,
13393                                                whens: vec![
13394                                                    (
13395                                                        Expression::Lt(Box::new(BinaryOp::new(
13396                                                            Expression::Identifier(crate::expressions::Identifier::new("a")),
13397                                                            Expression::Identifier(crate::expressions::Identifier::new("b")),
13398                                                        ))),
13399                                                        Expression::number(1),
13400                                                    ),
13401                                                    (
13402                                                        Expression::Gt(Box::new(BinaryOp::new(
13403                                                            Expression::Identifier(crate::expressions::Identifier::new("a")),
13404                                                            Expression::Identifier(crate::expressions::Identifier::new("b")),
13405                                                        ))),
13406                                                        Expression::Literal(Box::new(Literal::Number("-1".to_string()))),
13407                                                    ),
13408                                                ],
13409                                                else_: Some(Expression::number(0)),
13410                                                comments: Vec::new(),
13411                                                inferred_type: None,
13412                                            })),
13413                                            colon: false,
13414                                            parameter_types: Vec::new(),
13415                                        }));
13416                                        Ok(Expression::Function(Box::new(Function::new(
13417                                            "ARRAY_SORT".to_string(),
13418                                            vec![arr, lambda],
13419                                        ))))
13420                                    }
13421                                    _ => Ok(Expression::Function(Box::new(Function::new(
13422                                        "LIST_REVERSE_SORT".to_string(),
13423                                        f.args,
13424                                    )))),
13425                                }
13426                            }
13427                            // SPLIT_TO_ARRAY(x) with 1 arg -> add default ',' separator and rename
13428                            "SPLIT_TO_ARRAY" if f.args.len() == 1 => {
13429                                let mut args = f.args;
13430                                args.push(Expression::string(","));
13431                                let name = match target {
13432                                    DialectType::DuckDB => "STR_SPLIT",
13433                                    DialectType::Presto | DialectType::Trino => "SPLIT",
13434                                    DialectType::Spark
13435                                    | DialectType::Databricks
13436                                    | DialectType::Hive => "SPLIT",
13437                                    DialectType::PostgreSQL => "STRING_TO_ARRAY",
13438                                    DialectType::Redshift => "SPLIT_TO_ARRAY",
13439                                    _ => "SPLIT",
13440                                };
13441                                Ok(Expression::Function(Box::new(Function::new(
13442                                    name.to_string(),
13443                                    args,
13444                                ))))
13445                            }
13446                            // SPLIT_TO_ARRAY(x, sep) with 2 args -> rename based on target
13447                            "SPLIT_TO_ARRAY" if f.args.len() == 2 => {
13448                                let name = match target {
13449                                    DialectType::DuckDB => "STR_SPLIT",
13450                                    DialectType::Presto | DialectType::Trino => "SPLIT",
13451                                    DialectType::Spark
13452                                    | DialectType::Databricks
13453                                    | DialectType::Hive => "SPLIT",
13454                                    DialectType::PostgreSQL => "STRING_TO_ARRAY",
13455                                    DialectType::Redshift => "SPLIT_TO_ARRAY",
13456                                    _ => "SPLIT",
13457                                };
13458                                Ok(Expression::Function(Box::new(Function::new(
13459                                    name.to_string(),
13460                                    f.args,
13461                                ))))
13462                            }
13463                            // STRING_TO_ARRAY/STR_SPLIT -> target-specific split function
13464                            "STRING_TO_ARRAY" | "STR_SPLIT" if f.args.len() >= 2 => {
13465                                let name = match target {
13466                                    DialectType::DuckDB => "STR_SPLIT",
13467                                    DialectType::Presto | DialectType::Trino => "SPLIT",
13468                                    DialectType::Spark
13469                                    | DialectType::Databricks
13470                                    | DialectType::Hive => "SPLIT",
13471                                    DialectType::Doris | DialectType::StarRocks => {
13472                                        "SPLIT_BY_STRING"
13473                                    }
13474                                    DialectType::PostgreSQL | DialectType::Redshift => {
13475                                        "STRING_TO_ARRAY"
13476                                    }
13477                                    _ => "SPLIT",
13478                                };
13479                                // For Spark/Hive, SPLIT uses regex - need to escape literal with \Q...\E
13480                                if matches!(
13481                                    target,
13482                                    DialectType::Spark
13483                                        | DialectType::Databricks
13484                                        | DialectType::Hive
13485                                ) {
13486                                    let mut args = f.args;
13487                                    let x = args.remove(0);
13488                                    let sep = args.remove(0);
13489                                    // Wrap separator in CONCAT('\\Q', sep, '\\E')
13490                                    let escaped_sep =
13491                                        Expression::Function(Box::new(Function::new(
13492                                            "CONCAT".to_string(),
13493                                            vec![
13494                                                Expression::string("\\Q"),
13495                                                sep,
13496                                                Expression::string("\\E"),
13497                                            ],
13498                                        )));
13499                                    Ok(Expression::Function(Box::new(Function::new(
13500                                        name.to_string(),
13501                                        vec![x, escaped_sep],
13502                                    ))))
13503                                } else {
13504                                    Ok(Expression::Function(Box::new(Function::new(
13505                                        name.to_string(),
13506                                        f.args,
13507                                    ))))
13508                                }
13509                            }
13510                            // STR_SPLIT_REGEX(x, 'a') / REGEXP_SPLIT(x, 'a') -> target-specific regex split
13511                            "STR_SPLIT_REGEX" | "REGEXP_SPLIT" if f.args.len() == 2 => {
13512                                let name = match target {
13513                                    DialectType::DuckDB => "STR_SPLIT_REGEX",
13514                                    DialectType::Presto | DialectType::Trino => "REGEXP_SPLIT",
13515                                    DialectType::Spark
13516                                    | DialectType::Databricks
13517                                    | DialectType::Hive => "SPLIT",
13518                                    _ => "REGEXP_SPLIT",
13519                                };
13520                                Ok(Expression::Function(Box::new(Function::new(
13521                                    name.to_string(),
13522                                    f.args,
13523                                ))))
13524                            }
13525                            // SPLIT(str, delim) from Snowflake -> DuckDB with CASE wrapper
13526                            "SPLIT"
13527                                if f.args.len() == 2
13528                                    && matches!(source, DialectType::Snowflake)
13529                                    && matches!(target, DialectType::DuckDB) =>
13530                            {
13531                                let mut args = f.args;
13532                                let str_arg = args.remove(0);
13533                                let delim_arg = args.remove(0);
13534
13535                                // STR_SPLIT(str, delim) as the base
13536                                let base_func = Expression::Function(Box::new(Function::new(
13537                                    "STR_SPLIT".to_string(),
13538                                    vec![str_arg.clone(), delim_arg.clone()],
13539                                )));
13540
13541                                // [str] - array with single element
13542                                let array_with_input =
13543                                    Expression::Array(Box::new(crate::expressions::Array {
13544                                        expressions: vec![str_arg],
13545                                    }));
13546
13547                                // CASE
13548                                //   WHEN delim IS NULL THEN NULL
13549                                //   WHEN delim = '' THEN [str]
13550                                //   ELSE STR_SPLIT(str, delim)
13551                                // END
13552                                Ok(Expression::Case(Box::new(Case {
13553                                    operand: None,
13554                                    whens: vec![
13555                                        (
13556                                            Expression::Is(Box::new(BinaryOp {
13557                                                left: delim_arg.clone(),
13558                                                right: Expression::Null(Null),
13559                                                left_comments: vec![],
13560                                                operator_comments: vec![],
13561                                                trailing_comments: vec![],
13562                                                inferred_type: None,
13563                                            })),
13564                                            Expression::Null(Null),
13565                                        ),
13566                                        (
13567                                            Expression::Eq(Box::new(BinaryOp {
13568                                                left: delim_arg,
13569                                                right: Expression::string(""),
13570                                                left_comments: vec![],
13571                                                operator_comments: vec![],
13572                                                trailing_comments: vec![],
13573                                                inferred_type: None,
13574                                            })),
13575                                            array_with_input,
13576                                        ),
13577                                    ],
13578                                    else_: Some(base_func),
13579                                    comments: vec![],
13580                                    inferred_type: None,
13581                                })))
13582                            }
13583                            // SPLIT(x, sep) from Presto/StarRocks/Doris -> target-specific split with regex escaping for Hive/Spark
13584                            "SPLIT"
13585                                if f.args.len() == 2
13586                                    && matches!(
13587                                        source,
13588                                        DialectType::Presto
13589                                            | DialectType::Trino
13590                                            | DialectType::Athena
13591                                            | DialectType::StarRocks
13592                                            | DialectType::Doris
13593                                    )
13594                                    && matches!(
13595                                        target,
13596                                        DialectType::Spark
13597                                            | DialectType::Databricks
13598                                            | DialectType::Hive
13599                                    ) =>
13600                            {
13601                                // Presto/StarRocks SPLIT is literal, Hive/Spark SPLIT is regex
13602                                let mut args = f.args;
13603                                let x = args.remove(0);
13604                                let sep = args.remove(0);
13605                                let escaped_sep = Expression::Function(Box::new(Function::new(
13606                                    "CONCAT".to_string(),
13607                                    vec![Expression::string("\\Q"), sep, Expression::string("\\E")],
13608                                )));
13609                                Ok(Expression::Function(Box::new(Function::new(
13610                                    "SPLIT".to_string(),
13611                                    vec![x, escaped_sep],
13612                                ))))
13613                            }
13614                            // SUBSTRINGINDEX -> SUBSTRING_INDEX (ClickHouse camelCase to standard)
13615                            // For ClickHouse target, preserve original name to maintain camelCase
13616                            "SUBSTRINGINDEX" => {
13617                                let name = if matches!(target, DialectType::ClickHouse) {
13618                                    f.name.clone()
13619                                } else {
13620                                    "SUBSTRING_INDEX".to_string()
13621                                };
13622                                Ok(Expression::Function(Box::new(Function::new(name, f.args))))
13623                            }
13624                            // ARRAY_LENGTH/SIZE/CARDINALITY -> target-specific array length function
13625                            "ARRAY_LENGTH" | "SIZE" | "CARDINALITY" => {
13626                                // DuckDB source CARDINALITY -> DuckDB target: keep as CARDINALITY (used for maps)
13627                                if name == "CARDINALITY"
13628                                    && matches!(source, DialectType::DuckDB)
13629                                    && matches!(target, DialectType::DuckDB)
13630                                {
13631                                    return Ok(Expression::Function(f));
13632                                }
13633                                // Get the array argument (first arg, drop dimension args)
13634                                let mut args = f.args;
13635                                let arr = if args.is_empty() {
13636                                    return Ok(Expression::Function(Box::new(Function::new(
13637                                        name.to_string(),
13638                                        args,
13639                                    ))));
13640                                } else {
13641                                    args.remove(0)
13642                                };
13643                                let name =
13644                                    match target {
13645                                        DialectType::Spark
13646                                        | DialectType::Databricks
13647                                        | DialectType::Hive => "SIZE",
13648                                        DialectType::Presto | DialectType::Trino => "CARDINALITY",
13649                                        DialectType::BigQuery => "ARRAY_LENGTH",
13650                                        DialectType::DuckDB => {
13651                                            // DuckDB: use ARRAY_LENGTH with all args
13652                                            let mut all_args = vec![arr];
13653                                            all_args.extend(args);
13654                                            return Ok(Expression::Function(Box::new(
13655                                                Function::new("ARRAY_LENGTH".to_string(), all_args),
13656                                            )));
13657                                        }
13658                                        DialectType::PostgreSQL | DialectType::Redshift => {
13659                                            // Keep ARRAY_LENGTH with dimension arg
13660                                            let mut all_args = vec![arr];
13661                                            all_args.extend(args);
13662                                            return Ok(Expression::Function(Box::new(
13663                                                Function::new("ARRAY_LENGTH".to_string(), all_args),
13664                                            )));
13665                                        }
13666                                        DialectType::ClickHouse => "LENGTH",
13667                                        _ => "ARRAY_LENGTH",
13668                                    };
13669                                Ok(Expression::Function(Box::new(Function::new(
13670                                    name.to_string(),
13671                                    vec![arr],
13672                                ))))
13673                            }
13674                            // TO_VARIANT(x) -> CAST(x AS VARIANT) for DuckDB
13675                            "TO_VARIANT" if f.args.len() == 1 => match target {
13676                                DialectType::DuckDB => {
13677                                    let arg = f.args.into_iter().next().unwrap();
13678                                    Ok(Expression::Cast(Box::new(Cast {
13679                                        this: arg,
13680                                        to: DataType::Custom {
13681                                            name: "VARIANT".to_string(),
13682                                        },
13683                                        double_colon_syntax: false,
13684                                        trailing_comments: Vec::new(),
13685                                        format: None,
13686                                        default: None,
13687                                        inferred_type: None,
13688                                    })))
13689                                }
13690                                _ => Ok(Expression::Function(f)),
13691                            },
13692                            // JSON_GROUP_ARRAY(x) -> JSON_AGG(x) for PostgreSQL
13693                            "JSON_GROUP_ARRAY" if f.args.len() == 1 => match target {
13694                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
13695                                    Function::new("JSON_AGG".to_string(), f.args),
13696                                ))),
13697                                _ => Ok(Expression::Function(f)),
13698                            },
13699                            // JSON_GROUP_OBJECT(key, value) -> JSON_OBJECT_AGG(key, value) for PostgreSQL
13700                            "JSON_GROUP_OBJECT" if f.args.len() == 2 => match target {
13701                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
13702                                    Function::new("JSON_OBJECT_AGG".to_string(), f.args),
13703                                ))),
13704                                _ => Ok(Expression::Function(f)),
13705                            },
13706                            // UNICODE(x) -> target-specific codepoint function
13707                            "UNICODE" if f.args.len() == 1 => {
13708                                match target {
13709                                    DialectType::SQLite | DialectType::DuckDB => {
13710                                        Ok(Expression::Function(Box::new(Function::new(
13711                                            "UNICODE".to_string(),
13712                                            f.args,
13713                                        ))))
13714                                    }
13715                                    DialectType::Oracle => {
13716                                        // ASCII(UNISTR(x))
13717                                        let inner = Expression::Function(Box::new(Function::new(
13718                                            "UNISTR".to_string(),
13719                                            f.args,
13720                                        )));
13721                                        Ok(Expression::Function(Box::new(Function::new(
13722                                            "ASCII".to_string(),
13723                                            vec![inner],
13724                                        ))))
13725                                    }
13726                                    DialectType::MySQL => {
13727                                        // ORD(CONVERT(x USING utf32))
13728                                        let arg = f.args.into_iter().next().unwrap();
13729                                        let convert_expr = Expression::ConvertToCharset(Box::new(
13730                                            crate::expressions::ConvertToCharset {
13731                                                this: Box::new(arg),
13732                                                dest: Some(Box::new(Expression::Identifier(
13733                                                    crate::expressions::Identifier::new("utf32"),
13734                                                ))),
13735                                                source: None,
13736                                            },
13737                                        ));
13738                                        Ok(Expression::Function(Box::new(Function::new(
13739                                            "ORD".to_string(),
13740                                            vec![convert_expr],
13741                                        ))))
13742                                    }
13743                                    _ => Ok(Expression::Function(Box::new(Function::new(
13744                                        "ASCII".to_string(),
13745                                        f.args,
13746                                    )))),
13747                                }
13748                            }
13749                            // XOR(a, b, ...) -> a XOR b XOR ... for MySQL, BITWISE_XOR for Presto/Trino, # for PostgreSQL, ^ for BigQuery
13750                            "XOR" if f.args.len() >= 2 => {
13751                                match target {
13752                                    DialectType::ClickHouse => {
13753                                        // ClickHouse: keep as xor() function with lowercase name
13754                                        Ok(Expression::Function(Box::new(Function::new(
13755                                            "xor".to_string(),
13756                                            f.args,
13757                                        ))))
13758                                    }
13759                                    DialectType::Presto | DialectType::Trino => {
13760                                        if f.args.len() == 2 {
13761                                            Ok(Expression::Function(Box::new(Function::new(
13762                                                "BITWISE_XOR".to_string(),
13763                                                f.args,
13764                                            ))))
13765                                        } else {
13766                                            // Nest: BITWISE_XOR(BITWISE_XOR(a, b), c)
13767                                            let mut args = f.args;
13768                                            let first = args.remove(0);
13769                                            let second = args.remove(0);
13770                                            let mut result =
13771                                                Expression::Function(Box::new(Function::new(
13772                                                    "BITWISE_XOR".to_string(),
13773                                                    vec![first, second],
13774                                                )));
13775                                            for arg in args {
13776                                                result =
13777                                                    Expression::Function(Box::new(Function::new(
13778                                                        "BITWISE_XOR".to_string(),
13779                                                        vec![result, arg],
13780                                                    )));
13781                                            }
13782                                            Ok(result)
13783                                        }
13784                                    }
13785                                    DialectType::MySQL
13786                                    | DialectType::SingleStore
13787                                    | DialectType::Doris
13788                                    | DialectType::StarRocks => {
13789                                        // Convert XOR(a, b, c) -> Expression::Xor with expressions list
13790                                        let args = f.args;
13791                                        Ok(Expression::Xor(Box::new(crate::expressions::Xor {
13792                                            this: None,
13793                                            expression: None,
13794                                            expressions: args,
13795                                        })))
13796                                    }
13797                                    DialectType::PostgreSQL | DialectType::Redshift => {
13798                                        // PostgreSQL: a # b (hash operator for XOR)
13799                                        let mut args = f.args;
13800                                        let first = args.remove(0);
13801                                        let second = args.remove(0);
13802                                        let mut result = Expression::BitwiseXor(Box::new(
13803                                            BinaryOp::new(first, second),
13804                                        ));
13805                                        for arg in args {
13806                                            result = Expression::BitwiseXor(Box::new(
13807                                                BinaryOp::new(result, arg),
13808                                            ));
13809                                        }
13810                                        Ok(result)
13811                                    }
13812                                    DialectType::DuckDB => {
13813                                        // DuckDB: keep as XOR function (DuckDB ^ is Power, not XOR)
13814                                        Ok(Expression::Function(Box::new(Function::new(
13815                                            "XOR".to_string(),
13816                                            f.args,
13817                                        ))))
13818                                    }
13819                                    DialectType::BigQuery => {
13820                                        // BigQuery: a ^ b (caret operator for XOR)
13821                                        let mut args = f.args;
13822                                        let first = args.remove(0);
13823                                        let second = args.remove(0);
13824                                        let mut result = Expression::BitwiseXor(Box::new(
13825                                            BinaryOp::new(first, second),
13826                                        ));
13827                                        for arg in args {
13828                                            result = Expression::BitwiseXor(Box::new(
13829                                                BinaryOp::new(result, arg),
13830                                            ));
13831                                        }
13832                                        Ok(result)
13833                                    }
13834                                    _ => Ok(Expression::Function(Box::new(Function::new(
13835                                        "XOR".to_string(),
13836                                        f.args,
13837                                    )))),
13838                                }
13839                            }
13840                            // ARRAY_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
13841                            "ARRAY_REVERSE_SORT" if f.args.len() >= 1 => {
13842                                match target {
13843                                    DialectType::Spark
13844                                    | DialectType::Databricks
13845                                    | DialectType::Hive => {
13846                                        let mut args = f.args;
13847                                        args.push(Expression::Identifier(
13848                                            crate::expressions::Identifier::new("FALSE"),
13849                                        ));
13850                                        Ok(Expression::Function(Box::new(Function::new(
13851                                            "SORT_ARRAY".to_string(),
13852                                            args,
13853                                        ))))
13854                                    }
13855                                    DialectType::Presto
13856                                    | DialectType::Trino
13857                                    | DialectType::Athena => {
13858                                        // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
13859                                        let arr = f.args.into_iter().next().unwrap();
13860                                        let lambda = Expression::Lambda(Box::new(
13861                                            crate::expressions::LambdaExpr {
13862                                                parameters: vec![
13863                                                    Identifier::new("a"),
13864                                                    Identifier::new("b"),
13865                                                ],
13866                                                colon: false,
13867                                                parameter_types: Vec::new(),
13868                                                body: Expression::Case(Box::new(Case {
13869                                                    operand: None,
13870                                                    whens: vec![
13871                                                        (
13872                                                            Expression::Lt(Box::new(
13873                                                                BinaryOp::new(
13874                                                                    Expression::Identifier(
13875                                                                        Identifier::new("a"),
13876                                                                    ),
13877                                                                    Expression::Identifier(
13878                                                                        Identifier::new("b"),
13879                                                                    ),
13880                                                                ),
13881                                                            )),
13882                                                            Expression::number(1),
13883                                                        ),
13884                                                        (
13885                                                            Expression::Gt(Box::new(
13886                                                                BinaryOp::new(
13887                                                                    Expression::Identifier(
13888                                                                        Identifier::new("a"),
13889                                                                    ),
13890                                                                    Expression::Identifier(
13891                                                                        Identifier::new("b"),
13892                                                                    ),
13893                                                                ),
13894                                                            )),
13895                                                            Expression::Neg(Box::new(
13896                                                                crate::expressions::UnaryOp {
13897                                                                    this: Expression::number(1),
13898                                                                    inferred_type: None,
13899                                                                },
13900                                                            )),
13901                                                        ),
13902                                                    ],
13903                                                    else_: Some(Expression::number(0)),
13904                                                    comments: Vec::new(),
13905                                                    inferred_type: None,
13906                                                })),
13907                                            },
13908                                        ));
13909                                        Ok(Expression::Function(Box::new(Function::new(
13910                                            "ARRAY_SORT".to_string(),
13911                                            vec![arr, lambda],
13912                                        ))))
13913                                    }
13914                                    _ => Ok(Expression::Function(Box::new(Function::new(
13915                                        "ARRAY_REVERSE_SORT".to_string(),
13916                                        f.args,
13917                                    )))),
13918                                }
13919                            }
13920                            // ENCODE(x) -> ENCODE(x, 'utf-8') for Spark/Hive, TO_UTF8(x) for Presto
13921                            "ENCODE" if f.args.len() == 1 => match target {
13922                                DialectType::Spark
13923                                | DialectType::Databricks
13924                                | DialectType::Hive => {
13925                                    let mut args = f.args;
13926                                    args.push(Expression::string("utf-8"));
13927                                    Ok(Expression::Function(Box::new(Function::new(
13928                                        "ENCODE".to_string(),
13929                                        args,
13930                                    ))))
13931                                }
13932                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
13933                                    Ok(Expression::Function(Box::new(Function::new(
13934                                        "TO_UTF8".to_string(),
13935                                        f.args,
13936                                    ))))
13937                                }
13938                                _ => Ok(Expression::Function(Box::new(Function::new(
13939                                    "ENCODE".to_string(),
13940                                    f.args,
13941                                )))),
13942                            },
13943                            // DECODE(x) -> DECODE(x, 'utf-8') for Spark/Hive, FROM_UTF8(x) for Presto
13944                            "DECODE" if f.args.len() == 1 => match target {
13945                                DialectType::Spark
13946                                | DialectType::Databricks
13947                                | DialectType::Hive => {
13948                                    let mut args = f.args;
13949                                    args.push(Expression::string("utf-8"));
13950                                    Ok(Expression::Function(Box::new(Function::new(
13951                                        "DECODE".to_string(),
13952                                        args,
13953                                    ))))
13954                                }
13955                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
13956                                    Ok(Expression::Function(Box::new(Function::new(
13957                                        "FROM_UTF8".to_string(),
13958                                        f.args,
13959                                    ))))
13960                                }
13961                                _ => Ok(Expression::Function(Box::new(Function::new(
13962                                    "DECODE".to_string(),
13963                                    f.args,
13964                                )))),
13965                            },
13966                            // QUANTILE(x, p) -> PERCENTILE(x, p) for Spark/Hive
13967                            "QUANTILE" if f.args.len() == 2 => {
13968                                let name = match target {
13969                                    DialectType::Spark
13970                                    | DialectType::Databricks
13971                                    | DialectType::Hive => "PERCENTILE",
13972                                    DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
13973                                    DialectType::BigQuery => "PERCENTILE_CONT",
13974                                    _ => "QUANTILE",
13975                                };
13976                                Ok(Expression::Function(Box::new(Function::new(
13977                                    name.to_string(),
13978                                    f.args,
13979                                ))))
13980                            }
13981                            // QUANTILE_CONT(x, q) -> PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
13982                            "QUANTILE_CONT" if f.args.len() == 2 => {
13983                                let mut args = f.args;
13984                                let column = args.remove(0);
13985                                let quantile = args.remove(0);
13986                                match target {
13987                                    DialectType::DuckDB => {
13988                                        Ok(Expression::Function(Box::new(Function::new(
13989                                            "QUANTILE_CONT".to_string(),
13990                                            vec![column, quantile],
13991                                        ))))
13992                                    }
13993                                    DialectType::PostgreSQL
13994                                    | DialectType::Redshift
13995                                    | DialectType::Snowflake => {
13996                                        // PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x)
13997                                        let inner = Expression::PercentileCont(Box::new(
13998                                            crate::expressions::PercentileFunc {
13999                                                this: column.clone(),
14000                                                percentile: quantile,
14001                                                order_by: None,
14002                                                filter: None,
14003                                            },
14004                                        ));
14005                                        Ok(Expression::WithinGroup(Box::new(
14006                                            crate::expressions::WithinGroup {
14007                                                this: inner,
14008                                                order_by: vec![crate::expressions::Ordered {
14009                                                    this: column,
14010                                                    desc: false,
14011                                                    nulls_first: None,
14012                                                    explicit_asc: false,
14013                                                    with_fill: None,
14014                                                }],
14015                                            },
14016                                        )))
14017                                    }
14018                                    _ => Ok(Expression::Function(Box::new(Function::new(
14019                                        "QUANTILE_CONT".to_string(),
14020                                        vec![column, quantile],
14021                                    )))),
14022                                }
14023                            }
14024                            // QUANTILE_DISC(x, q) -> PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
14025                            "QUANTILE_DISC" if f.args.len() == 2 => {
14026                                let mut args = f.args;
14027                                let column = args.remove(0);
14028                                let quantile = args.remove(0);
14029                                match target {
14030                                    DialectType::DuckDB => {
14031                                        Ok(Expression::Function(Box::new(Function::new(
14032                                            "QUANTILE_DISC".to_string(),
14033                                            vec![column, quantile],
14034                                        ))))
14035                                    }
14036                                    DialectType::PostgreSQL
14037                                    | DialectType::Redshift
14038                                    | DialectType::Snowflake => {
14039                                        // PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x)
14040                                        let inner = Expression::PercentileDisc(Box::new(
14041                                            crate::expressions::PercentileFunc {
14042                                                this: column.clone(),
14043                                                percentile: quantile,
14044                                                order_by: None,
14045                                                filter: None,
14046                                            },
14047                                        ));
14048                                        Ok(Expression::WithinGroup(Box::new(
14049                                            crate::expressions::WithinGroup {
14050                                                this: inner,
14051                                                order_by: vec![crate::expressions::Ordered {
14052                                                    this: column,
14053                                                    desc: false,
14054                                                    nulls_first: None,
14055                                                    explicit_asc: false,
14056                                                    with_fill: None,
14057                                                }],
14058                                            },
14059                                        )))
14060                                    }
14061                                    _ => Ok(Expression::Function(Box::new(Function::new(
14062                                        "QUANTILE_DISC".to_string(),
14063                                        vec![column, quantile],
14064                                    )))),
14065                                }
14066                            }
14067                            // PERCENTILE_APPROX(x, p) / APPROX_PERCENTILE(x, p) -> target-specific
14068                            "PERCENTILE_APPROX" | "APPROX_PERCENTILE" if f.args.len() >= 2 => {
14069                                let name = match target {
14070                                    DialectType::Presto
14071                                    | DialectType::Trino
14072                                    | DialectType::Athena => "APPROX_PERCENTILE",
14073                                    DialectType::Spark
14074                                    | DialectType::Databricks
14075                                    | DialectType::Hive => "PERCENTILE_APPROX",
14076                                    DialectType::DuckDB => "APPROX_QUANTILE",
14077                                    DialectType::PostgreSQL | DialectType::Redshift => {
14078                                        "PERCENTILE_CONT"
14079                                    }
14080                                    _ => &f.name,
14081                                };
14082                                Ok(Expression::Function(Box::new(Function::new(
14083                                    name.to_string(),
14084                                    f.args,
14085                                ))))
14086                            }
14087                            // EPOCH(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
14088                            "EPOCH" if f.args.len() == 1 => {
14089                                let name = match target {
14090                                    DialectType::Spark
14091                                    | DialectType::Databricks
14092                                    | DialectType::Hive => "UNIX_TIMESTAMP",
14093                                    DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
14094                                    _ => "EPOCH",
14095                                };
14096                                Ok(Expression::Function(Box::new(Function::new(
14097                                    name.to_string(),
14098                                    f.args,
14099                                ))))
14100                            }
14101                            // EPOCH_MS(x) -> target-specific epoch milliseconds conversion
14102                            "EPOCH_MS" if f.args.len() == 1 => {
14103                                match target {
14104                                    DialectType::Spark | DialectType::Databricks => {
14105                                        Ok(Expression::Function(Box::new(Function::new(
14106                                            "TIMESTAMP_MILLIS".to_string(),
14107                                            f.args,
14108                                        ))))
14109                                    }
14110                                    DialectType::Hive => {
14111                                        // Hive: FROM_UNIXTIME(x / 1000)
14112                                        let arg = f.args.into_iter().next().unwrap();
14113                                        let div_expr = Expression::Div(Box::new(
14114                                            crate::expressions::BinaryOp::new(
14115                                                arg,
14116                                                Expression::number(1000),
14117                                            ),
14118                                        ));
14119                                        Ok(Expression::Function(Box::new(Function::new(
14120                                            "FROM_UNIXTIME".to_string(),
14121                                            vec![div_expr],
14122                                        ))))
14123                                    }
14124                                    DialectType::Presto | DialectType::Trino => {
14125                                        Ok(Expression::Function(Box::new(Function::new(
14126                                            "FROM_UNIXTIME".to_string(),
14127                                            vec![Expression::Div(Box::new(
14128                                                crate::expressions::BinaryOp::new(
14129                                                    f.args.into_iter().next().unwrap(),
14130                                                    Expression::number(1000),
14131                                                ),
14132                                            ))],
14133                                        ))))
14134                                    }
14135                                    _ => Ok(Expression::Function(Box::new(Function::new(
14136                                        "EPOCH_MS".to_string(),
14137                                        f.args,
14138                                    )))),
14139                                }
14140                            }
14141                            // HASHBYTES('algorithm', x) -> target-specific hash function
14142                            "HASHBYTES" if f.args.len() == 2 => {
14143                                // Keep HASHBYTES as-is for TSQL target
14144                                if matches!(target, DialectType::TSQL) {
14145                                    return Ok(Expression::Function(f));
14146                                }
14147                                let algo_expr = &f.args[0];
14148                                let algo = match algo_expr {
14149                                    Expression::Literal(lit)
14150                                        if matches!(
14151                                            lit.as_ref(),
14152                                            crate::expressions::Literal::String(_)
14153                                        ) =>
14154                                    {
14155                                        let crate::expressions::Literal::String(s) = lit.as_ref()
14156                                        else {
14157                                            unreachable!()
14158                                        };
14159                                        s.to_ascii_uppercase()
14160                                    }
14161                                    _ => return Ok(Expression::Function(f)),
14162                                };
14163                                let data_arg = f.args.into_iter().nth(1).unwrap();
14164                                match algo.as_str() {
14165                                    "SHA1" => {
14166                                        let name = match target {
14167                                            DialectType::Spark | DialectType::Databricks => "SHA",
14168                                            DialectType::Hive => "SHA1",
14169                                            _ => "SHA1",
14170                                        };
14171                                        Ok(Expression::Function(Box::new(Function::new(
14172                                            name.to_string(),
14173                                            vec![data_arg],
14174                                        ))))
14175                                    }
14176                                    "SHA2_256" => {
14177                                        Ok(Expression::Function(Box::new(Function::new(
14178                                            "SHA2".to_string(),
14179                                            vec![data_arg, Expression::number(256)],
14180                                        ))))
14181                                    }
14182                                    "SHA2_512" => {
14183                                        Ok(Expression::Function(Box::new(Function::new(
14184                                            "SHA2".to_string(),
14185                                            vec![data_arg, Expression::number(512)],
14186                                        ))))
14187                                    }
14188                                    "MD5" => Ok(Expression::Function(Box::new(Function::new(
14189                                        "MD5".to_string(),
14190                                        vec![data_arg],
14191                                    )))),
14192                                    _ => Ok(Expression::Function(Box::new(Function::new(
14193                                        "HASHBYTES".to_string(),
14194                                        vec![Expression::string(&algo), data_arg],
14195                                    )))),
14196                                }
14197                            }
14198                            // JSON_EXTRACT_PATH(json, key1, key2, ...) -> target-specific JSON extraction
14199                            "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
14200                                let is_text = name == "JSON_EXTRACT_PATH_TEXT";
14201                                let mut args = f.args;
14202                                let json_expr = args.remove(0);
14203                                // Build JSON path from remaining keys: $.key1.key2 or $.key1[0]
14204                                let mut json_path = "$".to_string();
14205                                for a in &args {
14206                                    match a {
14207                                        Expression::Literal(lit)
14208                                            if matches!(
14209                                                lit.as_ref(),
14210                                                crate::expressions::Literal::String(_)
14211                                            ) =>
14212                                        {
14213                                            let crate::expressions::Literal::String(s) =
14214                                                lit.as_ref()
14215                                            else {
14216                                                unreachable!()
14217                                            };
14218                                            // Numeric string keys become array indices: [0]
14219                                            if s.chars().all(|c| c.is_ascii_digit()) {
14220                                                json_path.push('[');
14221                                                json_path.push_str(s);
14222                                                json_path.push(']');
14223                                            } else {
14224                                                json_path.push('.');
14225                                                json_path.push_str(s);
14226                                            }
14227                                        }
14228                                        _ => {
14229                                            json_path.push_str(".?");
14230                                        }
14231                                    }
14232                                }
14233                                match target {
14234                                    DialectType::Spark
14235                                    | DialectType::Databricks
14236                                    | DialectType::Hive => {
14237                                        Ok(Expression::Function(Box::new(Function::new(
14238                                            "GET_JSON_OBJECT".to_string(),
14239                                            vec![json_expr, Expression::string(&json_path)],
14240                                        ))))
14241                                    }
14242                                    DialectType::Presto | DialectType::Trino => {
14243                                        let func_name = if is_text {
14244                                            "JSON_EXTRACT_SCALAR"
14245                                        } else {
14246                                            "JSON_EXTRACT"
14247                                        };
14248                                        Ok(Expression::Function(Box::new(Function::new(
14249                                            func_name.to_string(),
14250                                            vec![json_expr, Expression::string(&json_path)],
14251                                        ))))
14252                                    }
14253                                    DialectType::BigQuery | DialectType::MySQL => {
14254                                        let func_name = if is_text {
14255                                            "JSON_EXTRACT_SCALAR"
14256                                        } else {
14257                                            "JSON_EXTRACT"
14258                                        };
14259                                        Ok(Expression::Function(Box::new(Function::new(
14260                                            func_name.to_string(),
14261                                            vec![json_expr, Expression::string(&json_path)],
14262                                        ))))
14263                                    }
14264                                    DialectType::PostgreSQL | DialectType::Materialize => {
14265                                        // Keep as JSON_EXTRACT_PATH_TEXT / JSON_EXTRACT_PATH for PostgreSQL/Materialize
14266                                        let func_name = if is_text {
14267                                            "JSON_EXTRACT_PATH_TEXT"
14268                                        } else {
14269                                            "JSON_EXTRACT_PATH"
14270                                        };
14271                                        let mut new_args = vec![json_expr];
14272                                        new_args.extend(args);
14273                                        Ok(Expression::Function(Box::new(Function::new(
14274                                            func_name.to_string(),
14275                                            new_args,
14276                                        ))))
14277                                    }
14278                                    DialectType::DuckDB | DialectType::SQLite => {
14279                                        // Use -> for JSON_EXTRACT_PATH, ->> for JSON_EXTRACT_PATH_TEXT
14280                                        if is_text {
14281                                            Ok(Expression::JsonExtractScalar(Box::new(
14282                                                crate::expressions::JsonExtractFunc {
14283                                                    this: json_expr,
14284                                                    path: Expression::string(&json_path),
14285                                                    returning: None,
14286                                                    arrow_syntax: true,
14287                                                    hash_arrow_syntax: false,
14288                                                    wrapper_option: None,
14289                                                    quotes_option: None,
14290                                                    on_scalar_string: false,
14291                                                    on_error: None,
14292                                                },
14293                                            )))
14294                                        } else {
14295                                            Ok(Expression::JsonExtract(Box::new(
14296                                                crate::expressions::JsonExtractFunc {
14297                                                    this: json_expr,
14298                                                    path: Expression::string(&json_path),
14299                                                    returning: None,
14300                                                    arrow_syntax: true,
14301                                                    hash_arrow_syntax: false,
14302                                                    wrapper_option: None,
14303                                                    quotes_option: None,
14304                                                    on_scalar_string: false,
14305                                                    on_error: None,
14306                                                },
14307                                            )))
14308                                        }
14309                                    }
14310                                    DialectType::Redshift => {
14311                                        // Keep as JSON_EXTRACT_PATH_TEXT for Redshift
14312                                        let mut new_args = vec![json_expr];
14313                                        new_args.extend(args);
14314                                        Ok(Expression::Function(Box::new(Function::new(
14315                                            "JSON_EXTRACT_PATH_TEXT".to_string(),
14316                                            new_args,
14317                                        ))))
14318                                    }
14319                                    DialectType::TSQL => {
14320                                        // ISNULL(JSON_QUERY(json, '$.path'), JSON_VALUE(json, '$.path'))
14321                                        let jq = Expression::Function(Box::new(Function::new(
14322                                            "JSON_QUERY".to_string(),
14323                                            vec![json_expr.clone(), Expression::string(&json_path)],
14324                                        )));
14325                                        let jv = Expression::Function(Box::new(Function::new(
14326                                            "JSON_VALUE".to_string(),
14327                                            vec![json_expr, Expression::string(&json_path)],
14328                                        )));
14329                                        Ok(Expression::Function(Box::new(Function::new(
14330                                            "ISNULL".to_string(),
14331                                            vec![jq, jv],
14332                                        ))))
14333                                    }
14334                                    DialectType::ClickHouse => {
14335                                        let func_name = if is_text {
14336                                            "JSONExtractString"
14337                                        } else {
14338                                            "JSONExtractRaw"
14339                                        };
14340                                        let mut new_args = vec![json_expr];
14341                                        new_args.extend(args);
14342                                        Ok(Expression::Function(Box::new(Function::new(
14343                                            func_name.to_string(),
14344                                            new_args,
14345                                        ))))
14346                                    }
14347                                    _ => {
14348                                        let func_name = if is_text {
14349                                            "JSON_EXTRACT_SCALAR"
14350                                        } else {
14351                                            "JSON_EXTRACT"
14352                                        };
14353                                        Ok(Expression::Function(Box::new(Function::new(
14354                                            func_name.to_string(),
14355                                            vec![json_expr, Expression::string(&json_path)],
14356                                        ))))
14357                                    }
14358                                }
14359                            }
14360                            // APPROX_DISTINCT(x) -> APPROX_COUNT_DISTINCT(x) for Spark/Hive/BigQuery
14361                            "APPROX_DISTINCT" if f.args.len() >= 1 => {
14362                                let name = match target {
14363                                    DialectType::Spark
14364                                    | DialectType::Databricks
14365                                    | DialectType::Hive
14366                                    | DialectType::BigQuery => "APPROX_COUNT_DISTINCT",
14367                                    _ => "APPROX_DISTINCT",
14368                                };
14369                                let mut args = f.args;
14370                                // Hive doesn't support the accuracy parameter
14371                                if name == "APPROX_COUNT_DISTINCT"
14372                                    && matches!(target, DialectType::Hive)
14373                                {
14374                                    args.truncate(1);
14375                                }
14376                                Ok(Expression::Function(Box::new(Function::new(
14377                                    name.to_string(),
14378                                    args,
14379                                ))))
14380                            }
14381                            // REGEXP_EXTRACT(x, pattern) - normalize default group index
14382                            "REGEXP_EXTRACT" if f.args.len() == 2 => {
14383                                // Determine source default group index
14384                                let source_default = match source {
14385                                    DialectType::Presto
14386                                    | DialectType::Trino
14387                                    | DialectType::DuckDB => 0,
14388                                    _ => 1, // Hive/Spark/Databricks default = 1
14389                                };
14390                                // Determine target default group index
14391                                let target_default = match target {
14392                                    DialectType::Presto
14393                                    | DialectType::Trino
14394                                    | DialectType::DuckDB
14395                                    | DialectType::BigQuery => 0,
14396                                    DialectType::Snowflake => {
14397                                        // Snowflake uses REGEXP_SUBSTR
14398                                        return Ok(Expression::Function(Box::new(Function::new(
14399                                            "REGEXP_SUBSTR".to_string(),
14400                                            f.args,
14401                                        ))));
14402                                    }
14403                                    _ => 1, // Hive/Spark/Databricks default = 1
14404                                };
14405                                if source_default != target_default {
14406                                    let mut args = f.args;
14407                                    args.push(Expression::number(source_default));
14408                                    Ok(Expression::Function(Box::new(Function::new(
14409                                        "REGEXP_EXTRACT".to_string(),
14410                                        args,
14411                                    ))))
14412                                } else {
14413                                    Ok(Expression::Function(Box::new(Function::new(
14414                                        "REGEXP_EXTRACT".to_string(),
14415                                        f.args,
14416                                    ))))
14417                                }
14418                            }
14419                            // RLIKE(str, pattern) -> RegexpLike expression (generates as target-specific form)
14420                            "RLIKE" if f.args.len() == 2 => {
14421                                let mut args = f.args;
14422                                let str_expr = args.remove(0);
14423                                let pattern = args.remove(0);
14424                                match target {
14425                                    DialectType::DuckDB => {
14426                                        // REGEXP_MATCHES(str, pattern)
14427                                        Ok(Expression::Function(Box::new(Function::new(
14428                                            "REGEXP_MATCHES".to_string(),
14429                                            vec![str_expr, pattern],
14430                                        ))))
14431                                    }
14432                                    _ => {
14433                                        // Convert to RegexpLike which generates as RLIKE/~/REGEXP_LIKE per dialect
14434                                        Ok(Expression::RegexpLike(Box::new(
14435                                            crate::expressions::RegexpFunc {
14436                                                this: str_expr,
14437                                                pattern,
14438                                                flags: None,
14439                                            },
14440                                        )))
14441                                    }
14442                                }
14443                            }
14444                            // EOMONTH(date[, month_offset]) -> target-specific
14445                            "EOMONTH" if f.args.len() >= 1 => {
14446                                let mut args = f.args;
14447                                let date_arg = args.remove(0);
14448                                let month_offset = if !args.is_empty() {
14449                                    Some(args.remove(0))
14450                                } else {
14451                                    None
14452                                };
14453
14454                                // Helper: wrap date in CAST to DATE
14455                                let cast_to_date = |e: Expression| -> Expression {
14456                                    Expression::Cast(Box::new(Cast {
14457                                        this: e,
14458                                        to: DataType::Date,
14459                                        trailing_comments: vec![],
14460                                        double_colon_syntax: false,
14461                                        format: None,
14462                                        default: None,
14463                                        inferred_type: None,
14464                                    }))
14465                                };
14466
14467                                match target {
14468                                    DialectType::TSQL | DialectType::Fabric => {
14469                                        // TSQL: EOMONTH(CAST(date AS DATE)) or EOMONTH(DATEADD(MONTH, offset, CAST(date AS DATE)))
14470                                        let date = cast_to_date(date_arg);
14471                                        let date = if let Some(offset) = month_offset {
14472                                            Expression::Function(Box::new(Function::new(
14473                                                "DATEADD".to_string(),
14474                                                vec![
14475                                                    Expression::Identifier(Identifier::new(
14476                                                        "MONTH",
14477                                                    )),
14478                                                    offset,
14479                                                    date,
14480                                                ],
14481                                            )))
14482                                        } else {
14483                                            date
14484                                        };
14485                                        Ok(Expression::Function(Box::new(Function::new(
14486                                            "EOMONTH".to_string(),
14487                                            vec![date],
14488                                        ))))
14489                                    }
14490                                    DialectType::Presto
14491                                    | DialectType::Trino
14492                                    | DialectType::Athena => {
14493                                        // Presto: LAST_DAY_OF_MONTH(CAST(CAST(date AS TIMESTAMP) AS DATE))
14494                                        // or with offset: LAST_DAY_OF_MONTH(DATE_ADD('MONTH', offset, CAST(CAST(date AS TIMESTAMP) AS DATE)))
14495                                        let cast_ts = Expression::Cast(Box::new(Cast {
14496                                            this: date_arg,
14497                                            to: DataType::Timestamp {
14498                                                timezone: false,
14499                                                precision: None,
14500                                            },
14501                                            trailing_comments: vec![],
14502                                            double_colon_syntax: false,
14503                                            format: None,
14504                                            default: None,
14505                                            inferred_type: None,
14506                                        }));
14507                                        let date = cast_to_date(cast_ts);
14508                                        let date = if let Some(offset) = month_offset {
14509                                            Expression::Function(Box::new(Function::new(
14510                                                "DATE_ADD".to_string(),
14511                                                vec![Expression::string("MONTH"), offset, date],
14512                                            )))
14513                                        } else {
14514                                            date
14515                                        };
14516                                        Ok(Expression::Function(Box::new(Function::new(
14517                                            "LAST_DAY_OF_MONTH".to_string(),
14518                                            vec![date],
14519                                        ))))
14520                                    }
14521                                    DialectType::PostgreSQL => {
14522                                        // PostgreSQL: CAST(DATE_TRUNC('MONTH', CAST(date AS DATE) [+ INTERVAL 'offset MONTH']) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
14523                                        let date = cast_to_date(date_arg);
14524                                        let date = if let Some(offset) = month_offset {
14525                                            let interval_str = format!(
14526                                                "{} MONTH",
14527                                                Self::expr_to_string_static(&offset)
14528                                            );
14529                                            Expression::Add(Box::new(
14530                                                crate::expressions::BinaryOp::new(
14531                                                    date,
14532                                                    Expression::Interval(Box::new(
14533                                                        crate::expressions::Interval {
14534                                                            this: Some(Expression::string(
14535                                                                &interval_str,
14536                                                            )),
14537                                                            unit: None,
14538                                                        },
14539                                                    )),
14540                                                ),
14541                                            ))
14542                                        } else {
14543                                            date
14544                                        };
14545                                        let truncated =
14546                                            Expression::Function(Box::new(Function::new(
14547                                                "DATE_TRUNC".to_string(),
14548                                                vec![Expression::string("MONTH"), date],
14549                                            )));
14550                                        let plus_month = Expression::Add(Box::new(
14551                                            crate::expressions::BinaryOp::new(
14552                                                truncated,
14553                                                Expression::Interval(Box::new(
14554                                                    crate::expressions::Interval {
14555                                                        this: Some(Expression::string("1 MONTH")),
14556                                                        unit: None,
14557                                                    },
14558                                                )),
14559                                            ),
14560                                        ));
14561                                        let minus_day = Expression::Sub(Box::new(
14562                                            crate::expressions::BinaryOp::new(
14563                                                plus_month,
14564                                                Expression::Interval(Box::new(
14565                                                    crate::expressions::Interval {
14566                                                        this: Some(Expression::string("1 DAY")),
14567                                                        unit: None,
14568                                                    },
14569                                                )),
14570                                            ),
14571                                        ));
14572                                        Ok(Expression::Cast(Box::new(Cast {
14573                                            this: minus_day,
14574                                            to: DataType::Date,
14575                                            trailing_comments: vec![],
14576                                            double_colon_syntax: false,
14577                                            format: None,
14578                                            default: None,
14579                                            inferred_type: None,
14580                                        })))
14581                                    }
14582                                    DialectType::DuckDB => {
14583                                        // DuckDB: LAST_DAY(CAST(date AS DATE) [+ INTERVAL (offset) MONTH])
14584                                        let date = cast_to_date(date_arg);
14585                                        let date = if let Some(offset) = month_offset {
14586                                            // Wrap negative numbers in parentheses for DuckDB INTERVAL
14587                                            let interval_val =
14588                                                if matches!(&offset, Expression::Neg(_)) {
14589                                                    Expression::Paren(Box::new(
14590                                                        crate::expressions::Paren {
14591                                                            this: offset,
14592                                                            trailing_comments: Vec::new(),
14593                                                        },
14594                                                    ))
14595                                                } else {
14596                                                    offset
14597                                                };
14598                                            Expression::Add(Box::new(crate::expressions::BinaryOp::new(
14599                                                date,
14600                                                Expression::Interval(Box::new(crate::expressions::Interval {
14601                                                    this: Some(interval_val),
14602                                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
14603                                                        unit: crate::expressions::IntervalUnit::Month,
14604                                                        use_plural: false,
14605                                                    }),
14606                                                })),
14607                                            )))
14608                                        } else {
14609                                            date
14610                                        };
14611                                        Ok(Expression::Function(Box::new(Function::new(
14612                                            "LAST_DAY".to_string(),
14613                                            vec![date],
14614                                        ))))
14615                                    }
14616                                    DialectType::Snowflake | DialectType::Redshift => {
14617                                        // Snowflake/Redshift: LAST_DAY(TO_DATE(date) or CAST(date AS DATE))
14618                                        // With offset: LAST_DAY(DATEADD(MONTH, offset, TO_DATE(date)))
14619                                        let date = if matches!(target, DialectType::Snowflake) {
14620                                            Expression::Function(Box::new(Function::new(
14621                                                "TO_DATE".to_string(),
14622                                                vec![date_arg],
14623                                            )))
14624                                        } else {
14625                                            cast_to_date(date_arg)
14626                                        };
14627                                        let date = if let Some(offset) = month_offset {
14628                                            Expression::Function(Box::new(Function::new(
14629                                                "DATEADD".to_string(),
14630                                                vec![
14631                                                    Expression::Identifier(Identifier::new(
14632                                                        "MONTH",
14633                                                    )),
14634                                                    offset,
14635                                                    date,
14636                                                ],
14637                                            )))
14638                                        } else {
14639                                            date
14640                                        };
14641                                        Ok(Expression::Function(Box::new(Function::new(
14642                                            "LAST_DAY".to_string(),
14643                                            vec![date],
14644                                        ))))
14645                                    }
14646                                    DialectType::Spark | DialectType::Databricks => {
14647                                        // Spark: LAST_DAY(TO_DATE(date))
14648                                        // With offset: LAST_DAY(ADD_MONTHS(TO_DATE(date), offset))
14649                                        let date = Expression::Function(Box::new(Function::new(
14650                                            "TO_DATE".to_string(),
14651                                            vec![date_arg],
14652                                        )));
14653                                        let date = if let Some(offset) = month_offset {
14654                                            Expression::Function(Box::new(Function::new(
14655                                                "ADD_MONTHS".to_string(),
14656                                                vec![date, offset],
14657                                            )))
14658                                        } else {
14659                                            date
14660                                        };
14661                                        Ok(Expression::Function(Box::new(Function::new(
14662                                            "LAST_DAY".to_string(),
14663                                            vec![date],
14664                                        ))))
14665                                    }
14666                                    DialectType::MySQL => {
14667                                        // MySQL: LAST_DAY(DATE(date)) - no offset
14668                                        // With offset: LAST_DAY(DATE_ADD(date, INTERVAL offset MONTH)) - no DATE() wrapper
14669                                        let date = if let Some(offset) = month_offset {
14670                                            let iu = crate::expressions::IntervalUnit::Month;
14671                                            Expression::DateAdd(Box::new(
14672                                                crate::expressions::DateAddFunc {
14673                                                    this: date_arg,
14674                                                    interval: offset,
14675                                                    unit: iu,
14676                                                },
14677                                            ))
14678                                        } else {
14679                                            Expression::Function(Box::new(Function::new(
14680                                                "DATE".to_string(),
14681                                                vec![date_arg],
14682                                            )))
14683                                        };
14684                                        Ok(Expression::Function(Box::new(Function::new(
14685                                            "LAST_DAY".to_string(),
14686                                            vec![date],
14687                                        ))))
14688                                    }
14689                                    DialectType::BigQuery => {
14690                                        // BigQuery: LAST_DAY(CAST(date AS DATE))
14691                                        // With offset: LAST_DAY(DATE_ADD(CAST(date AS DATE), INTERVAL offset MONTH))
14692                                        let date = cast_to_date(date_arg);
14693                                        let date = if let Some(offset) = month_offset {
14694                                            let interval = Expression::Interval(Box::new(crate::expressions::Interval {
14695                                                this: Some(offset),
14696                                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
14697                                                    unit: crate::expressions::IntervalUnit::Month,
14698                                                    use_plural: false,
14699                                                }),
14700                                            }));
14701                                            Expression::Function(Box::new(Function::new(
14702                                                "DATE_ADD".to_string(),
14703                                                vec![date, interval],
14704                                            )))
14705                                        } else {
14706                                            date
14707                                        };
14708                                        Ok(Expression::Function(Box::new(Function::new(
14709                                            "LAST_DAY".to_string(),
14710                                            vec![date],
14711                                        ))))
14712                                    }
14713                                    DialectType::ClickHouse => {
14714                                        // ClickHouse: LAST_DAY(CAST(date AS Nullable(DATE)))
14715                                        let date = Expression::Cast(Box::new(Cast {
14716                                            this: date_arg,
14717                                            to: DataType::Nullable {
14718                                                inner: Box::new(DataType::Date),
14719                                            },
14720                                            trailing_comments: vec![],
14721                                            double_colon_syntax: false,
14722                                            format: None,
14723                                            default: None,
14724                                            inferred_type: None,
14725                                        }));
14726                                        let date = if let Some(offset) = month_offset {
14727                                            Expression::Function(Box::new(Function::new(
14728                                                "DATE_ADD".to_string(),
14729                                                vec![
14730                                                    Expression::Identifier(Identifier::new(
14731                                                        "MONTH",
14732                                                    )),
14733                                                    offset,
14734                                                    date,
14735                                                ],
14736                                            )))
14737                                        } else {
14738                                            date
14739                                        };
14740                                        Ok(Expression::Function(Box::new(Function::new(
14741                                            "LAST_DAY".to_string(),
14742                                            vec![date],
14743                                        ))))
14744                                    }
14745                                    DialectType::Hive => {
14746                                        // Hive: LAST_DAY(date)
14747                                        let date = if let Some(offset) = month_offset {
14748                                            Expression::Function(Box::new(Function::new(
14749                                                "ADD_MONTHS".to_string(),
14750                                                vec![date_arg, offset],
14751                                            )))
14752                                        } else {
14753                                            date_arg
14754                                        };
14755                                        Ok(Expression::Function(Box::new(Function::new(
14756                                            "LAST_DAY".to_string(),
14757                                            vec![date],
14758                                        ))))
14759                                    }
14760                                    _ => {
14761                                        // Default: LAST_DAY(date)
14762                                        let date = if let Some(offset) = month_offset {
14763                                            let unit =
14764                                                Expression::Identifier(Identifier::new("MONTH"));
14765                                            Expression::Function(Box::new(Function::new(
14766                                                "DATEADD".to_string(),
14767                                                vec![unit, offset, date_arg],
14768                                            )))
14769                                        } else {
14770                                            date_arg
14771                                        };
14772                                        Ok(Expression::Function(Box::new(Function::new(
14773                                            "LAST_DAY".to_string(),
14774                                            vec![date],
14775                                        ))))
14776                                    }
14777                                }
14778                            }
14779                            // LAST_DAY(x) / LAST_DAY_OF_MONTH(x) -> target-specific
14780                            "LAST_DAY" | "LAST_DAY_OF_MONTH"
14781                                if !matches!(source, DialectType::BigQuery)
14782                                    && f.args.len() >= 1 =>
14783                            {
14784                                let first_arg = f.args.into_iter().next().unwrap();
14785                                match target {
14786                                    DialectType::TSQL | DialectType::Fabric => {
14787                                        Ok(Expression::Function(Box::new(Function::new(
14788                                            "EOMONTH".to_string(),
14789                                            vec![first_arg],
14790                                        ))))
14791                                    }
14792                                    DialectType::Presto
14793                                    | DialectType::Trino
14794                                    | DialectType::Athena => {
14795                                        Ok(Expression::Function(Box::new(Function::new(
14796                                            "LAST_DAY_OF_MONTH".to_string(),
14797                                            vec![first_arg],
14798                                        ))))
14799                                    }
14800                                    _ => Ok(Expression::Function(Box::new(Function::new(
14801                                        "LAST_DAY".to_string(),
14802                                        vec![first_arg],
14803                                    )))),
14804                                }
14805                            }
14806                            // BigQuery PARSE_DATETIME(format, value) -> target-specific parsing calls.
14807                            "PARSE_DATETIME"
14808                                if matches!(source, DialectType::BigQuery) && f.args.len() == 2 =>
14809                            {
14810                                fn expand_bigquery_datetime_format(expr: Expression) -> Expression {
14811                                    match expr {
14812                                        Expression::Literal(lit) => match lit.as_ref() {
14813                                            Literal::String(s) => Expression::string(
14814                                                s.replace("%F", "%Y-%m-%d")
14815                                                    .replace("%T", "%H:%M:%S"),
14816                                            ),
14817                                            _ => Expression::Literal(lit),
14818                                        },
14819                                        other => other,
14820                                    }
14821                                }
14822
14823                                let mut args = f.args;
14824                                let format = expand_bigquery_datetime_format(args.remove(0));
14825                                let value = args.remove(0);
14826                                match target {
14827                                    DialectType::DuckDB => {
14828                                        let value_with_year = Expression::Concat(Box::new(
14829                                            crate::expressions::BinaryOp::new(
14830                                                Expression::string("1970 "),
14831                                                value,
14832                                            ),
14833                                        ));
14834                                        let format_with_year = Expression::Concat(Box::new(
14835                                            crate::expressions::BinaryOp::new(
14836                                                Expression::string("%Y "),
14837                                                format,
14838                                            ),
14839                                        ));
14840                                        Ok(Expression::Function(Box::new(Function::new(
14841                                            "STRPTIME".to_string(),
14842                                            vec![value_with_year, format_with_year],
14843                                        ))))
14844                                    }
14845                                    DialectType::Snowflake => {
14846                                        Ok(Expression::Function(Box::new(Function::new(
14847                                            "PARSE_DATETIME".to_string(),
14848                                            vec![value, format],
14849                                        ))))
14850                                    }
14851                                    _ => Ok(Expression::Function(Box::new(Function::new(
14852                                        "PARSE_DATETIME".to_string(),
14853                                        vec![format, value],
14854                                    )))),
14855                                }
14856                            }
14857                            // Presto/Trino ISO-8601 helpers become casts outside Presto-family targets.
14858                            "FROM_ISO8601_TIMESTAMP"
14859                                if matches!(
14860                                    source,
14861                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
14862                                ) && f.args.len() == 1
14863                                    && !matches!(
14864                                        target,
14865                                        DialectType::Presto
14866                                            | DialectType::Trino
14867                                            | DialectType::Athena
14868                                    ) =>
14869                            {
14870                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
14871                                    this: f.args.into_iter().next().unwrap(),
14872                                    to: DataType::Timestamp {
14873                                        precision: None,
14874                                        timezone: matches!(
14875                                            target,
14876                                            DialectType::DuckDB | DialectType::Snowflake
14877                                        ),
14878                                    },
14879                                    trailing_comments: Vec::new(),
14880                                    double_colon_syntax: false,
14881                                    format: None,
14882                                    default: None,
14883                                    inferred_type: None,
14884                                })))
14885                            }
14886                            "FROM_ISO8601_DATE"
14887                                if matches!(
14888                                    source,
14889                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
14890                                ) && f.args.len() == 1
14891                                    && !matches!(
14892                                        target,
14893                                        DialectType::Presto
14894                                            | DialectType::Trino
14895                                            | DialectType::Athena
14896                                    ) =>
14897                            {
14898                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
14899                                    this: f.args.into_iter().next().unwrap(),
14900                                    to: DataType::Date,
14901                                    trailing_comments: Vec::new(),
14902                                    double_colon_syntax: false,
14903                                    format: None,
14904                                    default: None,
14905                                    inferred_type: None,
14906                                })))
14907                            }
14908                            // MAP(keys_array, vals_array) from Presto (2-arg form) -> target-specific
14909                            "MAP"
14910                                if f.args.len() == 2
14911                                    && matches!(
14912                                        source,
14913                                        DialectType::Presto
14914                                            | DialectType::Trino
14915                                            | DialectType::Athena
14916                                    ) =>
14917                            {
14918                                let keys_arg = f.args[0].clone();
14919                                let vals_arg = f.args[1].clone();
14920
14921                                // Helper: extract array elements from Array/ArrayFunc/Function("ARRAY") expressions
14922                                fn extract_array_elements(
14923                                    expr: &Expression,
14924                                ) -> Option<&Vec<Expression>> {
14925                                    match expr {
14926                                        Expression::Array(arr) => Some(&arr.expressions),
14927                                        Expression::ArrayFunc(arr) => Some(&arr.expressions),
14928                                        Expression::Function(f)
14929                                            if f.name.eq_ignore_ascii_case("ARRAY") =>
14930                                        {
14931                                            Some(&f.args)
14932                                        }
14933                                        _ => None,
14934                                    }
14935                                }
14936
14937                                match target {
14938                                    DialectType::Spark | DialectType::Databricks => {
14939                                        // Presto MAP(keys, vals) -> Spark MAP_FROM_ARRAYS(keys, vals)
14940                                        Ok(Expression::Function(Box::new(Function::new(
14941                                            "MAP_FROM_ARRAYS".to_string(),
14942                                            f.args,
14943                                        ))))
14944                                    }
14945                                    DialectType::Hive => {
14946                                        // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Hive MAP(k1, v1, k2, v2)
14947                                        if let (Some(keys), Some(vals)) = (
14948                                            extract_array_elements(&keys_arg),
14949                                            extract_array_elements(&vals_arg),
14950                                        ) {
14951                                            if keys.len() == vals.len() {
14952                                                let mut interleaved = Vec::new();
14953                                                for (k, v) in keys.iter().zip(vals.iter()) {
14954                                                    interleaved.push(k.clone());
14955                                                    interleaved.push(v.clone());
14956                                                }
14957                                                Ok(Expression::Function(Box::new(Function::new(
14958                                                    "MAP".to_string(),
14959                                                    interleaved,
14960                                                ))))
14961                                            } else {
14962                                                Ok(Expression::Function(Box::new(Function::new(
14963                                                    "MAP".to_string(),
14964                                                    f.args,
14965                                                ))))
14966                                            }
14967                                        } else {
14968                                            Ok(Expression::Function(Box::new(Function::new(
14969                                                "MAP".to_string(),
14970                                                f.args,
14971                                            ))))
14972                                        }
14973                                    }
14974                                    DialectType::Snowflake => {
14975                                        // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Snowflake OBJECT_CONSTRUCT(k1, v1, k2, v2)
14976                                        if let (Some(keys), Some(vals)) = (
14977                                            extract_array_elements(&keys_arg),
14978                                            extract_array_elements(&vals_arg),
14979                                        ) {
14980                                            if keys.len() == vals.len() {
14981                                                let mut interleaved = Vec::new();
14982                                                for (k, v) in keys.iter().zip(vals.iter()) {
14983                                                    interleaved.push(k.clone());
14984                                                    interleaved.push(v.clone());
14985                                                }
14986                                                Ok(Expression::Function(Box::new(Function::new(
14987                                                    "OBJECT_CONSTRUCT".to_string(),
14988                                                    interleaved,
14989                                                ))))
14990                                            } else {
14991                                                Ok(Expression::Function(Box::new(Function::new(
14992                                                    "MAP".to_string(),
14993                                                    f.args,
14994                                                ))))
14995                                            }
14996                                        } else {
14997                                            Ok(Expression::Function(Box::new(Function::new(
14998                                                "MAP".to_string(),
14999                                                f.args,
15000                                            ))))
15001                                        }
15002                                    }
15003                                    _ => Ok(Expression::Function(f)),
15004                                }
15005                            }
15006                            // MAP() with 0 args from Spark -> MAP(ARRAY[], ARRAY[]) for Presto/Trino
15007                            "MAP"
15008                                if f.args.is_empty()
15009                                    && matches!(
15010                                        source,
15011                                        DialectType::Hive
15012                                            | DialectType::Spark
15013                                            | DialectType::Databricks
15014                                    )
15015                                    && matches!(
15016                                        target,
15017                                        DialectType::Presto
15018                                            | DialectType::Trino
15019                                            | DialectType::Athena
15020                                    ) =>
15021                            {
15022                                let empty_keys =
15023                                    Expression::Array(Box::new(crate::expressions::Array {
15024                                        expressions: vec![],
15025                                    }));
15026                                let empty_vals =
15027                                    Expression::Array(Box::new(crate::expressions::Array {
15028                                        expressions: vec![],
15029                                    }));
15030                                Ok(Expression::Function(Box::new(Function::new(
15031                                    "MAP".to_string(),
15032                                    vec![empty_keys, empty_vals],
15033                                ))))
15034                            }
15035                            // MAP(k1, v1, k2, v2, ...) from Hive/Spark -> target-specific
15036                            "MAP"
15037                                if f.args.len() >= 2
15038                                    && f.args.len() % 2 == 0
15039                                    && matches!(
15040                                        source,
15041                                        DialectType::Hive
15042                                            | DialectType::Spark
15043                                            | DialectType::Databricks
15044                                            | DialectType::ClickHouse
15045                                            | DialectType::StarRocks
15046                                    ) =>
15047                            {
15048                                let args = f.args;
15049                                match target {
15050                                    DialectType::DuckDB => {
15051                                        // MAP([k1, k2], [v1, v2])
15052                                        let mut keys = Vec::new();
15053                                        let mut vals = Vec::new();
15054                                        for (i, arg) in args.into_iter().enumerate() {
15055                                            if i % 2 == 0 {
15056                                                keys.push(arg);
15057                                            } else {
15058                                                vals.push(arg);
15059                                            }
15060                                        }
15061                                        let keys_arr = Expression::Array(Box::new(
15062                                            crate::expressions::Array { expressions: keys },
15063                                        ));
15064                                        let vals_arr = Expression::Array(Box::new(
15065                                            crate::expressions::Array { expressions: vals },
15066                                        ));
15067                                        Ok(Expression::Function(Box::new(Function::new(
15068                                            "MAP".to_string(),
15069                                            vec![keys_arr, vals_arr],
15070                                        ))))
15071                                    }
15072                                    DialectType::Presto | DialectType::Trino => {
15073                                        // MAP(ARRAY[k1, k2], ARRAY[v1, v2])
15074                                        let mut keys = Vec::new();
15075                                        let mut vals = Vec::new();
15076                                        for (i, arg) in args.into_iter().enumerate() {
15077                                            if i % 2 == 0 {
15078                                                keys.push(arg);
15079                                            } else {
15080                                                vals.push(arg);
15081                                            }
15082                                        }
15083                                        let keys_arr = Expression::Array(Box::new(
15084                                            crate::expressions::Array { expressions: keys },
15085                                        ));
15086                                        let vals_arr = Expression::Array(Box::new(
15087                                            crate::expressions::Array { expressions: vals },
15088                                        ));
15089                                        Ok(Expression::Function(Box::new(Function::new(
15090                                            "MAP".to_string(),
15091                                            vec![keys_arr, vals_arr],
15092                                        ))))
15093                                    }
15094                                    DialectType::Snowflake => Ok(Expression::Function(Box::new(
15095                                        Function::new("OBJECT_CONSTRUCT".to_string(), args),
15096                                    ))),
15097                                    DialectType::ClickHouse => Ok(Expression::Function(Box::new(
15098                                        Function::new("map".to_string(), args),
15099                                    ))),
15100                                    _ => Ok(Expression::Function(Box::new(Function::new(
15101                                        "MAP".to_string(),
15102                                        args,
15103                                    )))),
15104                                }
15105                            }
15106                            // COLLECT_LIST(x) -> ARRAY_AGG(x) for most targets
15107                            "COLLECT_LIST" if f.args.len() >= 1 => {
15108                                let name = match target {
15109                                    DialectType::Spark
15110                                    | DialectType::Databricks
15111                                    | DialectType::Hive => "COLLECT_LIST",
15112                                    DialectType::DuckDB
15113                                    | DialectType::PostgreSQL
15114                                    | DialectType::Redshift
15115                                    | DialectType::Snowflake
15116                                    | DialectType::BigQuery => "ARRAY_AGG",
15117                                    DialectType::Presto | DialectType::Trino => "ARRAY_AGG",
15118                                    _ => "ARRAY_AGG",
15119                                };
15120                                Ok(Expression::Function(Box::new(Function::new(
15121                                    name.to_string(),
15122                                    f.args,
15123                                ))))
15124                            }
15125                            // COLLECT_SET(x) -> target-specific distinct array aggregation
15126                            "COLLECT_SET" if f.args.len() >= 1 => {
15127                                let name = match target {
15128                                    DialectType::Spark
15129                                    | DialectType::Databricks
15130                                    | DialectType::Hive => "COLLECT_SET",
15131                                    DialectType::Presto
15132                                    | DialectType::Trino
15133                                    | DialectType::Athena => "SET_AGG",
15134                                    DialectType::Snowflake => "ARRAY_UNIQUE_AGG",
15135                                    _ => "ARRAY_AGG",
15136                                };
15137                                Ok(Expression::Function(Box::new(Function::new(
15138                                    name.to_string(),
15139                                    f.args,
15140                                ))))
15141                            }
15142                            // ISNAN(x) / IS_NAN(x) - normalize
15143                            "ISNAN" | "IS_NAN" => {
15144                                let name = match target {
15145                                    DialectType::Spark
15146                                    | DialectType::Databricks
15147                                    | DialectType::Hive => "ISNAN",
15148                                    DialectType::Presto
15149                                    | DialectType::Trino
15150                                    | DialectType::Athena => "IS_NAN",
15151                                    DialectType::BigQuery
15152                                    | DialectType::PostgreSQL
15153                                    | DialectType::Redshift => "IS_NAN",
15154                                    DialectType::ClickHouse => "IS_NAN",
15155                                    _ => "ISNAN",
15156                                };
15157                                Ok(Expression::Function(Box::new(Function::new(
15158                                    name.to_string(),
15159                                    f.args,
15160                                ))))
15161                            }
15162                            // SPLIT_PART(str, delim, index) -> target-specific
15163                            "SPLIT_PART" if f.args.len() == 3 => {
15164                                match target {
15165                                    DialectType::Spark | DialectType::Databricks => {
15166                                        // Keep as SPLIT_PART (Spark 3.4+)
15167                                        Ok(Expression::Function(Box::new(Function::new(
15168                                            "SPLIT_PART".to_string(),
15169                                            f.args,
15170                                        ))))
15171                                    }
15172                                    DialectType::DuckDB
15173                                        if matches!(source, DialectType::Snowflake) =>
15174                                    {
15175                                        // Snowflake SPLIT_PART -> DuckDB with CASE wrapper:
15176                                        // - part_index 0 treated as 1
15177                                        // - empty delimiter: return whole string if index 1 or -1, else ''
15178                                        let mut args = f.args;
15179                                        let str_arg = args.remove(0);
15180                                        let delim_arg = args.remove(0);
15181                                        let idx_arg = args.remove(0);
15182
15183                                        // (CASE WHEN idx = 0 THEN 1 ELSE idx END)
15184                                        let adjusted_idx = Expression::Paren(Box::new(Paren {
15185                                            this: Expression::Case(Box::new(Case {
15186                                                operand: None,
15187                                                whens: vec![(
15188                                                    Expression::Eq(Box::new(BinaryOp {
15189                                                        left: idx_arg.clone(),
15190                                                        right: Expression::number(0),
15191                                                        left_comments: vec![],
15192                                                        operator_comments: vec![],
15193                                                        trailing_comments: vec![],
15194                                                        inferred_type: None,
15195                                                    })),
15196                                                    Expression::number(1),
15197                                                )],
15198                                                else_: Some(idx_arg.clone()),
15199                                                comments: vec![],
15200                                                inferred_type: None,
15201                                            })),
15202                                            trailing_comments: vec![],
15203                                        }));
15204
15205                                        // SPLIT_PART(str, delim, adjusted_idx)
15206                                        let base_func =
15207                                            Expression::Function(Box::new(Function::new(
15208                                                "SPLIT_PART".to_string(),
15209                                                vec![
15210                                                    str_arg.clone(),
15211                                                    delim_arg.clone(),
15212                                                    adjusted_idx.clone(),
15213                                                ],
15214                                            )));
15215
15216                                        // (CASE WHEN adjusted_idx = 1 OR adjusted_idx = -1 THEN str ELSE '' END)
15217                                        let empty_delim_case = Expression::Paren(Box::new(Paren {
15218                                            this: Expression::Case(Box::new(Case {
15219                                                operand: None,
15220                                                whens: vec![(
15221                                                    Expression::Or(Box::new(BinaryOp {
15222                                                        left: Expression::Eq(Box::new(BinaryOp {
15223                                                            left: adjusted_idx.clone(),
15224                                                            right: Expression::number(1),
15225                                                            left_comments: vec![],
15226                                                            operator_comments: vec![],
15227                                                            trailing_comments: vec![],
15228                                                            inferred_type: None,
15229                                                        })),
15230                                                        right: Expression::Eq(Box::new(BinaryOp {
15231                                                            left: adjusted_idx,
15232                                                            right: Expression::number(-1),
15233                                                            left_comments: vec![],
15234                                                            operator_comments: vec![],
15235                                                            trailing_comments: vec![],
15236                                                            inferred_type: None,
15237                                                        })),
15238                                                        left_comments: vec![],
15239                                                        operator_comments: vec![],
15240                                                        trailing_comments: vec![],
15241                                                        inferred_type: None,
15242                                                    })),
15243                                                    str_arg,
15244                                                )],
15245                                                else_: Some(Expression::string("")),
15246                                                comments: vec![],
15247                                                inferred_type: None,
15248                                            })),
15249                                            trailing_comments: vec![],
15250                                        }));
15251
15252                                        // CASE WHEN delim = '' THEN (empty case) ELSE SPLIT_PART(...) END
15253                                        Ok(Expression::Case(Box::new(Case {
15254                                            operand: None,
15255                                            whens: vec![(
15256                                                Expression::Eq(Box::new(BinaryOp {
15257                                                    left: delim_arg,
15258                                                    right: Expression::string(""),
15259                                                    left_comments: vec![],
15260                                                    operator_comments: vec![],
15261                                                    trailing_comments: vec![],
15262                                                    inferred_type: None,
15263                                                })),
15264                                                empty_delim_case,
15265                                            )],
15266                                            else_: Some(base_func),
15267                                            comments: vec![],
15268                                            inferred_type: None,
15269                                        })))
15270                                    }
15271                                    DialectType::DuckDB
15272                                    | DialectType::PostgreSQL
15273                                    | DialectType::Snowflake
15274                                    | DialectType::Redshift
15275                                    | DialectType::Trino
15276                                    | DialectType::Presto => Ok(Expression::Function(Box::new(
15277                                        Function::new("SPLIT_PART".to_string(), f.args),
15278                                    ))),
15279                                    DialectType::Hive => {
15280                                        // SPLIT(str, delim)[index]
15281                                        // Complex conversion, just keep as-is for now
15282                                        Ok(Expression::Function(Box::new(Function::new(
15283                                            "SPLIT_PART".to_string(),
15284                                            f.args,
15285                                        ))))
15286                                    }
15287                                    _ => Ok(Expression::Function(Box::new(Function::new(
15288                                        "SPLIT_PART".to_string(),
15289                                        f.args,
15290                                    )))),
15291                                }
15292                            }
15293                            // JSON_EXTRACT(json, path) -> target-specific JSON extraction
15294                            "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR" if f.args.len() == 2 => {
15295                                let is_scalar = name == "JSON_EXTRACT_SCALAR";
15296                                match target {
15297                                    DialectType::Spark
15298                                    | DialectType::Databricks
15299                                    | DialectType::Hive => {
15300                                        let mut args = f.args;
15301                                        // Spark/Hive don't support Presto's TRY(expr) wrapper form here.
15302                                        // Mirror sqlglot by unwrapping TRY(expr) to expr before GET_JSON_OBJECT.
15303                                        if let Some(Expression::Function(inner)) = args.first() {
15304                                            if inner.name.eq_ignore_ascii_case("TRY")
15305                                                && inner.args.len() == 1
15306                                            {
15307                                                let mut inner_args = inner.args.clone();
15308                                                args[0] = inner_args.remove(0);
15309                                            }
15310                                        }
15311                                        Ok(Expression::Function(Box::new(Function::new(
15312                                            "GET_JSON_OBJECT".to_string(),
15313                                            args,
15314                                        ))))
15315                                    }
15316                                    DialectType::DuckDB | DialectType::SQLite => {
15317                                        // json -> path syntax
15318                                        let mut args = f.args;
15319                                        let json_expr = args.remove(0);
15320                                        let path = args.remove(0);
15321                                        Ok(Expression::JsonExtract(Box::new(
15322                                            crate::expressions::JsonExtractFunc {
15323                                                this: json_expr,
15324                                                path,
15325                                                returning: None,
15326                                                arrow_syntax: true,
15327                                                hash_arrow_syntax: false,
15328                                                wrapper_option: None,
15329                                                quotes_option: None,
15330                                                on_scalar_string: false,
15331                                                on_error: None,
15332                                            },
15333                                        )))
15334                                    }
15335                                    DialectType::TSQL => {
15336                                        let func_name = if is_scalar {
15337                                            "JSON_VALUE"
15338                                        } else {
15339                                            "JSON_QUERY"
15340                                        };
15341                                        Ok(Expression::Function(Box::new(Function::new(
15342                                            func_name.to_string(),
15343                                            f.args,
15344                                        ))))
15345                                    }
15346                                    DialectType::PostgreSQL | DialectType::Redshift => {
15347                                        let func_name = if is_scalar {
15348                                            "JSON_EXTRACT_PATH_TEXT"
15349                                        } else {
15350                                            "JSON_EXTRACT_PATH"
15351                                        };
15352                                        Ok(Expression::Function(Box::new(Function::new(
15353                                            func_name.to_string(),
15354                                            f.args,
15355                                        ))))
15356                                    }
15357                                    _ => Ok(Expression::Function(Box::new(Function::new(
15358                                        name.to_string(),
15359                                        f.args,
15360                                    )))),
15361                                }
15362                            }
15363                            // MySQL JSON_SEARCH(json_doc, mode, search[, escape_char[, path]]) -> DuckDB json_tree-based lookup
15364                            "JSON_SEARCH"
15365                                if matches!(target, DialectType::DuckDB)
15366                                    && (3..=5).contains(&f.args.len()) =>
15367                            {
15368                                let args = &f.args;
15369
15370                                // Only rewrite deterministic modes and NULL/no escape-char variant.
15371                                let mode = match &args[1] {
15372                                    Expression::Literal(lit)
15373                                        if matches!(
15374                                            lit.as_ref(),
15375                                            crate::expressions::Literal::String(_)
15376                                        ) =>
15377                                    {
15378                                        let crate::expressions::Literal::String(s) = lit.as_ref()
15379                                        else {
15380                                            unreachable!()
15381                                        };
15382                                        s.to_ascii_lowercase()
15383                                    }
15384                                    _ => return Ok(Expression::Function(f)),
15385                                };
15386                                if mode != "one" && mode != "all" {
15387                                    return Ok(Expression::Function(f));
15388                                }
15389                                if args.len() >= 4 && !matches!(&args[3], Expression::Null(_)) {
15390                                    return Ok(Expression::Function(f));
15391                                }
15392
15393                                let json_doc_sql = match Generator::sql(&args[0]) {
15394                                    Ok(sql) => sql,
15395                                    Err(_) => return Ok(Expression::Function(f)),
15396                                };
15397                                let search_sql = match Generator::sql(&args[2]) {
15398                                    Ok(sql) => sql,
15399                                    Err(_) => return Ok(Expression::Function(f)),
15400                                };
15401                                let path_sql = if args.len() == 5 {
15402                                    match Generator::sql(&args[4]) {
15403                                        Ok(sql) => sql,
15404                                        Err(_) => return Ok(Expression::Function(f)),
15405                                    }
15406                                } else {
15407                                    "'$'".to_string()
15408                                };
15409
15410                                let rewrite_sql = if mode == "all" {
15411                                    format!(
15412                                        "(SELECT TO_JSON(LIST(__jt.fullkey)) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}))",
15413                                        json_doc_sql, path_sql, search_sql
15414                                    )
15415                                } else {
15416                                    format!(
15417                                        "(SELECT TO_JSON(__jt.fullkey) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}) ORDER BY __jt.id LIMIT 1)",
15418                                        json_doc_sql, path_sql, search_sql
15419                                    )
15420                                };
15421
15422                                Ok(Expression::Raw(crate::expressions::Raw {
15423                                    sql: rewrite_sql,
15424                                }))
15425                            }
15426                            // SingleStore JSON_EXTRACT_JSON(json, key1, key2, ...) -> JSON_EXTRACT(json, '$.key1.key2' or '$.key1[key2]')
15427                            // BSON_EXTRACT_BSON(json, key1, ...) -> JSONB_EXTRACT(json, '$.key1')
15428                            "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
15429                                if f.args.len() >= 2
15430                                    && matches!(source, DialectType::SingleStore) =>
15431                            {
15432                                let is_bson = name == "BSON_EXTRACT_BSON";
15433                                let mut args = f.args;
15434                                let json_expr = args.remove(0);
15435
15436                                // Build JSONPath from remaining arguments
15437                                let mut path = String::from("$");
15438                                for arg in &args {
15439                                    if let Expression::Literal(lit) = arg {
15440                                        if let crate::expressions::Literal::String(s) = lit.as_ref()
15441                                        {
15442                                            // Check if it's a numeric string (array index)
15443                                            if s.parse::<i64>().is_ok() {
15444                                                path.push('[');
15445                                                path.push_str(s);
15446                                                path.push(']');
15447                                            } else {
15448                                                path.push('.');
15449                                                path.push_str(s);
15450                                            }
15451                                        }
15452                                    }
15453                                }
15454
15455                                let target_func = if is_bson {
15456                                    "JSONB_EXTRACT"
15457                                } else {
15458                                    "JSON_EXTRACT"
15459                                };
15460                                Ok(Expression::Function(Box::new(Function::new(
15461                                    target_func.to_string(),
15462                                    vec![json_expr, Expression::string(&path)],
15463                                ))))
15464                            }
15465                            // ARRAY_SUM(lambda, array) from Doris -> ClickHouse arraySum
15466                            "ARRAY_SUM" if matches!(target, DialectType::ClickHouse) => {
15467                                Ok(Expression::Function(Box::new(Function {
15468                                    name: "arraySum".to_string(),
15469                                    args: f.args,
15470                                    distinct: f.distinct,
15471                                    trailing_comments: f.trailing_comments,
15472                                    use_bracket_syntax: f.use_bracket_syntax,
15473                                    no_parens: f.no_parens,
15474                                    quoted: f.quoted,
15475                                    span: None,
15476                                    inferred_type: None,
15477                                })))
15478                            }
15479                            // TSQL JSON_QUERY/JSON_VALUE -> target-specific
15480                            // Note: For TSQL->TSQL, JsonQuery stays as Expression::JsonQuery (source transform not called)
15481                            // and is handled by JsonQueryValueConvert action. This handles the case where
15482                            // TSQL read transform converted JsonQuery to Function("JSON_QUERY") for cross-dialect.
15483                            "JSON_QUERY" | "JSON_VALUE"
15484                                if f.args.len() == 2
15485                                    && matches!(
15486                                        source,
15487                                        DialectType::TSQL | DialectType::Fabric
15488                                    ) =>
15489                            {
15490                                match target {
15491                                    DialectType::Spark
15492                                    | DialectType::Databricks
15493                                    | DialectType::Hive => Ok(Expression::Function(Box::new(
15494                                        Function::new("GET_JSON_OBJECT".to_string(), f.args),
15495                                    ))),
15496                                    _ => Ok(Expression::Function(Box::new(Function::new(
15497                                        name.to_string(),
15498                                        f.args,
15499                                    )))),
15500                                }
15501                            }
15502                            // UNIX_TIMESTAMP(x) -> TO_UNIXTIME(x) for Presto
15503                            "UNIX_TIMESTAMP" if f.args.len() == 1 => {
15504                                let arg = f.args.into_iter().next().unwrap();
15505                                let is_hive_source = matches!(
15506                                    source,
15507                                    DialectType::Hive
15508                                        | DialectType::Spark
15509                                        | DialectType::Databricks
15510                                );
15511                                match target {
15512                                    DialectType::DuckDB if is_hive_source => {
15513                                        // DuckDB: EPOCH(STRPTIME(x, '%Y-%m-%d %H:%M:%S'))
15514                                        let strptime =
15515                                            Expression::Function(Box::new(Function::new(
15516                                                "STRPTIME".to_string(),
15517                                                vec![arg, Expression::string("%Y-%m-%d %H:%M:%S")],
15518                                            )));
15519                                        Ok(Expression::Function(Box::new(Function::new(
15520                                            "EPOCH".to_string(),
15521                                            vec![strptime],
15522                                        ))))
15523                                    }
15524                                    DialectType::Presto | DialectType::Trino if is_hive_source => {
15525                                        // Presto: TO_UNIXTIME(COALESCE(TRY(DATE_PARSE(CAST(x AS VARCHAR), '%Y-%m-%d %T')), PARSE_DATETIME(DATE_FORMAT(x, '%Y-%m-%d %T'), 'yyyy-MM-dd HH:mm:ss')))
15526                                        let cast_varchar =
15527                                            Expression::Cast(Box::new(crate::expressions::Cast {
15528                                                this: arg.clone(),
15529                                                to: DataType::VarChar {
15530                                                    length: None,
15531                                                    parenthesized_length: false,
15532                                                },
15533                                                trailing_comments: vec![],
15534                                                double_colon_syntax: false,
15535                                                format: None,
15536                                                default: None,
15537                                                inferred_type: None,
15538                                            }));
15539                                        let date_parse =
15540                                            Expression::Function(Box::new(Function::new(
15541                                                "DATE_PARSE".to_string(),
15542                                                vec![
15543                                                    cast_varchar,
15544                                                    Expression::string("%Y-%m-%d %T"),
15545                                                ],
15546                                            )));
15547                                        let try_expr = Expression::Function(Box::new(
15548                                            Function::new("TRY".to_string(), vec![date_parse]),
15549                                        ));
15550                                        let date_format =
15551                                            Expression::Function(Box::new(Function::new(
15552                                                "DATE_FORMAT".to_string(),
15553                                                vec![arg, Expression::string("%Y-%m-%d %T")],
15554                                            )));
15555                                        let parse_datetime =
15556                                            Expression::Function(Box::new(Function::new(
15557                                                "PARSE_DATETIME".to_string(),
15558                                                vec![
15559                                                    date_format,
15560                                                    Expression::string("yyyy-MM-dd HH:mm:ss"),
15561                                                ],
15562                                            )));
15563                                        let coalesce =
15564                                            Expression::Function(Box::new(Function::new(
15565                                                "COALESCE".to_string(),
15566                                                vec![try_expr, parse_datetime],
15567                                            )));
15568                                        Ok(Expression::Function(Box::new(Function::new(
15569                                            "TO_UNIXTIME".to_string(),
15570                                            vec![coalesce],
15571                                        ))))
15572                                    }
15573                                    DialectType::Presto | DialectType::Trino => {
15574                                        Ok(Expression::Function(Box::new(Function::new(
15575                                            "TO_UNIXTIME".to_string(),
15576                                            vec![arg],
15577                                        ))))
15578                                    }
15579                                    _ => Ok(Expression::Function(Box::new(Function::new(
15580                                        "UNIX_TIMESTAMP".to_string(),
15581                                        vec![arg],
15582                                    )))),
15583                                }
15584                            }
15585                            // TO_UNIX_TIMESTAMP(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
15586                            "TO_UNIX_TIMESTAMP" if f.args.len() >= 1 => match target {
15587                                DialectType::Spark
15588                                | DialectType::Databricks
15589                                | DialectType::Hive => Ok(Expression::Function(Box::new(
15590                                    Function::new("UNIX_TIMESTAMP".to_string(), f.args),
15591                                ))),
15592                                _ => Ok(Expression::Function(Box::new(Function::new(
15593                                    "TO_UNIX_TIMESTAMP".to_string(),
15594                                    f.args,
15595                                )))),
15596                            },
15597                            // CURDATE() -> CURRENT_DATE
15598                            "CURDATE" => {
15599                                Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
15600                            }
15601                            // CURTIME() -> CURRENT_TIME
15602                            "CURTIME" => {
15603                                Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
15604                                    precision: None,
15605                                }))
15606                            }
15607                            // ARRAY_SORT(x) or ARRAY_SORT(x, lambda) -> SORT_ARRAY(x) for Hive, LIST_SORT for DuckDB
15608                            "ARRAY_SORT" if f.args.len() >= 1 => {
15609                                match target {
15610                                    DialectType::Hive => {
15611                                        let mut args = f.args;
15612                                        args.truncate(1); // Drop lambda comparator
15613                                        Ok(Expression::Function(Box::new(Function::new(
15614                                            "SORT_ARRAY".to_string(),
15615                                            args,
15616                                        ))))
15617                                    }
15618                                    DialectType::DuckDB
15619                                        if matches!(source, DialectType::Snowflake) =>
15620                                    {
15621                                        // Snowflake ARRAY_SORT(arr[, asc_bool[, nulls_first_bool]]) -> DuckDB LIST_SORT(arr[, 'ASC'/'DESC'[, 'NULLS FIRST']])
15622                                        let mut args_iter = f.args.into_iter();
15623                                        let arr = args_iter.next().unwrap();
15624                                        let asc_arg = args_iter.next();
15625                                        let nulls_first_arg = args_iter.next();
15626
15627                                        let is_asc_bool = asc_arg
15628                                            .as_ref()
15629                                            .map(|a| matches!(a, Expression::Boolean(_)))
15630                                            .unwrap_or(false);
15631                                        let is_nf_bool = nulls_first_arg
15632                                            .as_ref()
15633                                            .map(|a| matches!(a, Expression::Boolean(_)))
15634                                            .unwrap_or(false);
15635
15636                                        // No boolean args: pass through as-is
15637                                        if !is_asc_bool && !is_nf_bool {
15638                                            let mut result_args = vec![arr];
15639                                            if let Some(asc) = asc_arg {
15640                                                result_args.push(asc);
15641                                                if let Some(nf) = nulls_first_arg {
15642                                                    result_args.push(nf);
15643                                                }
15644                                            }
15645                                            Ok(Expression::Function(Box::new(Function::new(
15646                                                "LIST_SORT".to_string(),
15647                                                result_args,
15648                                            ))))
15649                                        } else {
15650                                            // Has boolean args: convert to DuckDB LIST_SORT format
15651                                            let descending = matches!(&asc_arg, Some(Expression::Boolean(b)) if !b.value);
15652
15653                                            // Snowflake defaults: nulls_first = TRUE for DESC, FALSE for ASC
15654                                            let nulls_are_first = match &nulls_first_arg {
15655                                                Some(Expression::Boolean(b)) => b.value,
15656                                                None if is_asc_bool => descending, // Snowflake default
15657                                                _ => false,
15658                                            };
15659                                            let nulls_first_sql = if nulls_are_first {
15660                                                Some(Expression::string("NULLS FIRST"))
15661                                            } else {
15662                                                None
15663                                            };
15664
15665                                            if !is_asc_bool {
15666                                                // asc is non-boolean expression, nulls_first is boolean
15667                                                let mut result_args = vec![arr];
15668                                                if let Some(asc) = asc_arg {
15669                                                    result_args.push(asc);
15670                                                }
15671                                                if let Some(nf) = nulls_first_sql {
15672                                                    result_args.push(nf);
15673                                                }
15674                                                Ok(Expression::Function(Box::new(Function::new(
15675                                                    "LIST_SORT".to_string(),
15676                                                    result_args,
15677                                                ))))
15678                                            } else {
15679                                                if !descending && !nulls_are_first {
15680                                                    // ASC, NULLS LAST (default) -> LIST_SORT(arr)
15681                                                    Ok(Expression::Function(Box::new(
15682                                                        Function::new(
15683                                                            "LIST_SORT".to_string(),
15684                                                            vec![arr],
15685                                                        ),
15686                                                    )))
15687                                                } else if descending && !nulls_are_first {
15688                                                    // DESC, NULLS LAST -> ARRAY_REVERSE_SORT(arr)
15689                                                    Ok(Expression::Function(Box::new(
15690                                                        Function::new(
15691                                                            "ARRAY_REVERSE_SORT".to_string(),
15692                                                            vec![arr],
15693                                                        ),
15694                                                    )))
15695                                                } else {
15696                                                    // NULLS FIRST -> LIST_SORT(arr, 'ASC'/'DESC', 'NULLS FIRST')
15697                                                    let order_str =
15698                                                        if descending { "DESC" } else { "ASC" };
15699                                                    Ok(Expression::Function(Box::new(
15700                                                        Function::new(
15701                                                            "LIST_SORT".to_string(),
15702                                                            vec![
15703                                                                arr,
15704                                                                Expression::string(order_str),
15705                                                                Expression::string("NULLS FIRST"),
15706                                                            ],
15707                                                        ),
15708                                                    )))
15709                                                }
15710                                            }
15711                                        }
15712                                    }
15713                                    DialectType::DuckDB => {
15714                                        // Non-Snowflake source: ARRAY_SORT(x, lambda) -> ARRAY_SORT(x) (drop comparator)
15715                                        let mut args = f.args;
15716                                        args.truncate(1); // Drop lambda comparator for DuckDB
15717                                        Ok(Expression::Function(Box::new(Function::new(
15718                                            "ARRAY_SORT".to_string(),
15719                                            args,
15720                                        ))))
15721                                    }
15722                                    _ => Ok(Expression::Function(f)),
15723                                }
15724                            }
15725                            // SORT_ARRAY(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, keep for Hive/Spark
15726                            "SORT_ARRAY" if f.args.len() == 1 => match target {
15727                                DialectType::Hive
15728                                | DialectType::Spark
15729                                | DialectType::Databricks => Ok(Expression::Function(f)),
15730                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
15731                                    Function::new("LIST_SORT".to_string(), f.args),
15732                                ))),
15733                                _ => Ok(Expression::Function(Box::new(Function::new(
15734                                    "ARRAY_SORT".to_string(),
15735                                    f.args,
15736                                )))),
15737                            },
15738                            // SORT_ARRAY(x, FALSE) -> ARRAY_REVERSE_SORT(x) for DuckDB, ARRAY_SORT(x, lambda) for Presto
15739                            "SORT_ARRAY" if f.args.len() == 2 => {
15740                                let is_desc =
15741                                    matches!(&f.args[1], Expression::Boolean(b) if !b.value);
15742                                if is_desc {
15743                                    match target {
15744                                        DialectType::DuckDB => {
15745                                            Ok(Expression::Function(Box::new(Function::new(
15746                                                "ARRAY_REVERSE_SORT".to_string(),
15747                                                vec![f.args.into_iter().next().unwrap()],
15748                                            ))))
15749                                        }
15750                                        DialectType::Presto | DialectType::Trino => {
15751                                            let arr_arg = f.args.into_iter().next().unwrap();
15752                                            let a = Expression::Column(Box::new(
15753                                                crate::expressions::Column {
15754                                                    name: crate::expressions::Identifier::new("a"),
15755                                                    table: None,
15756                                                    join_mark: false,
15757                                                    trailing_comments: Vec::new(),
15758                                                    span: None,
15759                                                    inferred_type: None,
15760                                                },
15761                                            ));
15762                                            let b = Expression::Column(Box::new(
15763                                                crate::expressions::Column {
15764                                                    name: crate::expressions::Identifier::new("b"),
15765                                                    table: None,
15766                                                    join_mark: false,
15767                                                    trailing_comments: Vec::new(),
15768                                                    span: None,
15769                                                    inferred_type: None,
15770                                                },
15771                                            ));
15772                                            let case_expr = Expression::Case(Box::new(
15773                                                crate::expressions::Case {
15774                                                    operand: None,
15775                                                    whens: vec![
15776                                                        (
15777                                                            Expression::Lt(Box::new(
15778                                                                BinaryOp::new(a.clone(), b.clone()),
15779                                                            )),
15780                                                            Expression::Literal(Box::new(
15781                                                                Literal::Number("1".to_string()),
15782                                                            )),
15783                                                        ),
15784                                                        (
15785                                                            Expression::Gt(Box::new(
15786                                                                BinaryOp::new(a.clone(), b.clone()),
15787                                                            )),
15788                                                            Expression::Literal(Box::new(
15789                                                                Literal::Number("-1".to_string()),
15790                                                            )),
15791                                                        ),
15792                                                    ],
15793                                                    else_: Some(Expression::Literal(Box::new(
15794                                                        Literal::Number("0".to_string()),
15795                                                    ))),
15796                                                    comments: Vec::new(),
15797                                                    inferred_type: None,
15798                                                },
15799                                            ));
15800                                            let lambda = Expression::Lambda(Box::new(
15801                                                crate::expressions::LambdaExpr {
15802                                                    parameters: vec![
15803                                                        crate::expressions::Identifier::new("a"),
15804                                                        crate::expressions::Identifier::new("b"),
15805                                                    ],
15806                                                    body: case_expr,
15807                                                    colon: false,
15808                                                    parameter_types: Vec::new(),
15809                                                },
15810                                            ));
15811                                            Ok(Expression::Function(Box::new(Function::new(
15812                                                "ARRAY_SORT".to_string(),
15813                                                vec![arr_arg, lambda],
15814                                            ))))
15815                                        }
15816                                        _ => Ok(Expression::Function(f)),
15817                                    }
15818                                } else {
15819                                    // SORT_ARRAY(x, TRUE) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for others
15820                                    match target {
15821                                        DialectType::Hive => Ok(Expression::Function(f)),
15822                                        DialectType::DuckDB => {
15823                                            Ok(Expression::Function(Box::new(Function::new(
15824                                                "LIST_SORT".to_string(),
15825                                                vec![f.args.into_iter().next().unwrap()],
15826                                            ))))
15827                                        }
15828                                        _ => Ok(Expression::Function(Box::new(Function::new(
15829                                            "ARRAY_SORT".to_string(),
15830                                            vec![f.args.into_iter().next().unwrap()],
15831                                        )))),
15832                                    }
15833                                }
15834                            }
15835                            // LEFT(x, n), RIGHT(x, n) -> SUBSTRING for targets without LEFT/RIGHT
15836                            "LEFT" if f.args.len() == 2 => {
15837                                match target {
15838                                    DialectType::Hive
15839                                    | DialectType::Presto
15840                                    | DialectType::Trino
15841                                    | DialectType::Athena => {
15842                                        let x = f.args[0].clone();
15843                                        let n = f.args[1].clone();
15844                                        Ok(Expression::Function(Box::new(Function::new(
15845                                            "SUBSTRING".to_string(),
15846                                            vec![x, Expression::number(1), n],
15847                                        ))))
15848                                    }
15849                                    DialectType::Spark | DialectType::Databricks
15850                                        if matches!(
15851                                            source,
15852                                            DialectType::TSQL | DialectType::Fabric
15853                                        ) =>
15854                                    {
15855                                        // TSQL LEFT(x, n) -> LEFT(CAST(x AS STRING), n) for Spark
15856                                        let x = f.args[0].clone();
15857                                        let n = f.args[1].clone();
15858                                        let cast_x = Expression::Cast(Box::new(Cast {
15859                                            this: x,
15860                                            to: DataType::VarChar {
15861                                                length: None,
15862                                                parenthesized_length: false,
15863                                            },
15864                                            double_colon_syntax: false,
15865                                            trailing_comments: Vec::new(),
15866                                            format: None,
15867                                            default: None,
15868                                            inferred_type: None,
15869                                        }));
15870                                        Ok(Expression::Function(Box::new(Function::new(
15871                                            "LEFT".to_string(),
15872                                            vec![cast_x, n],
15873                                        ))))
15874                                    }
15875                                    _ => Ok(Expression::Function(f)),
15876                                }
15877                            }
15878                            "RIGHT" if f.args.len() == 2 => {
15879                                match target {
15880                                    DialectType::Hive
15881                                    | DialectType::Presto
15882                                    | DialectType::Trino
15883                                    | DialectType::Athena => {
15884                                        let x = f.args[0].clone();
15885                                        let n = f.args[1].clone();
15886                                        // SUBSTRING(x, LENGTH(x) - (n - 1))
15887                                        let len_x = Expression::Function(Box::new(Function::new(
15888                                            "LENGTH".to_string(),
15889                                            vec![x.clone()],
15890                                        )));
15891                                        let n_minus_1 = Expression::Sub(Box::new(
15892                                            crate::expressions::BinaryOp::new(
15893                                                n,
15894                                                Expression::number(1),
15895                                            ),
15896                                        ));
15897                                        let n_minus_1_paren = Expression::Paren(Box::new(
15898                                            crate::expressions::Paren {
15899                                                this: n_minus_1,
15900                                                trailing_comments: Vec::new(),
15901                                            },
15902                                        ));
15903                                        let offset = Expression::Sub(Box::new(
15904                                            crate::expressions::BinaryOp::new(
15905                                                len_x,
15906                                                n_minus_1_paren,
15907                                            ),
15908                                        ));
15909                                        Ok(Expression::Function(Box::new(Function::new(
15910                                            "SUBSTRING".to_string(),
15911                                            vec![x, offset],
15912                                        ))))
15913                                    }
15914                                    DialectType::Spark | DialectType::Databricks
15915                                        if matches!(
15916                                            source,
15917                                            DialectType::TSQL | DialectType::Fabric
15918                                        ) =>
15919                                    {
15920                                        // TSQL RIGHT(x, n) -> RIGHT(CAST(x AS STRING), n) for Spark
15921                                        let x = f.args[0].clone();
15922                                        let n = f.args[1].clone();
15923                                        let cast_x = Expression::Cast(Box::new(Cast {
15924                                            this: x,
15925                                            to: DataType::VarChar {
15926                                                length: None,
15927                                                parenthesized_length: false,
15928                                            },
15929                                            double_colon_syntax: false,
15930                                            trailing_comments: Vec::new(),
15931                                            format: None,
15932                                            default: None,
15933                                            inferred_type: None,
15934                                        }));
15935                                        Ok(Expression::Function(Box::new(Function::new(
15936                                            "RIGHT".to_string(),
15937                                            vec![cast_x, n],
15938                                        ))))
15939                                    }
15940                                    _ => Ok(Expression::Function(f)),
15941                                }
15942                            }
15943                            // MAP_FROM_ARRAYS(keys, vals) -> target-specific map construction
15944                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
15945                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
15946                                    Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
15947                                ))),
15948                                DialectType::Spark | DialectType::Databricks => {
15949                                    Ok(Expression::Function(Box::new(Function::new(
15950                                        "MAP_FROM_ARRAYS".to_string(),
15951                                        f.args,
15952                                    ))))
15953                                }
15954                                _ => Ok(Expression::Function(Box::new(Function::new(
15955                                    "MAP".to_string(),
15956                                    f.args,
15957                                )))),
15958                            },
15959                            // LIKE(foo, 'pat') -> foo LIKE 'pat'; LIKE(foo, 'pat', '!') -> foo LIKE 'pat' ESCAPE '!'
15960                            // SQLite uses LIKE(pattern, string[, escape]) with args in reverse order
15961                            "LIKE" if f.args.len() >= 2 => {
15962                                let (this, pattern) = if matches!(source, DialectType::SQLite) {
15963                                    // SQLite: LIKE(pattern, string) -> string LIKE pattern
15964                                    (f.args[1].clone(), f.args[0].clone())
15965                                } else {
15966                                    // Standard: LIKE(string, pattern) -> string LIKE pattern
15967                                    (f.args[0].clone(), f.args[1].clone())
15968                                };
15969                                let escape = if f.args.len() >= 3 {
15970                                    Some(f.args[2].clone())
15971                                } else {
15972                                    None
15973                                };
15974                                Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
15975                                    left: this,
15976                                    right: pattern,
15977                                    escape,
15978                                    quantifier: None,
15979                                    inferred_type: None,
15980                                })))
15981                            }
15982                            // ILIKE(foo, 'pat') -> foo ILIKE 'pat'
15983                            "ILIKE" if f.args.len() >= 2 => {
15984                                let this = f.args[0].clone();
15985                                let pattern = f.args[1].clone();
15986                                let escape = if f.args.len() >= 3 {
15987                                    Some(f.args[2].clone())
15988                                } else {
15989                                    None
15990                                };
15991                                Ok(Expression::ILike(Box::new(crate::expressions::LikeOp {
15992                                    left: this,
15993                                    right: pattern,
15994                                    escape,
15995                                    quantifier: None,
15996                                    inferred_type: None,
15997                                })))
15998                            }
15999                            // CHAR(n) -> CHR(n) for non-MySQL/non-TSQL targets
16000                            "CHAR" if f.args.len() == 1 => match target {
16001                                DialectType::MySQL
16002                                | DialectType::SingleStore
16003                                | DialectType::TSQL => Ok(Expression::Function(f)),
16004                                _ => Ok(Expression::Function(Box::new(Function::new(
16005                                    "CHR".to_string(),
16006                                    f.args,
16007                                )))),
16008                            },
16009                            // CONCAT(a, b) -> a || b for PostgreSQL
16010                            "CONCAT"
16011                                if f.args.len() == 2
16012                                    && matches!(target, DialectType::PostgreSQL)
16013                                    && matches!(
16014                                        source,
16015                                        DialectType::ClickHouse | DialectType::MySQL
16016                                    ) =>
16017                            {
16018                                let mut args = f.args;
16019                                let right = args.pop().unwrap();
16020                                let left = args.pop().unwrap();
16021                                Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
16022                                    this: Box::new(left),
16023                                    expression: Box::new(right),
16024                                    safe: None,
16025                                })))
16026                            }
16027                            // ARRAY_TO_STRING(arr, delim) -> target-specific
16028                            "ARRAY_TO_STRING"
16029                                if f.args.len() == 2
16030                                    && matches!(target, DialectType::DuckDB)
16031                                    && matches!(source, DialectType::Snowflake) =>
16032                            {
16033                                let mut args = f.args;
16034                                let arr = args.remove(0);
16035                                let sep = args.remove(0);
16036                                // sep IS NULL
16037                                let sep_is_null = Expression::IsNull(Box::new(IsNull {
16038                                    this: sep.clone(),
16039                                    not: false,
16040                                    postfix_form: false,
16041                                }));
16042                                // COALESCE(CAST(x AS TEXT), '')
16043                                let cast_x = Expression::Cast(Box::new(Cast {
16044                                    this: Expression::Identifier(Identifier::new("x")),
16045                                    to: DataType::Text,
16046                                    trailing_comments: Vec::new(),
16047                                    double_colon_syntax: false,
16048                                    format: None,
16049                                    default: None,
16050                                    inferred_type: None,
16051                                }));
16052                                let coalesce = Expression::Coalesce(Box::new(
16053                                    crate::expressions::VarArgFunc {
16054                                        original_name: None,
16055                                        expressions: vec![
16056                                            cast_x,
16057                                            Expression::Literal(Box::new(Literal::String(
16058                                                String::new(),
16059                                            ))),
16060                                        ],
16061                                        inferred_type: None,
16062                                    },
16063                                ));
16064                                let lambda =
16065                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
16066                                        parameters: vec![Identifier::new("x")],
16067                                        body: coalesce,
16068                                        colon: false,
16069                                        parameter_types: Vec::new(),
16070                                    }));
16071                                let list_transform = Expression::Function(Box::new(Function::new(
16072                                    "LIST_TRANSFORM".to_string(),
16073                                    vec![arr, lambda],
16074                                )));
16075                                let array_to_string =
16076                                    Expression::Function(Box::new(Function::new(
16077                                        "ARRAY_TO_STRING".to_string(),
16078                                        vec![list_transform, sep],
16079                                    )));
16080                                Ok(Expression::Case(Box::new(Case {
16081                                    operand: None,
16082                                    whens: vec![(sep_is_null, Expression::Null(Null))],
16083                                    else_: Some(array_to_string),
16084                                    comments: Vec::new(),
16085                                    inferred_type: None,
16086                                })))
16087                            }
16088                            "ARRAY_TO_STRING" if f.args.len() >= 2 => match target {
16089                                DialectType::Presto | DialectType::Trino => {
16090                                    Ok(Expression::Function(Box::new(Function::new(
16091                                        "ARRAY_JOIN".to_string(),
16092                                        f.args,
16093                                    ))))
16094                                }
16095                                DialectType::TSQL => Ok(Expression::Function(Box::new(
16096                                    Function::new("STRING_AGG".to_string(), f.args),
16097                                ))),
16098                                _ => Ok(Expression::Function(f)),
16099                            },
16100                            // ARRAY_CONCAT / LIST_CONCAT -> target-specific
16101                            "ARRAY_CONCAT" | "LIST_CONCAT" if f.args.len() == 2 => match target {
16102                                DialectType::Spark
16103                                | DialectType::Databricks
16104                                | DialectType::Hive => Ok(Expression::Function(Box::new(
16105                                    Function::new("CONCAT".to_string(), f.args),
16106                                ))),
16107                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
16108                                    Function::new("ARRAY_CAT".to_string(), f.args),
16109                                ))),
16110                                DialectType::Redshift => Ok(Expression::Function(Box::new(
16111                                    Function::new("ARRAY_CONCAT".to_string(), f.args),
16112                                ))),
16113                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
16114                                    Function::new("ARRAY_CAT".to_string(), f.args),
16115                                ))),
16116                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
16117                                    Function::new("LIST_CONCAT".to_string(), f.args),
16118                                ))),
16119                                DialectType::Presto | DialectType::Trino => {
16120                                    Ok(Expression::Function(Box::new(Function::new(
16121                                        "CONCAT".to_string(),
16122                                        f.args,
16123                                    ))))
16124                                }
16125                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
16126                                    Function::new("ARRAY_CONCAT".to_string(), f.args),
16127                                ))),
16128                                _ => Ok(Expression::Function(f)),
16129                            },
16130                            // ARRAY_CONTAINS(arr, x) / HAS(arr, x) / CONTAINS(arr, x) normalization
16131                            "HAS" if f.args.len() == 2 => match target {
16132                                DialectType::Spark
16133                                | DialectType::Databricks
16134                                | DialectType::Hive => Ok(Expression::Function(Box::new(
16135                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
16136                                ))),
16137                                DialectType::Presto | DialectType::Trino => {
16138                                    Ok(Expression::Function(Box::new(Function::new(
16139                                        "CONTAINS".to_string(),
16140                                        f.args,
16141                                    ))))
16142                                }
16143                                _ => Ok(Expression::Function(f)),
16144                            },
16145                            // NVL(a, b, c, d) -> COALESCE(a, b, c, d) - NVL should keep all args
16146                            "NVL" if f.args.len() > 2 => Ok(Expression::Function(Box::new(
16147                                Function::new("COALESCE".to_string(), f.args),
16148                            ))),
16149                            // ISNULL(x) in MySQL -> (x IS NULL)
16150                            "ISNULL"
16151                                if f.args.len() == 1
16152                                    && matches!(source, DialectType::MySQL)
16153                                    && matches!(target, DialectType::MySQL) =>
16154                            {
16155                                let arg = f.args.into_iter().next().unwrap();
16156                                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
16157                                    this: Expression::IsNull(Box::new(
16158                                        crate::expressions::IsNull {
16159                                            this: arg,
16160                                            not: false,
16161                                            postfix_form: false,
16162                                        },
16163                                    )),
16164                                    trailing_comments: Vec::new(),
16165                                })))
16166                            }
16167                            // MONTHNAME(x) -> DATE_FORMAT(x, '%M') for MySQL -> MySQL
16168                            "MONTHNAME"
16169                                if f.args.len() == 1 && matches!(target, DialectType::MySQL) =>
16170                            {
16171                                let arg = f.args.into_iter().next().unwrap();
16172                                Ok(Expression::Function(Box::new(Function::new(
16173                                    "DATE_FORMAT".to_string(),
16174                                    vec![arg, Expression::string("%M")],
16175                                ))))
16176                            }
16177                            // ClickHouse splitByString('s', x) -> DuckDB STR_SPLIT(x, 's') / Hive SPLIT(x, CONCAT('\\Q', 's', '\\E'))
16178                            "SPLITBYSTRING" if f.args.len() == 2 => {
16179                                let sep = f.args[0].clone();
16180                                let str_arg = f.args[1].clone();
16181                                match target {
16182                                    DialectType::DuckDB => Ok(Expression::Function(Box::new(
16183                                        Function::new("STR_SPLIT".to_string(), vec![str_arg, sep]),
16184                                    ))),
16185                                    DialectType::Doris => {
16186                                        Ok(Expression::Function(Box::new(Function::new(
16187                                            "SPLIT_BY_STRING".to_string(),
16188                                            vec![str_arg, sep],
16189                                        ))))
16190                                    }
16191                                    DialectType::Hive
16192                                    | DialectType::Spark
16193                                    | DialectType::Databricks => {
16194                                        // SPLIT(x, CONCAT('\\Q', sep, '\\E'))
16195                                        let escaped =
16196                                            Expression::Function(Box::new(Function::new(
16197                                                "CONCAT".to_string(),
16198                                                vec![
16199                                                    Expression::string("\\Q"),
16200                                                    sep,
16201                                                    Expression::string("\\E"),
16202                                                ],
16203                                            )));
16204                                        Ok(Expression::Function(Box::new(Function::new(
16205                                            "SPLIT".to_string(),
16206                                            vec![str_arg, escaped],
16207                                        ))))
16208                                    }
16209                                    _ => Ok(Expression::Function(f)),
16210                                }
16211                            }
16212                            // ClickHouse splitByRegexp('pattern', x) -> DuckDB STR_SPLIT_REGEX(x, 'pattern')
16213                            "SPLITBYREGEXP" if f.args.len() == 2 => {
16214                                let sep = f.args[0].clone();
16215                                let str_arg = f.args[1].clone();
16216                                match target {
16217                                    DialectType::DuckDB => {
16218                                        Ok(Expression::Function(Box::new(Function::new(
16219                                            "STR_SPLIT_REGEX".to_string(),
16220                                            vec![str_arg, sep],
16221                                        ))))
16222                                    }
16223                                    DialectType::Hive
16224                                    | DialectType::Spark
16225                                    | DialectType::Databricks => {
16226                                        Ok(Expression::Function(Box::new(Function::new(
16227                                            "SPLIT".to_string(),
16228                                            vec![str_arg, sep],
16229                                        ))))
16230                                    }
16231                                    _ => Ok(Expression::Function(f)),
16232                                }
16233                            }
16234                            // ClickHouse toMonday(x) -> DATE_TRUNC('WEEK', x) / DATE_TRUNC(x, 'WEEK') for Doris
16235                            "TOMONDAY" => {
16236                                if f.args.len() == 1 {
16237                                    let arg = f.args.into_iter().next().unwrap();
16238                                    match target {
16239                                        DialectType::Doris => {
16240                                            Ok(Expression::Function(Box::new(Function::new(
16241                                                "DATE_TRUNC".to_string(),
16242                                                vec![arg, Expression::string("WEEK")],
16243                                            ))))
16244                                        }
16245                                        _ => Ok(Expression::Function(Box::new(Function::new(
16246                                            "DATE_TRUNC".to_string(),
16247                                            vec![Expression::string("WEEK"), arg],
16248                                        )))),
16249                                    }
16250                                } else {
16251                                    Ok(Expression::Function(f))
16252                                }
16253                            }
16254                            // COLLECT_LIST with FILTER(WHERE x IS NOT NULL) for targets that need it
16255                            "COLLECT_LIST" if f.args.len() == 1 => match target {
16256                                DialectType::Spark
16257                                | DialectType::Databricks
16258                                | DialectType::Hive => Ok(Expression::Function(f)),
16259                                _ => Ok(Expression::Function(Box::new(Function::new(
16260                                    "ARRAY_AGG".to_string(),
16261                                    f.args,
16262                                )))),
16263                            },
16264                            // TO_CHAR(x) with 1 arg -> CAST(x AS STRING) for Doris
16265                            "TO_CHAR"
16266                                if f.args.len() == 1 && matches!(target, DialectType::Doris) =>
16267                            {
16268                                let arg = f.args.into_iter().next().unwrap();
16269                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
16270                                    this: arg,
16271                                    to: DataType::Custom {
16272                                        name: "STRING".to_string(),
16273                                    },
16274                                    double_colon_syntax: false,
16275                                    trailing_comments: Vec::new(),
16276                                    format: None,
16277                                    default: None,
16278                                    inferred_type: None,
16279                                })))
16280                            }
16281                            // DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL
16282                            "DBMS_RANDOM.VALUE" if f.args.is_empty() => match target {
16283                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
16284                                    Function::new("RANDOM".to_string(), vec![]),
16285                                ))),
16286                                _ => Ok(Expression::Function(f)),
16287                            },
16288                            // ClickHouse formatDateTime -> target-specific
16289                            "FORMATDATETIME" if f.args.len() >= 2 => match target {
16290                                DialectType::MySQL => Ok(Expression::Function(Box::new(
16291                                    Function::new("DATE_FORMAT".to_string(), f.args),
16292                                ))),
16293                                _ => Ok(Expression::Function(f)),
16294                            },
16295                            // REPLICATE('x', n) -> REPEAT('x', n) for non-TSQL targets
16296                            "REPLICATE" if f.args.len() == 2 => match target {
16297                                DialectType::TSQL => Ok(Expression::Function(f)),
16298                                _ => Ok(Expression::Function(Box::new(Function::new(
16299                                    "REPEAT".to_string(),
16300                                    f.args,
16301                                )))),
16302                            },
16303                            // LEN(x) -> LENGTH(x) for non-TSQL targets
16304                            // No CAST needed when arg is already a string literal
16305                            "LEN" if f.args.len() == 1 => {
16306                                match target {
16307                                    DialectType::TSQL => Ok(Expression::Function(f)),
16308                                    DialectType::Spark | DialectType::Databricks => {
16309                                        let arg = f.args.into_iter().next().unwrap();
16310                                        // Don't wrap string literals with CAST - they're already strings
16311                                        let is_string = matches!(
16312                                            &arg,
16313                                            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
16314                                        );
16315                                        let final_arg = if is_string {
16316                                            arg
16317                                        } else {
16318                                            Expression::Cast(Box::new(Cast {
16319                                                this: arg,
16320                                                to: DataType::VarChar {
16321                                                    length: None,
16322                                                    parenthesized_length: false,
16323                                                },
16324                                                double_colon_syntax: false,
16325                                                trailing_comments: Vec::new(),
16326                                                format: None,
16327                                                default: None,
16328                                                inferred_type: None,
16329                                            }))
16330                                        };
16331                                        Ok(Expression::Function(Box::new(Function::new(
16332                                            "LENGTH".to_string(),
16333                                            vec![final_arg],
16334                                        ))))
16335                                    }
16336                                    _ => {
16337                                        let arg = f.args.into_iter().next().unwrap();
16338                                        Ok(Expression::Function(Box::new(Function::new(
16339                                            "LENGTH".to_string(),
16340                                            vec![arg],
16341                                        ))))
16342                                    }
16343                                }
16344                            }
16345                            // COUNT_BIG(x) -> COUNT(x) for non-TSQL targets
16346                            "COUNT_BIG" if f.args.len() == 1 => match target {
16347                                DialectType::TSQL => Ok(Expression::Function(f)),
16348                                _ => Ok(Expression::Function(Box::new(Function::new(
16349                                    "COUNT".to_string(),
16350                                    f.args,
16351                                )))),
16352                            },
16353                            // DATEFROMPARTS(y, m, d) -> MAKE_DATE(y, m, d) for non-TSQL targets
16354                            "DATEFROMPARTS" if f.args.len() == 3 => match target {
16355                                DialectType::TSQL => Ok(Expression::Function(f)),
16356                                _ => Ok(Expression::Function(Box::new(Function::new(
16357                                    "MAKE_DATE".to_string(),
16358                                    f.args,
16359                                )))),
16360                            },
16361                            // REGEXP_LIKE(str, pattern) -> RegexpLike expression (target-specific output)
16362                            "REGEXP_LIKE" if f.args.len() >= 2 => {
16363                                let str_expr = f.args[0].clone();
16364                                let pattern = f.args[1].clone();
16365                                let flags = if f.args.len() >= 3 {
16366                                    Some(f.args[2].clone())
16367                                } else {
16368                                    None
16369                                };
16370                                match target {
16371                                    DialectType::DuckDB => {
16372                                        let mut new_args = vec![str_expr, pattern];
16373                                        if let Some(fl) = flags {
16374                                            new_args.push(fl);
16375                                        }
16376                                        Ok(Expression::Function(Box::new(Function::new(
16377                                            "REGEXP_MATCHES".to_string(),
16378                                            new_args,
16379                                        ))))
16380                                    }
16381                                    _ => Ok(Expression::RegexpLike(Box::new(
16382                                        crate::expressions::RegexpFunc {
16383                                            this: str_expr,
16384                                            pattern,
16385                                            flags,
16386                                        },
16387                                    ))),
16388                                }
16389                            }
16390                            // ClickHouse arrayJoin -> UNNEST for PostgreSQL
16391                            "ARRAYJOIN" if f.args.len() == 1 => match target {
16392                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
16393                                    Function::new("UNNEST".to_string(), f.args),
16394                                ))),
16395                                _ => Ok(Expression::Function(f)),
16396                            },
16397                            // DATETIMEFROMPARTS(y, m, d, h, mi, s, ms) -> MAKE_TIMESTAMP / TIMESTAMP_FROM_PARTS
16398                            "DATETIMEFROMPARTS" if f.args.len() == 7 => {
16399                                match target {
16400                                    DialectType::TSQL => Ok(Expression::Function(f)),
16401                                    DialectType::DuckDB => {
16402                                        // MAKE_TIMESTAMP(y, m, d, h, mi, s + (ms / 1000.0))
16403                                        let mut args = f.args;
16404                                        let ms = args.pop().unwrap();
16405                                        let s = args.pop().unwrap();
16406                                        // s + (ms / 1000.0)
16407                                        let ms_frac = Expression::Div(Box::new(BinaryOp::new(
16408                                            ms,
16409                                            Expression::Literal(Box::new(
16410                                                crate::expressions::Literal::Number(
16411                                                    "1000.0".to_string(),
16412                                                ),
16413                                            )),
16414                                        )));
16415                                        let s_with_ms = Expression::Add(Box::new(BinaryOp::new(
16416                                            s,
16417                                            Expression::Paren(Box::new(Paren {
16418                                                this: ms_frac,
16419                                                trailing_comments: vec![],
16420                                            })),
16421                                        )));
16422                                        args.push(s_with_ms);
16423                                        Ok(Expression::Function(Box::new(Function::new(
16424                                            "MAKE_TIMESTAMP".to_string(),
16425                                            args,
16426                                        ))))
16427                                    }
16428                                    DialectType::Snowflake => {
16429                                        // TIMESTAMP_FROM_PARTS(y, m, d, h, mi, s, ms * 1000000)
16430                                        let mut args = f.args;
16431                                        let ms = args.pop().unwrap();
16432                                        // ms * 1000000
16433                                        let ns = Expression::Mul(Box::new(BinaryOp::new(
16434                                            ms,
16435                                            Expression::number(1000000),
16436                                        )));
16437                                        args.push(ns);
16438                                        Ok(Expression::Function(Box::new(Function::new(
16439                                            "TIMESTAMP_FROM_PARTS".to_string(),
16440                                            args,
16441                                        ))))
16442                                    }
16443                                    _ => {
16444                                        // Default: keep function name for other targets
16445                                        Ok(Expression::Function(Box::new(Function::new(
16446                                            "DATETIMEFROMPARTS".to_string(),
16447                                            f.args,
16448                                        ))))
16449                                    }
16450                                }
16451                            }
16452                            // CONVERT(type, expr [, style]) -> CAST(expr AS type) for non-TSQL targets
16453                            // TRY_CONVERT(type, expr [, style]) -> TRY_CAST(expr AS type) for non-TSQL targets
16454                            "CONVERT" | "TRY_CONVERT" if f.args.len() >= 2 => {
16455                                let is_try = name == "TRY_CONVERT";
16456                                let type_expr = f.args[0].clone();
16457                                let value_expr = f.args[1].clone();
16458                                let style = if f.args.len() >= 3 {
16459                                    Some(&f.args[2])
16460                                } else {
16461                                    None
16462                                };
16463
16464                                // For TSQL->TSQL, normalize types and preserve CONVERT/TRY_CONVERT
16465                                if matches!(target, DialectType::TSQL) {
16466                                    let normalized_type = match &type_expr {
16467                                        Expression::DataType(dt) => {
16468                                            let new_dt = match dt {
16469                                                DataType::Int { .. } => DataType::Custom {
16470                                                    name: "INTEGER".to_string(),
16471                                                },
16472                                                _ => dt.clone(),
16473                                            };
16474                                            Expression::DataType(new_dt)
16475                                        }
16476                                        Expression::Identifier(id) => {
16477                                            if id.name.eq_ignore_ascii_case("INT") {
16478                                                Expression::Identifier(
16479                                                    crate::expressions::Identifier::new("INTEGER"),
16480                                                )
16481                                            } else {
16482                                                let upper = id.name.to_ascii_uppercase();
16483                                                Expression::Identifier(
16484                                                    crate::expressions::Identifier::new(upper),
16485                                                )
16486                                            }
16487                                        }
16488                                        Expression::Column(col) => {
16489                                            if col.name.name.eq_ignore_ascii_case("INT") {
16490                                                Expression::Identifier(
16491                                                    crate::expressions::Identifier::new("INTEGER"),
16492                                                )
16493                                            } else {
16494                                                let upper = col.name.name.to_ascii_uppercase();
16495                                                Expression::Identifier(
16496                                                    crate::expressions::Identifier::new(upper),
16497                                                )
16498                                            }
16499                                        }
16500                                        _ => type_expr.clone(),
16501                                    };
16502                                    let func_name = if is_try { "TRY_CONVERT" } else { "CONVERT" };
16503                                    let mut new_args = vec![normalized_type, value_expr];
16504                                    if let Some(s) = style {
16505                                        new_args.push(s.clone());
16506                                    }
16507                                    return Ok(Expression::Function(Box::new(Function::new(
16508                                        func_name.to_string(),
16509                                        new_args,
16510                                    ))));
16511                                }
16512
16513                                // For other targets: CONVERT(type, expr) -> CAST(expr AS type)
16514                                fn expr_to_datatype(e: &Expression) -> Option<DataType> {
16515                                    match e {
16516                                        Expression::DataType(dt) => {
16517                                            // Convert NVARCHAR/NCHAR Custom types to standard VarChar/Char
16518                                            match dt {
16519                                                DataType::Custom { name }
16520                                                    if name.starts_with("NVARCHAR(")
16521                                                        || name.starts_with("NCHAR(") =>
16522                                                {
16523                                                    // Extract the length from "NVARCHAR(200)" or "NCHAR(40)"
16524                                                    let inner = &name[name.find('(').unwrap() + 1
16525                                                        ..name.len() - 1];
16526                                                    if inner.eq_ignore_ascii_case("MAX") {
16527                                                        Some(DataType::Text)
16528                                                    } else if let Ok(len) = inner.parse::<u32>() {
16529                                                        if name.starts_with("NCHAR") {
16530                                                            Some(DataType::Char {
16531                                                                length: Some(len),
16532                                                            })
16533                                                        } else {
16534                                                            Some(DataType::VarChar {
16535                                                                length: Some(len),
16536                                                                parenthesized_length: false,
16537                                                            })
16538                                                        }
16539                                                    } else {
16540                                                        Some(dt.clone())
16541                                                    }
16542                                                }
16543                                                DataType::Custom { name } if name == "NVARCHAR" => {
16544                                                    Some(DataType::VarChar {
16545                                                        length: None,
16546                                                        parenthesized_length: false,
16547                                                    })
16548                                                }
16549                                                DataType::Custom { name } if name == "NCHAR" => {
16550                                                    Some(DataType::Char { length: None })
16551                                                }
16552                                                DataType::Custom { name }
16553                                                    if name == "NVARCHAR(MAX)"
16554                                                        || name == "VARCHAR(MAX)" =>
16555                                                {
16556                                                    Some(DataType::Text)
16557                                                }
16558                                                _ => Some(dt.clone()),
16559                                            }
16560                                        }
16561                                        Expression::Identifier(id) => {
16562                                            let name = id.name.to_ascii_uppercase();
16563                                            match name.as_str() {
16564                                                "INT" | "INTEGER" => Some(DataType::Int {
16565                                                    length: None,
16566                                                    integer_spelling: false,
16567                                                }),
16568                                                "BIGINT" => Some(DataType::BigInt { length: None }),
16569                                                "SMALLINT" => {
16570                                                    Some(DataType::SmallInt { length: None })
16571                                                }
16572                                                "TINYINT" => {
16573                                                    Some(DataType::TinyInt { length: None })
16574                                                }
16575                                                "FLOAT" => Some(DataType::Float {
16576                                                    precision: None,
16577                                                    scale: None,
16578                                                    real_spelling: false,
16579                                                }),
16580                                                "REAL" => Some(DataType::Float {
16581                                                    precision: None,
16582                                                    scale: None,
16583                                                    real_spelling: true,
16584                                                }),
16585                                                "DATETIME" | "DATETIME2" => {
16586                                                    Some(DataType::Timestamp {
16587                                                        timezone: false,
16588                                                        precision: None,
16589                                                    })
16590                                                }
16591                                                "DATE" => Some(DataType::Date),
16592                                                "BIT" => Some(DataType::Boolean),
16593                                                "TEXT" => Some(DataType::Text),
16594                                                "NUMERIC" => Some(DataType::Decimal {
16595                                                    precision: None,
16596                                                    scale: None,
16597                                                }),
16598                                                "MONEY" => Some(DataType::Decimal {
16599                                                    precision: Some(15),
16600                                                    scale: Some(4),
16601                                                }),
16602                                                "SMALLMONEY" => Some(DataType::Decimal {
16603                                                    precision: Some(6),
16604                                                    scale: Some(4),
16605                                                }),
16606                                                "VARCHAR" => Some(DataType::VarChar {
16607                                                    length: None,
16608                                                    parenthesized_length: false,
16609                                                }),
16610                                                "NVARCHAR" => Some(DataType::VarChar {
16611                                                    length: None,
16612                                                    parenthesized_length: false,
16613                                                }),
16614                                                "CHAR" => Some(DataType::Char { length: None }),
16615                                                "NCHAR" => Some(DataType::Char { length: None }),
16616                                                _ => Some(DataType::Custom { name }),
16617                                            }
16618                                        }
16619                                        Expression::Column(col) => {
16620                                            let name = col.name.name.to_ascii_uppercase();
16621                                            match name.as_str() {
16622                                                "INT" | "INTEGER" => Some(DataType::Int {
16623                                                    length: None,
16624                                                    integer_spelling: false,
16625                                                }),
16626                                                "BIGINT" => Some(DataType::BigInt { length: None }),
16627                                                "FLOAT" => Some(DataType::Float {
16628                                                    precision: None,
16629                                                    scale: None,
16630                                                    real_spelling: false,
16631                                                }),
16632                                                "DATETIME" | "DATETIME2" => {
16633                                                    Some(DataType::Timestamp {
16634                                                        timezone: false,
16635                                                        precision: None,
16636                                                    })
16637                                                }
16638                                                "DATE" => Some(DataType::Date),
16639                                                "NUMERIC" => Some(DataType::Decimal {
16640                                                    precision: None,
16641                                                    scale: None,
16642                                                }),
16643                                                "VARCHAR" => Some(DataType::VarChar {
16644                                                    length: None,
16645                                                    parenthesized_length: false,
16646                                                }),
16647                                                "NVARCHAR" => Some(DataType::VarChar {
16648                                                    length: None,
16649                                                    parenthesized_length: false,
16650                                                }),
16651                                                "CHAR" => Some(DataType::Char { length: None }),
16652                                                "NCHAR" => Some(DataType::Char { length: None }),
16653                                                _ => Some(DataType::Custom { name }),
16654                                            }
16655                                        }
16656                                        // NVARCHAR(200) parsed as Function("NVARCHAR", [200])
16657                                        Expression::Function(f) => {
16658                                            let fname = f.name.to_ascii_uppercase();
16659                                            match fname.as_str() {
16660                                                "VARCHAR" | "NVARCHAR" => {
16661                                                    let len = f.args.first().and_then(|a| {
16662                                                        if let Expression::Literal(lit) = a
16663                                                        {
16664                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
16665                                                            n.parse::<u32>().ok()
16666                                                        } else { None }
16667                                                        } else if let Expression::Identifier(id) = a
16668                                                        {
16669                                                            if id.name.eq_ignore_ascii_case("MAX") {
16670                                                                None
16671                                                            } else {
16672                                                                None
16673                                                            }
16674                                                        } else {
16675                                                            None
16676                                                        }
16677                                                    });
16678                                                    // Check for VARCHAR(MAX) -> TEXT
16679                                                    let is_max = f.args.first().map_or(false, |a| {
16680                                                        matches!(a, Expression::Identifier(id) if id.name.eq_ignore_ascii_case("MAX"))
16681                                                        || matches!(a, Expression::Column(col) if col.name.name.eq_ignore_ascii_case("MAX"))
16682                                                    });
16683                                                    if is_max {
16684                                                        Some(DataType::Text)
16685                                                    } else {
16686                                                        Some(DataType::VarChar {
16687                                                            length: len,
16688                                                            parenthesized_length: false,
16689                                                        })
16690                                                    }
16691                                                }
16692                                                "NCHAR" | "CHAR" => {
16693                                                    let len = f.args.first().and_then(|a| {
16694                                                        if let Expression::Literal(lit) = a
16695                                                        {
16696                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
16697                                                            n.parse::<u32>().ok()
16698                                                        } else { None }
16699                                                        } else {
16700                                                            None
16701                                                        }
16702                                                    });
16703                                                    Some(DataType::Char { length: len })
16704                                                }
16705                                                "NUMERIC" | "DECIMAL" => {
16706                                                    let precision = f.args.first().and_then(|a| {
16707                                                        if let Expression::Literal(lit) = a
16708                                                        {
16709                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
16710                                                            n.parse::<u32>().ok()
16711                                                        } else { None }
16712                                                        } else {
16713                                                            None
16714                                                        }
16715                                                    });
16716                                                    let scale = f.args.get(1).and_then(|a| {
16717                                                        if let Expression::Literal(lit) = a
16718                                                        {
16719                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
16720                                                            n.parse::<u32>().ok()
16721                                                        } else { None }
16722                                                        } else {
16723                                                            None
16724                                                        }
16725                                                    });
16726                                                    Some(DataType::Decimal { precision, scale })
16727                                                }
16728                                                _ => None,
16729                                            }
16730                                        }
16731                                        _ => None,
16732                                    }
16733                                }
16734
16735                                if let Some(mut dt) = expr_to_datatype(&type_expr) {
16736                                    // For TSQL source: VARCHAR/CHAR without length defaults to 30
16737                                    let is_tsql_source =
16738                                        matches!(source, DialectType::TSQL | DialectType::Fabric);
16739                                    if is_tsql_source {
16740                                        match &dt {
16741                                            DataType::VarChar { length: None, .. } => {
16742                                                dt = DataType::VarChar {
16743                                                    length: Some(30),
16744                                                    parenthesized_length: false,
16745                                                };
16746                                            }
16747                                            DataType::Char { length: None } => {
16748                                                dt = DataType::Char { length: Some(30) };
16749                                            }
16750                                            _ => {}
16751                                        }
16752                                    }
16753
16754                                    // Determine if this is a string type
16755                                    let is_string_type = matches!(
16756                                        dt,
16757                                        DataType::VarChar { .. }
16758                                            | DataType::Char { .. }
16759                                            | DataType::Text
16760                                    ) || matches!(&dt, DataType::Custom { name } if name == "NVARCHAR" || name == "NCHAR"
16761                                            || name.starts_with("NVARCHAR(") || name.starts_with("NCHAR(")
16762                                            || name.starts_with("VARCHAR(") || name == "VARCHAR"
16763                                            || name == "STRING");
16764
16765                                    // Determine if this is a date/time type
16766                                    let is_datetime_type = matches!(
16767                                        dt,
16768                                        DataType::Timestamp { .. } | DataType::Date
16769                                    ) || matches!(&dt, DataType::Custom { name } if name == "DATETIME"
16770                                            || name == "DATETIME2" || name == "SMALLDATETIME");
16771
16772                                    // Check for date conversion with style
16773                                    if style.is_some() {
16774                                        let style_num = style.and_then(|s| {
16775                                            if let Expression::Literal(lit) = s {
16776                                                if let crate::expressions::Literal::Number(n) =
16777                                                    lit.as_ref()
16778                                                {
16779                                                    n.parse::<u32>().ok()
16780                                                } else {
16781                                                    None
16782                                                }
16783                                            } else {
16784                                                None
16785                                            }
16786                                        });
16787
16788                                        // TSQL CONVERT date styles (Java format)
16789                                        let format_str = style_num.and_then(|n| match n {
16790                                            101 => Some("MM/dd/yyyy"),
16791                                            102 => Some("yyyy.MM.dd"),
16792                                            103 => Some("dd/MM/yyyy"),
16793                                            104 => Some("dd.MM.yyyy"),
16794                                            105 => Some("dd-MM-yyyy"),
16795                                            108 => Some("HH:mm:ss"),
16796                                            110 => Some("MM-dd-yyyy"),
16797                                            112 => Some("yyyyMMdd"),
16798                                            120 | 20 => Some("yyyy-MM-dd HH:mm:ss"),
16799                                            121 | 21 => Some("yyyy-MM-dd HH:mm:ss.SSSSSS"),
16800                                            126 | 127 => Some("yyyy-MM-dd'T'HH:mm:ss.SSS"),
16801                                            _ => None,
16802                                        });
16803
16804                                        // Non-string, non-datetime types with style: just CAST, ignore the style
16805                                        if !is_string_type && !is_datetime_type {
16806                                            let cast_expr = if is_try {
16807                                                Expression::TryCast(Box::new(
16808                                                    crate::expressions::Cast {
16809                                                        this: value_expr,
16810                                                        to: dt,
16811                                                        trailing_comments: Vec::new(),
16812                                                        double_colon_syntax: false,
16813                                                        format: None,
16814                                                        default: None,
16815                                                        inferred_type: None,
16816                                                    },
16817                                                ))
16818                                            } else {
16819                                                Expression::Cast(Box::new(
16820                                                    crate::expressions::Cast {
16821                                                        this: value_expr,
16822                                                        to: dt,
16823                                                        trailing_comments: Vec::new(),
16824                                                        double_colon_syntax: false,
16825                                                        format: None,
16826                                                        default: None,
16827                                                        inferred_type: None,
16828                                                    },
16829                                                ))
16830                                            };
16831                                            return Ok(cast_expr);
16832                                        }
16833
16834                                        if let Some(java_fmt) = format_str {
16835                                            let c_fmt = java_fmt
16836                                                .replace("yyyy", "%Y")
16837                                                .replace("MM", "%m")
16838                                                .replace("dd", "%d")
16839                                                .replace("HH", "%H")
16840                                                .replace("mm", "%M")
16841                                                .replace("ss", "%S")
16842                                                .replace("SSSSSS", "%f")
16843                                                .replace("SSS", "%f")
16844                                                .replace("'T'", "T");
16845
16846                                            // For datetime target types: style is the INPUT format for parsing strings -> dates
16847                                            if is_datetime_type {
16848                                                match target {
16849                                                    DialectType::DuckDB => {
16850                                                        return Ok(Expression::Function(Box::new(
16851                                                            Function::new(
16852                                                                "STRPTIME".to_string(),
16853                                                                vec![
16854                                                                    value_expr,
16855                                                                    Expression::string(&c_fmt),
16856                                                                ],
16857                                                            ),
16858                                                        )));
16859                                                    }
16860                                                    DialectType::Spark
16861                                                    | DialectType::Databricks => {
16862                                                        // CONVERT(DATETIME, x, style) -> TO_TIMESTAMP(x, fmt)
16863                                                        // CONVERT(DATE, x, style) -> TO_DATE(x, fmt)
16864                                                        let func_name =
16865                                                            if matches!(dt, DataType::Date) {
16866                                                                "TO_DATE"
16867                                                            } else {
16868                                                                "TO_TIMESTAMP"
16869                                                            };
16870                                                        return Ok(Expression::Function(Box::new(
16871                                                            Function::new(
16872                                                                func_name.to_string(),
16873                                                                vec![
16874                                                                    value_expr,
16875                                                                    Expression::string(java_fmt),
16876                                                                ],
16877                                                            ),
16878                                                        )));
16879                                                    }
16880                                                    DialectType::Hive => {
16881                                                        return Ok(Expression::Function(Box::new(
16882                                                            Function::new(
16883                                                                "TO_TIMESTAMP".to_string(),
16884                                                                vec![
16885                                                                    value_expr,
16886                                                                    Expression::string(java_fmt),
16887                                                                ],
16888                                                            ),
16889                                                        )));
16890                                                    }
16891                                                    _ => {
16892                                                        return Ok(Expression::Cast(Box::new(
16893                                                            crate::expressions::Cast {
16894                                                                this: value_expr,
16895                                                                to: dt,
16896                                                                trailing_comments: Vec::new(),
16897                                                                double_colon_syntax: false,
16898                                                                format: None,
16899                                                                default: None,
16900                                                                inferred_type: None,
16901                                                            },
16902                                                        )));
16903                                                    }
16904                                                }
16905                                            }
16906
16907                                            // For string target types: style is the OUTPUT format for dates -> strings
16908                                            match target {
16909                                                DialectType::DuckDB => Ok(Expression::Function(
16910                                                    Box::new(Function::new(
16911                                                        "STRPTIME".to_string(),
16912                                                        vec![
16913                                                            value_expr,
16914                                                            Expression::string(&c_fmt),
16915                                                        ],
16916                                                    )),
16917                                                )),
16918                                                DialectType::Spark | DialectType::Databricks => {
16919                                                    // For string target types with style: CAST(DATE_FORMAT(x, fmt) AS type)
16920                                                    // Determine the target string type
16921                                                    let string_dt = match &dt {
16922                                                        DataType::VarChar {
16923                                                            length: Some(l),
16924                                                            ..
16925                                                        } => DataType::VarChar {
16926                                                            length: Some(*l),
16927                                                            parenthesized_length: false,
16928                                                        },
16929                                                        DataType::Text => DataType::Custom {
16930                                                            name: "STRING".to_string(),
16931                                                        },
16932                                                        _ => DataType::Custom {
16933                                                            name: "STRING".to_string(),
16934                                                        },
16935                                                    };
16936                                                    let date_format_expr = Expression::Function(
16937                                                        Box::new(Function::new(
16938                                                            "DATE_FORMAT".to_string(),
16939                                                            vec![
16940                                                                value_expr,
16941                                                                Expression::string(java_fmt),
16942                                                            ],
16943                                                        )),
16944                                                    );
16945                                                    let cast_expr = if is_try {
16946                                                        Expression::TryCast(Box::new(
16947                                                            crate::expressions::Cast {
16948                                                                this: date_format_expr,
16949                                                                to: string_dt,
16950                                                                trailing_comments: Vec::new(),
16951                                                                double_colon_syntax: false,
16952                                                                format: None,
16953                                                                default: None,
16954                                                                inferred_type: None,
16955                                                            },
16956                                                        ))
16957                                                    } else {
16958                                                        Expression::Cast(Box::new(
16959                                                            crate::expressions::Cast {
16960                                                                this: date_format_expr,
16961                                                                to: string_dt,
16962                                                                trailing_comments: Vec::new(),
16963                                                                double_colon_syntax: false,
16964                                                                format: None,
16965                                                                default: None,
16966                                                                inferred_type: None,
16967                                                            },
16968                                                        ))
16969                                                    };
16970                                                    Ok(cast_expr)
16971                                                }
16972                                                DialectType::MySQL | DialectType::SingleStore => {
16973                                                    // For MySQL: CAST(DATE_FORMAT(x, mysql_fmt) AS CHAR(n))
16974                                                    let mysql_fmt = java_fmt
16975                                                        .replace("yyyy", "%Y")
16976                                                        .replace("MM", "%m")
16977                                                        .replace("dd", "%d")
16978                                                        .replace("HH:mm:ss.SSSSSS", "%T")
16979                                                        .replace("HH:mm:ss", "%T")
16980                                                        .replace("HH", "%H")
16981                                                        .replace("mm", "%i")
16982                                                        .replace("ss", "%S");
16983                                                    let date_format_expr = Expression::Function(
16984                                                        Box::new(Function::new(
16985                                                            "DATE_FORMAT".to_string(),
16986                                                            vec![
16987                                                                value_expr,
16988                                                                Expression::string(&mysql_fmt),
16989                                                            ],
16990                                                        )),
16991                                                    );
16992                                                    // MySQL uses CHAR for string casts
16993                                                    let mysql_dt = match &dt {
16994                                                        DataType::VarChar { length, .. } => {
16995                                                            DataType::Char { length: *length }
16996                                                        }
16997                                                        _ => dt,
16998                                                    };
16999                                                    Ok(Expression::Cast(Box::new(
17000                                                        crate::expressions::Cast {
17001                                                            this: date_format_expr,
17002                                                            to: mysql_dt,
17003                                                            trailing_comments: Vec::new(),
17004                                                            double_colon_syntax: false,
17005                                                            format: None,
17006                                                            default: None,
17007                                                            inferred_type: None,
17008                                                        },
17009                                                    )))
17010                                                }
17011                                                DialectType::Hive => {
17012                                                    let func_name = "TO_TIMESTAMP";
17013                                                    Ok(Expression::Function(Box::new(
17014                                                        Function::new(
17015                                                            func_name.to_string(),
17016                                                            vec![
17017                                                                value_expr,
17018                                                                Expression::string(java_fmt),
17019                                                            ],
17020                                                        ),
17021                                                    )))
17022                                                }
17023                                                _ => Ok(Expression::Cast(Box::new(
17024                                                    crate::expressions::Cast {
17025                                                        this: value_expr,
17026                                                        to: dt,
17027                                                        trailing_comments: Vec::new(),
17028                                                        double_colon_syntax: false,
17029                                                        format: None,
17030                                                        default: None,
17031                                                        inferred_type: None,
17032                                                    },
17033                                                ))),
17034                                            }
17035                                        } else {
17036                                            // Unknown style, just CAST
17037                                            let cast_expr = if is_try {
17038                                                Expression::TryCast(Box::new(
17039                                                    crate::expressions::Cast {
17040                                                        this: value_expr,
17041                                                        to: dt,
17042                                                        trailing_comments: Vec::new(),
17043                                                        double_colon_syntax: false,
17044                                                        format: None,
17045                                                        default: None,
17046                                                        inferred_type: None,
17047                                                    },
17048                                                ))
17049                                            } else {
17050                                                Expression::Cast(Box::new(
17051                                                    crate::expressions::Cast {
17052                                                        this: value_expr,
17053                                                        to: dt,
17054                                                        trailing_comments: Vec::new(),
17055                                                        double_colon_syntax: false,
17056                                                        format: None,
17057                                                        default: None,
17058                                                        inferred_type: None,
17059                                                    },
17060                                                ))
17061                                            };
17062                                            Ok(cast_expr)
17063                                        }
17064                                    } else {
17065                                        // No style - simple CAST
17066                                        let final_dt = if matches!(
17067                                            target,
17068                                            DialectType::MySQL | DialectType::SingleStore
17069                                        ) {
17070                                            match &dt {
17071                                                DataType::Int { .. }
17072                                                | DataType::BigInt { .. }
17073                                                | DataType::SmallInt { .. }
17074                                                | DataType::TinyInt { .. } => DataType::Custom {
17075                                                    name: "SIGNED".to_string(),
17076                                                },
17077                                                DataType::VarChar { length, .. } => {
17078                                                    DataType::Char { length: *length }
17079                                                }
17080                                                _ => dt,
17081                                            }
17082                                        } else {
17083                                            dt
17084                                        };
17085                                        let cast_expr = if is_try {
17086                                            Expression::TryCast(Box::new(
17087                                                crate::expressions::Cast {
17088                                                    this: value_expr,
17089                                                    to: final_dt,
17090                                                    trailing_comments: Vec::new(),
17091                                                    double_colon_syntax: false,
17092                                                    format: None,
17093                                                    default: None,
17094                                                    inferred_type: None,
17095                                                },
17096                                            ))
17097                                        } else {
17098                                            Expression::Cast(Box::new(crate::expressions::Cast {
17099                                                this: value_expr,
17100                                                to: final_dt,
17101                                                trailing_comments: Vec::new(),
17102                                                double_colon_syntax: false,
17103                                                format: None,
17104                                                default: None,
17105                                                inferred_type: None,
17106                                            }))
17107                                        };
17108                                        Ok(cast_expr)
17109                                    }
17110                                } else {
17111                                    // Can't convert type expression - keep as CONVERT/TRY_CONVERT function
17112                                    Ok(Expression::Function(f))
17113                                }
17114                            }
17115                            // STRFTIME(val, fmt) from DuckDB / STRFTIME(fmt, val) from SQLite -> target-specific
17116                            "STRFTIME" if f.args.len() == 2 => {
17117                                // SQLite uses STRFTIME(fmt, val); DuckDB uses STRFTIME(val, fmt)
17118                                let (val, fmt_expr) = if matches!(source, DialectType::SQLite) {
17119                                    // SQLite: args[0] = format, args[1] = value
17120                                    (f.args[1].clone(), &f.args[0])
17121                                } else {
17122                                    // DuckDB and others: args[0] = value, args[1] = format
17123                                    (f.args[0].clone(), &f.args[1])
17124                                };
17125
17126                                // Helper to convert C-style format to Java-style
17127                                fn c_to_java_format(fmt: &str) -> String {
17128                                    fmt.replace("%Y", "yyyy")
17129                                        .replace("%m", "MM")
17130                                        .replace("%d", "dd")
17131                                        .replace("%H", "HH")
17132                                        .replace("%M", "mm")
17133                                        .replace("%S", "ss")
17134                                        .replace("%f", "SSSSSS")
17135                                        .replace("%y", "yy")
17136                                        .replace("%-m", "M")
17137                                        .replace("%-d", "d")
17138                                        .replace("%-H", "H")
17139                                        .replace("%-I", "h")
17140                                        .replace("%I", "hh")
17141                                        .replace("%p", "a")
17142                                        .replace("%j", "DDD")
17143                                        .replace("%a", "EEE")
17144                                        .replace("%b", "MMM")
17145                                        .replace("%F", "yyyy-MM-dd")
17146                                        .replace("%T", "HH:mm:ss")
17147                                }
17148
17149                                // Helper: recursively convert format strings within expressions (handles CONCAT)
17150                                fn convert_fmt_expr(
17151                                    expr: &Expression,
17152                                    converter: &dyn Fn(&str) -> String,
17153                                ) -> Expression {
17154                                    match expr {
17155                                        Expression::Literal(lit)
17156                                            if matches!(
17157                                                lit.as_ref(),
17158                                                crate::expressions::Literal::String(_)
17159                                            ) =>
17160                                        {
17161                                            let crate::expressions::Literal::String(s) =
17162                                                lit.as_ref()
17163                                            else {
17164                                                unreachable!()
17165                                            };
17166                                            Expression::string(&converter(s))
17167                                        }
17168                                        Expression::Function(func)
17169                                            if func.name.eq_ignore_ascii_case("CONCAT") =>
17170                                        {
17171                                            let new_args: Vec<Expression> = func
17172                                                .args
17173                                                .iter()
17174                                                .map(|a| convert_fmt_expr(a, converter))
17175                                                .collect();
17176                                            Expression::Function(Box::new(Function::new(
17177                                                "CONCAT".to_string(),
17178                                                new_args,
17179                                            )))
17180                                        }
17181                                        other => other.clone(),
17182                                    }
17183                                }
17184
17185                                match target {
17186                                    DialectType::DuckDB => {
17187                                        if matches!(source, DialectType::SQLite) {
17188                                            // SQLite STRFTIME(fmt, val) -> DuckDB STRFTIME(CAST(val AS TIMESTAMP), fmt)
17189                                            let cast_val = Expression::Cast(Box::new(Cast {
17190                                                this: val,
17191                                                to: crate::expressions::DataType::Timestamp {
17192                                                    precision: None,
17193                                                    timezone: false,
17194                                                },
17195                                                trailing_comments: Vec::new(),
17196                                                double_colon_syntax: false,
17197                                                format: None,
17198                                                default: None,
17199                                                inferred_type: None,
17200                                            }));
17201                                            Ok(Expression::Function(Box::new(Function::new(
17202                                                "STRFTIME".to_string(),
17203                                                vec![cast_val, fmt_expr.clone()],
17204                                            ))))
17205                                        } else {
17206                                            Ok(Expression::Function(f))
17207                                        }
17208                                    }
17209                                    DialectType::Spark
17210                                    | DialectType::Databricks
17211                                    | DialectType::Hive => {
17212                                        // STRFTIME(val, fmt) -> DATE_FORMAT(val, java_fmt)
17213                                        let converted_fmt =
17214                                            convert_fmt_expr(fmt_expr, &c_to_java_format);
17215                                        Ok(Expression::Function(Box::new(Function::new(
17216                                            "DATE_FORMAT".to_string(),
17217                                            vec![val, converted_fmt],
17218                                        ))))
17219                                    }
17220                                    DialectType::TSQL | DialectType::Fabric => {
17221                                        // STRFTIME(val, fmt) -> FORMAT(val, java_fmt)
17222                                        let converted_fmt =
17223                                            convert_fmt_expr(fmt_expr, &c_to_java_format);
17224                                        Ok(Expression::Function(Box::new(Function::new(
17225                                            "FORMAT".to_string(),
17226                                            vec![val, converted_fmt],
17227                                        ))))
17228                                    }
17229                                    DialectType::Presto
17230                                    | DialectType::Trino
17231                                    | DialectType::Athena => {
17232                                        // STRFTIME(val, fmt) -> DATE_FORMAT(val, presto_fmt) (convert DuckDB format to Presto)
17233                                        if let Expression::Literal(lit) = fmt_expr {
17234                                            if let crate::expressions::Literal::String(s) =
17235                                                lit.as_ref()
17236                                            {
17237                                                let presto_fmt = duckdb_to_presto_format(s);
17238                                                Ok(Expression::Function(Box::new(Function::new(
17239                                                    "DATE_FORMAT".to_string(),
17240                                                    vec![val, Expression::string(&presto_fmt)],
17241                                                ))))
17242                                            } else {
17243                                                Ok(Expression::Function(Box::new(Function::new(
17244                                                    "DATE_FORMAT".to_string(),
17245                                                    vec![val, fmt_expr.clone()],
17246                                                ))))
17247                                            }
17248                                        } else {
17249                                            Ok(Expression::Function(Box::new(Function::new(
17250                                                "DATE_FORMAT".to_string(),
17251                                                vec![val, fmt_expr.clone()],
17252                                            ))))
17253                                        }
17254                                    }
17255                                    DialectType::BigQuery => {
17256                                        // STRFTIME(val, fmt) -> FORMAT_DATE(bq_fmt, val) - note reversed arg order
17257                                        if let Expression::Literal(lit) = fmt_expr {
17258                                            if let crate::expressions::Literal::String(s) =
17259                                                lit.as_ref()
17260                                            {
17261                                                let bq_fmt = duckdb_to_bigquery_format(s);
17262                                                Ok(Expression::Function(Box::new(Function::new(
17263                                                    "FORMAT_DATE".to_string(),
17264                                                    vec![Expression::string(&bq_fmt), val],
17265                                                ))))
17266                                            } else {
17267                                                Ok(Expression::Function(Box::new(Function::new(
17268                                                    "FORMAT_DATE".to_string(),
17269                                                    vec![fmt_expr.clone(), val],
17270                                                ))))
17271                                            }
17272                                        } else {
17273                                            Ok(Expression::Function(Box::new(Function::new(
17274                                                "FORMAT_DATE".to_string(),
17275                                                vec![fmt_expr.clone(), val],
17276                                            ))))
17277                                        }
17278                                    }
17279                                    DialectType::PostgreSQL | DialectType::Redshift => {
17280                                        // STRFTIME(val, fmt) -> TO_CHAR(val, pg_fmt)
17281                                        if let Expression::Literal(lit) = fmt_expr {
17282                                            if let crate::expressions::Literal::String(s) =
17283                                                lit.as_ref()
17284                                            {
17285                                                let pg_fmt = s
17286                                                    .replace("%Y", "YYYY")
17287                                                    .replace("%m", "MM")
17288                                                    .replace("%d", "DD")
17289                                                    .replace("%H", "HH24")
17290                                                    .replace("%M", "MI")
17291                                                    .replace("%S", "SS")
17292                                                    .replace("%y", "YY")
17293                                                    .replace("%-m", "FMMM")
17294                                                    .replace("%-d", "FMDD")
17295                                                    .replace("%-H", "FMHH24")
17296                                                    .replace("%-I", "FMHH12")
17297                                                    .replace("%p", "AM")
17298                                                    .replace("%F", "YYYY-MM-DD")
17299                                                    .replace("%T", "HH24:MI:SS");
17300                                                Ok(Expression::Function(Box::new(Function::new(
17301                                                    "TO_CHAR".to_string(),
17302                                                    vec![val, Expression::string(&pg_fmt)],
17303                                                ))))
17304                                            } else {
17305                                                Ok(Expression::Function(Box::new(Function::new(
17306                                                    "TO_CHAR".to_string(),
17307                                                    vec![val, fmt_expr.clone()],
17308                                                ))))
17309                                            }
17310                                        } else {
17311                                            Ok(Expression::Function(Box::new(Function::new(
17312                                                "TO_CHAR".to_string(),
17313                                                vec![val, fmt_expr.clone()],
17314                                            ))))
17315                                        }
17316                                    }
17317                                    _ => Ok(Expression::Function(f)),
17318                                }
17319                            }
17320                            // STRPTIME(val, fmt) from DuckDB -> target-specific date parse function
17321                            "STRPTIME" if f.args.len() == 2 => {
17322                                let val = f.args[0].clone();
17323                                let fmt_expr = &f.args[1];
17324
17325                                fn c_to_java_format_parse(fmt: &str) -> String {
17326                                    fmt.replace("%Y", "yyyy")
17327                                        .replace("%m", "MM")
17328                                        .replace("%d", "dd")
17329                                        .replace("%H", "HH")
17330                                        .replace("%M", "mm")
17331                                        .replace("%S", "ss")
17332                                        .replace("%f", "SSSSSS")
17333                                        .replace("%y", "yy")
17334                                        .replace("%-m", "M")
17335                                        .replace("%-d", "d")
17336                                        .replace("%-H", "H")
17337                                        .replace("%-I", "h")
17338                                        .replace("%I", "hh")
17339                                        .replace("%p", "a")
17340                                        .replace("%F", "yyyy-MM-dd")
17341                                        .replace("%T", "HH:mm:ss")
17342                                }
17343
17344                                match target {
17345                                    DialectType::DuckDB => Ok(Expression::Function(f)),
17346                                    DialectType::Spark | DialectType::Databricks => {
17347                                        // STRPTIME(val, fmt) -> TO_TIMESTAMP(val, java_fmt)
17348                                        if let Expression::Literal(lit) = fmt_expr {
17349                                            if let crate::expressions::Literal::String(s) =
17350                                                lit.as_ref()
17351                                            {
17352                                                let java_fmt = c_to_java_format_parse(s);
17353                                                Ok(Expression::Function(Box::new(Function::new(
17354                                                    "TO_TIMESTAMP".to_string(),
17355                                                    vec![val, Expression::string(&java_fmt)],
17356                                                ))))
17357                                            } else {
17358                                                Ok(Expression::Function(Box::new(Function::new(
17359                                                    "TO_TIMESTAMP".to_string(),
17360                                                    vec![val, fmt_expr.clone()],
17361                                                ))))
17362                                            }
17363                                        } else {
17364                                            Ok(Expression::Function(Box::new(Function::new(
17365                                                "TO_TIMESTAMP".to_string(),
17366                                                vec![val, fmt_expr.clone()],
17367                                            ))))
17368                                        }
17369                                    }
17370                                    DialectType::Hive => {
17371                                        // STRPTIME(val, fmt) -> CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(val, java_fmt)) AS TIMESTAMP)
17372                                        if let Expression::Literal(lit) = fmt_expr {
17373                                            if let crate::expressions::Literal::String(s) =
17374                                                lit.as_ref()
17375                                            {
17376                                                let java_fmt = c_to_java_format_parse(s);
17377                                                let unix_ts =
17378                                                    Expression::Function(Box::new(Function::new(
17379                                                        "UNIX_TIMESTAMP".to_string(),
17380                                                        vec![val, Expression::string(&java_fmt)],
17381                                                    )));
17382                                                let from_unix =
17383                                                    Expression::Function(Box::new(Function::new(
17384                                                        "FROM_UNIXTIME".to_string(),
17385                                                        vec![unix_ts],
17386                                                    )));
17387                                                Ok(Expression::Cast(Box::new(
17388                                                    crate::expressions::Cast {
17389                                                        this: from_unix,
17390                                                        to: DataType::Timestamp {
17391                                                            timezone: false,
17392                                                            precision: None,
17393                                                        },
17394                                                        trailing_comments: Vec::new(),
17395                                                        double_colon_syntax: false,
17396                                                        format: None,
17397                                                        default: None,
17398                                                        inferred_type: None,
17399                                                    },
17400                                                )))
17401                                            } else {
17402                                                Ok(Expression::Function(f))
17403                                            }
17404                                        } else {
17405                                            Ok(Expression::Function(f))
17406                                        }
17407                                    }
17408                                    DialectType::Presto
17409                                    | DialectType::Trino
17410                                    | DialectType::Athena => {
17411                                        // STRPTIME(val, fmt) -> DATE_PARSE(val, presto_fmt) (convert DuckDB format to Presto)
17412                                        if let Expression::Literal(lit) = fmt_expr {
17413                                            if let crate::expressions::Literal::String(s) =
17414                                                lit.as_ref()
17415                                            {
17416                                                let presto_fmt = duckdb_to_presto_format(s);
17417                                                Ok(Expression::Function(Box::new(Function::new(
17418                                                    "DATE_PARSE".to_string(),
17419                                                    vec![val, Expression::string(&presto_fmt)],
17420                                                ))))
17421                                            } else {
17422                                                Ok(Expression::Function(Box::new(Function::new(
17423                                                    "DATE_PARSE".to_string(),
17424                                                    vec![val, fmt_expr.clone()],
17425                                                ))))
17426                                            }
17427                                        } else {
17428                                            Ok(Expression::Function(Box::new(Function::new(
17429                                                "DATE_PARSE".to_string(),
17430                                                vec![val, fmt_expr.clone()],
17431                                            ))))
17432                                        }
17433                                    }
17434                                    DialectType::BigQuery => {
17435                                        // STRPTIME(val, fmt) -> PARSE_TIMESTAMP(bq_fmt, val) - note reversed arg order
17436                                        if let Expression::Literal(lit) = fmt_expr {
17437                                            if let crate::expressions::Literal::String(s) =
17438                                                lit.as_ref()
17439                                            {
17440                                                let bq_fmt = duckdb_to_bigquery_format(s);
17441                                                Ok(Expression::Function(Box::new(Function::new(
17442                                                    "PARSE_TIMESTAMP".to_string(),
17443                                                    vec![Expression::string(&bq_fmt), val],
17444                                                ))))
17445                                            } else {
17446                                                Ok(Expression::Function(Box::new(Function::new(
17447                                                    "PARSE_TIMESTAMP".to_string(),
17448                                                    vec![fmt_expr.clone(), val],
17449                                                ))))
17450                                            }
17451                                        } else {
17452                                            Ok(Expression::Function(Box::new(Function::new(
17453                                                "PARSE_TIMESTAMP".to_string(),
17454                                                vec![fmt_expr.clone(), val],
17455                                            ))))
17456                                        }
17457                                    }
17458                                    _ => Ok(Expression::Function(f)),
17459                                }
17460                            }
17461                            // DATE_FORMAT(val, fmt) from Presto source (C-style format) -> target-specific
17462                            "DATE_FORMAT"
17463                                if f.args.len() >= 2
17464                                    && matches!(
17465                                        source,
17466                                        DialectType::Presto
17467                                            | DialectType::Trino
17468                                            | DialectType::Athena
17469                                    ) =>
17470                            {
17471                                let val = f.args[0].clone();
17472                                let fmt_expr = &f.args[1];
17473
17474                                match target {
17475                                    DialectType::Presto
17476                                    | DialectType::Trino
17477                                    | DialectType::Athena => {
17478                                        // Presto -> Presto: normalize format (e.g., %H:%i:%S -> %T)
17479                                        if let Expression::Literal(lit) = fmt_expr {
17480                                            if let crate::expressions::Literal::String(s) =
17481                                                lit.as_ref()
17482                                            {
17483                                                let normalized = normalize_presto_format(s);
17484                                                Ok(Expression::Function(Box::new(Function::new(
17485                                                    "DATE_FORMAT".to_string(),
17486                                                    vec![val, Expression::string(&normalized)],
17487                                                ))))
17488                                            } else {
17489                                                Ok(Expression::Function(f))
17490                                            }
17491                                        } else {
17492                                            Ok(Expression::Function(f))
17493                                        }
17494                                    }
17495                                    DialectType::Hive
17496                                    | DialectType::Spark
17497                                    | DialectType::Databricks => {
17498                                        // Convert Presto C-style to Java-style format
17499                                        if let Expression::Literal(lit) = fmt_expr {
17500                                            if let crate::expressions::Literal::String(s) =
17501                                                lit.as_ref()
17502                                            {
17503                                                let java_fmt = presto_to_java_format(s);
17504                                                Ok(Expression::Function(Box::new(Function::new(
17505                                                    "DATE_FORMAT".to_string(),
17506                                                    vec![val, Expression::string(&java_fmt)],
17507                                                ))))
17508                                            } else {
17509                                                Ok(Expression::Function(f))
17510                                            }
17511                                        } else {
17512                                            Ok(Expression::Function(f))
17513                                        }
17514                                    }
17515                                    DialectType::DuckDB => {
17516                                        // Convert to STRFTIME(val, duckdb_fmt)
17517                                        if let Expression::Literal(lit) = fmt_expr {
17518                                            if let crate::expressions::Literal::String(s) =
17519                                                lit.as_ref()
17520                                            {
17521                                                let duckdb_fmt = presto_to_duckdb_format(s);
17522                                                Ok(Expression::Function(Box::new(Function::new(
17523                                                    "STRFTIME".to_string(),
17524                                                    vec![val, Expression::string(&duckdb_fmt)],
17525                                                ))))
17526                                            } else {
17527                                                Ok(Expression::Function(Box::new(Function::new(
17528                                                    "STRFTIME".to_string(),
17529                                                    vec![val, fmt_expr.clone()],
17530                                                ))))
17531                                            }
17532                                        } else {
17533                                            Ok(Expression::Function(Box::new(Function::new(
17534                                                "STRFTIME".to_string(),
17535                                                vec![val, fmt_expr.clone()],
17536                                            ))))
17537                                        }
17538                                    }
17539                                    DialectType::BigQuery => {
17540                                        // Convert to FORMAT_DATE(bq_fmt, val) - reversed args
17541                                        if let Expression::Literal(lit) = fmt_expr {
17542                                            if let crate::expressions::Literal::String(s) =
17543                                                lit.as_ref()
17544                                            {
17545                                                let bq_fmt = presto_to_bigquery_format(s);
17546                                                Ok(Expression::Function(Box::new(Function::new(
17547                                                    "FORMAT_DATE".to_string(),
17548                                                    vec![Expression::string(&bq_fmt), val],
17549                                                ))))
17550                                            } else {
17551                                                Ok(Expression::Function(Box::new(Function::new(
17552                                                    "FORMAT_DATE".to_string(),
17553                                                    vec![fmt_expr.clone(), val],
17554                                                ))))
17555                                            }
17556                                        } else {
17557                                            Ok(Expression::Function(Box::new(Function::new(
17558                                                "FORMAT_DATE".to_string(),
17559                                                vec![fmt_expr.clone(), val],
17560                                            ))))
17561                                        }
17562                                    }
17563                                    _ => Ok(Expression::Function(f)),
17564                                }
17565                            }
17566                            // DATE_PARSE(val, fmt) from Presto source -> target-specific parse function
17567                            "DATE_PARSE"
17568                                if f.args.len() >= 2
17569                                    && matches!(
17570                                        source,
17571                                        DialectType::Presto
17572                                            | DialectType::Trino
17573                                            | DialectType::Athena
17574                                    ) =>
17575                            {
17576                                let val = f.args[0].clone();
17577                                let fmt_expr = &f.args[1];
17578
17579                                match target {
17580                                    DialectType::Presto
17581                                    | DialectType::Trino
17582                                    | DialectType::Athena => {
17583                                        // Presto -> Presto: normalize format
17584                                        if let Expression::Literal(lit) = fmt_expr {
17585                                            if let crate::expressions::Literal::String(s) =
17586                                                lit.as_ref()
17587                                            {
17588                                                let normalized = normalize_presto_format(s);
17589                                                Ok(Expression::Function(Box::new(Function::new(
17590                                                    "DATE_PARSE".to_string(),
17591                                                    vec![val, Expression::string(&normalized)],
17592                                                ))))
17593                                            } else {
17594                                                Ok(Expression::Function(f))
17595                                            }
17596                                        } else {
17597                                            Ok(Expression::Function(f))
17598                                        }
17599                                    }
17600                                    DialectType::Hive => {
17601                                        // Presto -> Hive: if default format, just CAST(x AS TIMESTAMP)
17602                                        if let Expression::Literal(lit) = fmt_expr {
17603                                            if let crate::expressions::Literal::String(s) =
17604                                                lit.as_ref()
17605                                            {
17606                                                if is_default_presto_timestamp_format(s)
17607                                                    || is_default_presto_date_format(s)
17608                                                {
17609                                                    Ok(Expression::Cast(Box::new(
17610                                                        crate::expressions::Cast {
17611                                                            this: val,
17612                                                            to: DataType::Timestamp {
17613                                                                timezone: false,
17614                                                                precision: None,
17615                                                            },
17616                                                            trailing_comments: Vec::new(),
17617                                                            double_colon_syntax: false,
17618                                                            format: None,
17619                                                            default: None,
17620                                                            inferred_type: None,
17621                                                        },
17622                                                    )))
17623                                                } else {
17624                                                    let java_fmt = presto_to_java_format(s);
17625                                                    Ok(Expression::Function(Box::new(
17626                                                        Function::new(
17627                                                            "TO_TIMESTAMP".to_string(),
17628                                                            vec![
17629                                                                val,
17630                                                                Expression::string(&java_fmt),
17631                                                            ],
17632                                                        ),
17633                                                    )))
17634                                                }
17635                                            } else {
17636                                                Ok(Expression::Function(f))
17637                                            }
17638                                        } else {
17639                                            Ok(Expression::Function(f))
17640                                        }
17641                                    }
17642                                    DialectType::Spark | DialectType::Databricks => {
17643                                        // Presto -> Spark: TO_TIMESTAMP(val, java_fmt)
17644                                        if let Expression::Literal(lit) = fmt_expr {
17645                                            if let crate::expressions::Literal::String(s) =
17646                                                lit.as_ref()
17647                                            {
17648                                                let java_fmt = presto_to_java_format(s);
17649                                                Ok(Expression::Function(Box::new(Function::new(
17650                                                    "TO_TIMESTAMP".to_string(),
17651                                                    vec![val, Expression::string(&java_fmt)],
17652                                                ))))
17653                                            } else {
17654                                                Ok(Expression::Function(f))
17655                                            }
17656                                        } else {
17657                                            Ok(Expression::Function(f))
17658                                        }
17659                                    }
17660                                    DialectType::DuckDB => {
17661                                        // Presto -> DuckDB: STRPTIME(val, duckdb_fmt)
17662                                        if let Expression::Literal(lit) = fmt_expr {
17663                                            if let crate::expressions::Literal::String(s) =
17664                                                lit.as_ref()
17665                                            {
17666                                                let duckdb_fmt = presto_to_duckdb_format(s);
17667                                                Ok(Expression::Function(Box::new(Function::new(
17668                                                    "STRPTIME".to_string(),
17669                                                    vec![val, Expression::string(&duckdb_fmt)],
17670                                                ))))
17671                                            } else {
17672                                                Ok(Expression::Function(Box::new(Function::new(
17673                                                    "STRPTIME".to_string(),
17674                                                    vec![val, fmt_expr.clone()],
17675                                                ))))
17676                                            }
17677                                        } else {
17678                                            Ok(Expression::Function(Box::new(Function::new(
17679                                                "STRPTIME".to_string(),
17680                                                vec![val, fmt_expr.clone()],
17681                                            ))))
17682                                        }
17683                                    }
17684                                    _ => Ok(Expression::Function(f)),
17685                                }
17686                            }
17687                            // FROM_BASE64(x) / TO_BASE64(x) from Presto -> Hive-specific renames
17688                            "FROM_BASE64"
17689                                if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
17690                            {
17691                                Ok(Expression::Function(Box::new(Function::new(
17692                                    "UNBASE64".to_string(),
17693                                    f.args,
17694                                ))))
17695                            }
17696                            "TO_BASE64"
17697                                if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
17698                            {
17699                                Ok(Expression::Function(Box::new(Function::new(
17700                                    "BASE64".to_string(),
17701                                    f.args,
17702                                ))))
17703                            }
17704                            // FROM_UNIXTIME(x) -> CAST(FROM_UNIXTIME(x) AS TIMESTAMP) for Spark
17705                            "FROM_UNIXTIME"
17706                                if f.args.len() == 1
17707                                    && matches!(
17708                                        source,
17709                                        DialectType::Presto
17710                                            | DialectType::Trino
17711                                            | DialectType::Athena
17712                                    )
17713                                    && matches!(
17714                                        target,
17715                                        DialectType::Spark | DialectType::Databricks
17716                                    ) =>
17717                            {
17718                                // Wrap FROM_UNIXTIME(x) in CAST(... AS TIMESTAMP)
17719                                let from_unix = Expression::Function(Box::new(Function::new(
17720                                    "FROM_UNIXTIME".to_string(),
17721                                    f.args,
17722                                )));
17723                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
17724                                    this: from_unix,
17725                                    to: DataType::Timestamp {
17726                                        timezone: false,
17727                                        precision: None,
17728                                    },
17729                                    trailing_comments: Vec::new(),
17730                                    double_colon_syntax: false,
17731                                    format: None,
17732                                    default: None,
17733                                    inferred_type: None,
17734                                })))
17735                            }
17736                            // DATE_FORMAT(val, fmt) from Hive/Spark/MySQL -> target-specific format function
17737                            "DATE_FORMAT"
17738                                if f.args.len() >= 2
17739                                    && !matches!(
17740                                        target,
17741                                        DialectType::Hive
17742                                            | DialectType::Spark
17743                                            | DialectType::Databricks
17744                                            | DialectType::MySQL
17745                                            | DialectType::SingleStore
17746                                    ) =>
17747                            {
17748                                let val = f.args[0].clone();
17749                                let fmt_expr = &f.args[1];
17750                                let is_hive_source = matches!(
17751                                    source,
17752                                    DialectType::Hive
17753                                        | DialectType::Spark
17754                                        | DialectType::Databricks
17755                                );
17756
17757                                fn java_to_c_format(fmt: &str) -> String {
17758                                    // Replace Java patterns with C strftime patterns.
17759                                    // Uses multi-pass to handle patterns that conflict.
17760                                    // First pass: replace multi-char patterns (longer first)
17761                                    let result = fmt
17762                                        .replace("yyyy", "%Y")
17763                                        .replace("SSSSSS", "%f")
17764                                        .replace("EEEE", "%W")
17765                                        .replace("MM", "%m")
17766                                        .replace("dd", "%d")
17767                                        .replace("HH", "%H")
17768                                        .replace("mm", "%M")
17769                                        .replace("ss", "%S")
17770                                        .replace("yy", "%y");
17771                                    // Second pass: handle single-char timezone patterns
17772                                    // z -> %Z (timezone name), Z -> %z (timezone offset)
17773                                    // Must be careful not to replace 'z'/'Z' inside already-replaced %Y, %M etc.
17774                                    let mut out = String::new();
17775                                    let chars: Vec<char> = result.chars().collect();
17776                                    let mut i = 0;
17777                                    while i < chars.len() {
17778                                        if chars[i] == '%' && i + 1 < chars.len() {
17779                                            // Already a format specifier, skip both chars
17780                                            out.push(chars[i]);
17781                                            out.push(chars[i + 1]);
17782                                            i += 2;
17783                                        } else if chars[i] == 'z' {
17784                                            out.push_str("%Z");
17785                                            i += 1;
17786                                        } else if chars[i] == 'Z' {
17787                                            out.push_str("%z");
17788                                            i += 1;
17789                                        } else {
17790                                            out.push(chars[i]);
17791                                            i += 1;
17792                                        }
17793                                    }
17794                                    out
17795                                }
17796
17797                                fn java_to_presto_format(fmt: &str) -> String {
17798                                    // Presto uses %T for HH:MM:SS
17799                                    let c_fmt = java_to_c_format(fmt);
17800                                    c_fmt.replace("%H:%M:%S", "%T")
17801                                }
17802
17803                                fn java_to_bq_format(fmt: &str) -> String {
17804                                    // BigQuery uses %F for yyyy-MM-dd and %T for HH:mm:ss
17805                                    let c_fmt = java_to_c_format(fmt);
17806                                    c_fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T")
17807                                }
17808
17809                                // For Hive source, CAST string literals to appropriate type
17810                                let cast_val = if is_hive_source {
17811                                    match &val {
17812                                        Expression::Literal(lit)
17813                                            if matches!(
17814                                                lit.as_ref(),
17815                                                crate::expressions::Literal::String(_)
17816                                            ) =>
17817                                        {
17818                                            match target {
17819                                                DialectType::DuckDB
17820                                                | DialectType::Presto
17821                                                | DialectType::Trino
17822                                                | DialectType::Athena => {
17823                                                    Self::ensure_cast_timestamp(val.clone())
17824                                                }
17825                                                DialectType::BigQuery => {
17826                                                    // BigQuery: CAST(val AS DATETIME)
17827                                                    Expression::Cast(Box::new(
17828                                                        crate::expressions::Cast {
17829                                                            this: val.clone(),
17830                                                            to: DataType::Custom {
17831                                                                name: "DATETIME".to_string(),
17832                                                            },
17833                                                            trailing_comments: vec![],
17834                                                            double_colon_syntax: false,
17835                                                            format: None,
17836                                                            default: None,
17837                                                            inferred_type: None,
17838                                                        },
17839                                                    ))
17840                                                }
17841                                                _ => val.clone(),
17842                                            }
17843                                        }
17844                                        // For CAST(x AS DATE) or DATE literal, Presto needs CAST(CAST(x AS DATE) AS TIMESTAMP)
17845                                        Expression::Cast(c)
17846                                            if matches!(c.to, DataType::Date)
17847                                                && matches!(
17848                                                    target,
17849                                                    DialectType::Presto
17850                                                        | DialectType::Trino
17851                                                        | DialectType::Athena
17852                                                ) =>
17853                                        {
17854                                            Expression::Cast(Box::new(crate::expressions::Cast {
17855                                                this: val.clone(),
17856                                                to: DataType::Timestamp {
17857                                                    timezone: false,
17858                                                    precision: None,
17859                                                },
17860                                                trailing_comments: vec![],
17861                                                double_colon_syntax: false,
17862                                                format: None,
17863                                                default: None,
17864                                                inferred_type: None,
17865                                            }))
17866                                        }
17867                                        Expression::Literal(lit)
17868                                            if matches!(
17869                                                lit.as_ref(),
17870                                                crate::expressions::Literal::Date(_)
17871                                            ) && matches!(
17872                                                target,
17873                                                DialectType::Presto
17874                                                    | DialectType::Trino
17875                                                    | DialectType::Athena
17876                                            ) =>
17877                                        {
17878                                            // DATE 'x' -> CAST(CAST('x' AS DATE) AS TIMESTAMP)
17879                                            let cast_date = Self::date_literal_to_cast(val.clone());
17880                                            Expression::Cast(Box::new(crate::expressions::Cast {
17881                                                this: cast_date,
17882                                                to: DataType::Timestamp {
17883                                                    timezone: false,
17884                                                    precision: None,
17885                                                },
17886                                                trailing_comments: vec![],
17887                                                double_colon_syntax: false,
17888                                                format: None,
17889                                                default: None,
17890                                                inferred_type: None,
17891                                            }))
17892                                        }
17893                                        _ => val.clone(),
17894                                    }
17895                                } else {
17896                                    val.clone()
17897                                };
17898
17899                                match target {
17900                                    DialectType::DuckDB => {
17901                                        if let Expression::Literal(lit) = fmt_expr {
17902                                            if let crate::expressions::Literal::String(s) =
17903                                                lit.as_ref()
17904                                            {
17905                                                let c_fmt = if is_hive_source {
17906                                                    java_to_c_format(s)
17907                                                } else {
17908                                                    s.clone()
17909                                                };
17910                                                Ok(Expression::Function(Box::new(Function::new(
17911                                                    "STRFTIME".to_string(),
17912                                                    vec![cast_val, Expression::string(&c_fmt)],
17913                                                ))))
17914                                            } else {
17915                                                Ok(Expression::Function(Box::new(Function::new(
17916                                                    "STRFTIME".to_string(),
17917                                                    vec![cast_val, fmt_expr.clone()],
17918                                                ))))
17919                                            }
17920                                        } else {
17921                                            Ok(Expression::Function(Box::new(Function::new(
17922                                                "STRFTIME".to_string(),
17923                                                vec![cast_val, fmt_expr.clone()],
17924                                            ))))
17925                                        }
17926                                    }
17927                                    DialectType::Presto
17928                                    | DialectType::Trino
17929                                    | DialectType::Athena => {
17930                                        if is_hive_source {
17931                                            if let Expression::Literal(lit) = fmt_expr {
17932                                                if let crate::expressions::Literal::String(s) =
17933                                                    lit.as_ref()
17934                                                {
17935                                                    let p_fmt = java_to_presto_format(s);
17936                                                    Ok(Expression::Function(Box::new(
17937                                                        Function::new(
17938                                                            "DATE_FORMAT".to_string(),
17939                                                            vec![
17940                                                                cast_val,
17941                                                                Expression::string(&p_fmt),
17942                                                            ],
17943                                                        ),
17944                                                    )))
17945                                                } else {
17946                                                    Ok(Expression::Function(Box::new(
17947                                                        Function::new(
17948                                                            "DATE_FORMAT".to_string(),
17949                                                            vec![cast_val, fmt_expr.clone()],
17950                                                        ),
17951                                                    )))
17952                                                }
17953                                            } else {
17954                                                Ok(Expression::Function(Box::new(Function::new(
17955                                                    "DATE_FORMAT".to_string(),
17956                                                    vec![cast_val, fmt_expr.clone()],
17957                                                ))))
17958                                            }
17959                                        } else {
17960                                            Ok(Expression::Function(Box::new(Function::new(
17961                                                "DATE_FORMAT".to_string(),
17962                                                f.args,
17963                                            ))))
17964                                        }
17965                                    }
17966                                    DialectType::BigQuery => {
17967                                        // DATE_FORMAT(val, fmt) -> FORMAT_DATE(fmt, val)
17968                                        if let Expression::Literal(lit) = fmt_expr {
17969                                            if let crate::expressions::Literal::String(s) =
17970                                                lit.as_ref()
17971                                            {
17972                                                let bq_fmt = if is_hive_source {
17973                                                    java_to_bq_format(s)
17974                                                } else {
17975                                                    java_to_c_format(s)
17976                                                };
17977                                                Ok(Expression::Function(Box::new(Function::new(
17978                                                    "FORMAT_DATE".to_string(),
17979                                                    vec![Expression::string(&bq_fmt), cast_val],
17980                                                ))))
17981                                            } else {
17982                                                Ok(Expression::Function(Box::new(Function::new(
17983                                                    "FORMAT_DATE".to_string(),
17984                                                    vec![fmt_expr.clone(), cast_val],
17985                                                ))))
17986                                            }
17987                                        } else {
17988                                            Ok(Expression::Function(Box::new(Function::new(
17989                                                "FORMAT_DATE".to_string(),
17990                                                vec![fmt_expr.clone(), cast_val],
17991                                            ))))
17992                                        }
17993                                    }
17994                                    DialectType::PostgreSQL | DialectType::Redshift => {
17995                                        if let Expression::Literal(lit) = fmt_expr {
17996                                            if let crate::expressions::Literal::String(s) =
17997                                                lit.as_ref()
17998                                            {
17999                                                let pg_fmt = s
18000                                                    .replace("yyyy", "YYYY")
18001                                                    .replace("MM", "MM")
18002                                                    .replace("dd", "DD")
18003                                                    .replace("HH", "HH24")
18004                                                    .replace("mm", "MI")
18005                                                    .replace("ss", "SS")
18006                                                    .replace("yy", "YY");
18007                                                Ok(Expression::Function(Box::new(Function::new(
18008                                                    "TO_CHAR".to_string(),
18009                                                    vec![val, Expression::string(&pg_fmt)],
18010                                                ))))
18011                                            } else {
18012                                                Ok(Expression::Function(Box::new(Function::new(
18013                                                    "TO_CHAR".to_string(),
18014                                                    vec![val, fmt_expr.clone()],
18015                                                ))))
18016                                            }
18017                                        } else {
18018                                            Ok(Expression::Function(Box::new(Function::new(
18019                                                "TO_CHAR".to_string(),
18020                                                vec![val, fmt_expr.clone()],
18021                                            ))))
18022                                        }
18023                                    }
18024                                    _ => Ok(Expression::Function(f)),
18025                                }
18026                            }
18027                            // DATEDIFF(unit, start, end) - 3-arg form
18028                            // SQLite uses DATEDIFF(date1, date2, unit_string) instead
18029                            "DATEDIFF" if f.args.len() == 3 => {
18030                                let mut args = f.args;
18031                                // SQLite source: args = (date1, date2, unit_string)
18032                                // Standard source: args = (unit, start, end)
18033                                let (_arg0, arg1, arg2, unit_str) =
18034                                    if matches!(source, DialectType::SQLite) {
18035                                        let date1 = args.remove(0);
18036                                        let date2 = args.remove(0);
18037                                        let unit_expr = args.remove(0);
18038                                        let unit_s = Self::get_unit_str_static(&unit_expr);
18039
18040                                        // For SQLite target, generate JULIANDAY arithmetic directly
18041                                        if matches!(target, DialectType::SQLite) {
18042                                            let jd_first = Expression::Function(Box::new(
18043                                                Function::new("JULIANDAY".to_string(), vec![date1]),
18044                                            ));
18045                                            let jd_second = Expression::Function(Box::new(
18046                                                Function::new("JULIANDAY".to_string(), vec![date2]),
18047                                            ));
18048                                            let diff = Expression::Sub(Box::new(
18049                                                crate::expressions::BinaryOp::new(
18050                                                    jd_first, jd_second,
18051                                                ),
18052                                            ));
18053                                            let paren_diff = Expression::Paren(Box::new(
18054                                                crate::expressions::Paren {
18055                                                    this: diff,
18056                                                    trailing_comments: Vec::new(),
18057                                                },
18058                                            ));
18059                                            let adjusted = match unit_s.as_str() {
18060                                                "HOUR" => Expression::Mul(Box::new(
18061                                                    crate::expressions::BinaryOp::new(
18062                                                        paren_diff,
18063                                                        Expression::Literal(Box::new(
18064                                                            Literal::Number("24.0".to_string()),
18065                                                        )),
18066                                                    ),
18067                                                )),
18068                                                "MINUTE" => Expression::Mul(Box::new(
18069                                                    crate::expressions::BinaryOp::new(
18070                                                        paren_diff,
18071                                                        Expression::Literal(Box::new(
18072                                                            Literal::Number("1440.0".to_string()),
18073                                                        )),
18074                                                    ),
18075                                                )),
18076                                                "SECOND" => Expression::Mul(Box::new(
18077                                                    crate::expressions::BinaryOp::new(
18078                                                        paren_diff,
18079                                                        Expression::Literal(Box::new(
18080                                                            Literal::Number("86400.0".to_string()),
18081                                                        )),
18082                                                    ),
18083                                                )),
18084                                                "MONTH" => Expression::Div(Box::new(
18085                                                    crate::expressions::BinaryOp::new(
18086                                                        paren_diff,
18087                                                        Expression::Literal(Box::new(
18088                                                            Literal::Number("30.0".to_string()),
18089                                                        )),
18090                                                    ),
18091                                                )),
18092                                                "YEAR" => Expression::Div(Box::new(
18093                                                    crate::expressions::BinaryOp::new(
18094                                                        paren_diff,
18095                                                        Expression::Literal(Box::new(
18096                                                            Literal::Number("365.0".to_string()),
18097                                                        )),
18098                                                    ),
18099                                                )),
18100                                                _ => paren_diff,
18101                                            };
18102                                            return Ok(Expression::Cast(Box::new(Cast {
18103                                                this: adjusted,
18104                                                to: DataType::Int {
18105                                                    length: None,
18106                                                    integer_spelling: true,
18107                                                },
18108                                                trailing_comments: vec![],
18109                                                double_colon_syntax: false,
18110                                                format: None,
18111                                                default: None,
18112                                                inferred_type: None,
18113                                            })));
18114                                        }
18115
18116                                        // For other targets, remap to standard (unit, start, end) form
18117                                        let unit_ident =
18118                                            Expression::Identifier(Identifier::new(&unit_s));
18119                                        (unit_ident, date1, date2, unit_s)
18120                                    } else {
18121                                        let arg0 = args.remove(0);
18122                                        let arg1 = args.remove(0);
18123                                        let arg2 = args.remove(0);
18124                                        let unit_s = Self::get_unit_str_static(&arg0);
18125                                        (arg0, arg1, arg2, unit_s)
18126                                    };
18127
18128                                // For Hive/Spark source, string literal dates need to be cast
18129                                // Note: Databricks is excluded - it handles string args like standard SQL
18130                                let is_hive_spark =
18131                                    matches!(source, DialectType::Hive | DialectType::Spark);
18132
18133                                match target {
18134                                    DialectType::Snowflake => {
18135                                        let unit =
18136                                            Expression::Identifier(Identifier::new(&unit_str));
18137                                        // Use ensure_to_date_preserved to add TO_DATE with a marker
18138                                        // that prevents the Snowflake TO_DATE handler from converting it to CAST
18139                                        let d1 = if is_hive_spark {
18140                                            Self::ensure_to_date_preserved(arg1)
18141                                        } else {
18142                                            arg1
18143                                        };
18144                                        let d2 = if is_hive_spark {
18145                                            Self::ensure_to_date_preserved(arg2)
18146                                        } else {
18147                                            arg2
18148                                        };
18149                                        Ok(Expression::Function(Box::new(Function::new(
18150                                            "DATEDIFF".to_string(),
18151                                            vec![unit, d1, d2],
18152                                        ))))
18153                                    }
18154                                    DialectType::Redshift => {
18155                                        let unit =
18156                                            Expression::Identifier(Identifier::new(&unit_str));
18157                                        let d1 = if is_hive_spark {
18158                                            Self::ensure_cast_date(arg1)
18159                                        } else {
18160                                            arg1
18161                                        };
18162                                        let d2 = if is_hive_spark {
18163                                            Self::ensure_cast_date(arg2)
18164                                        } else {
18165                                            arg2
18166                                        };
18167                                        Ok(Expression::Function(Box::new(Function::new(
18168                                            "DATEDIFF".to_string(),
18169                                            vec![unit, d1, d2],
18170                                        ))))
18171                                    }
18172                                    DialectType::TSQL => {
18173                                        let unit =
18174                                            Expression::Identifier(Identifier::new(&unit_str));
18175                                        Ok(Expression::Function(Box::new(Function::new(
18176                                            "DATEDIFF".to_string(),
18177                                            vec![unit, arg1, arg2],
18178                                        ))))
18179                                    }
18180                                    DialectType::DuckDB => {
18181                                        let is_redshift_tsql = matches!(
18182                                            source,
18183                                            DialectType::Redshift | DialectType::TSQL
18184                                        );
18185                                        if is_hive_spark {
18186                                            // For Hive/Spark source, CAST string args to DATE and emit DATE_DIFF directly
18187                                            let d1 = Self::ensure_cast_date(arg1);
18188                                            let d2 = Self::ensure_cast_date(arg2);
18189                                            Ok(Expression::Function(Box::new(Function::new(
18190                                                "DATE_DIFF".to_string(),
18191                                                vec![Expression::string(&unit_str), d1, d2],
18192                                            ))))
18193                                        } else if matches!(source, DialectType::Snowflake) {
18194                                            // For Snowflake source: special handling per unit
18195                                            match unit_str.as_str() {
18196                                                "NANOSECOND" => {
18197                                                    // DATEDIFF(NANOSECOND, start, end) -> EPOCH_NS(CAST(end AS TIMESTAMP_NS)) - EPOCH_NS(CAST(start AS TIMESTAMP_NS))
18198                                                    fn cast_to_timestamp_ns(
18199                                                        expr: Expression,
18200                                                    ) -> Expression
18201                                                    {
18202                                                        Expression::Cast(Box::new(Cast {
18203                                                            this: expr,
18204                                                            to: DataType::Custom {
18205                                                                name: "TIMESTAMP_NS".to_string(),
18206                                                            },
18207                                                            trailing_comments: vec![],
18208                                                            double_colon_syntax: false,
18209                                                            format: None,
18210                                                            default: None,
18211                                                            inferred_type: None,
18212                                                        }))
18213                                                    }
18214                                                    let epoch_end = Expression::Function(Box::new(
18215                                                        Function::new(
18216                                                            "EPOCH_NS".to_string(),
18217                                                            vec![cast_to_timestamp_ns(arg2)],
18218                                                        ),
18219                                                    ));
18220                                                    let epoch_start = Expression::Function(
18221                                                        Box::new(Function::new(
18222                                                            "EPOCH_NS".to_string(),
18223                                                            vec![cast_to_timestamp_ns(arg1)],
18224                                                        )),
18225                                                    );
18226                                                    Ok(Expression::Sub(Box::new(BinaryOp::new(
18227                                                        epoch_end,
18228                                                        epoch_start,
18229                                                    ))))
18230                                                }
18231                                                "WEEK" => {
18232                                                    // DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST(x AS DATE)), DATE_TRUNC('WEEK', CAST(y AS DATE)))
18233                                                    let d1 = Self::force_cast_date(arg1);
18234                                                    let d2 = Self::force_cast_date(arg2);
18235                                                    let dt1 = Expression::Function(Box::new(
18236                                                        Function::new(
18237                                                            "DATE_TRUNC".to_string(),
18238                                                            vec![Expression::string("WEEK"), d1],
18239                                                        ),
18240                                                    ));
18241                                                    let dt2 = Expression::Function(Box::new(
18242                                                        Function::new(
18243                                                            "DATE_TRUNC".to_string(),
18244                                                            vec![Expression::string("WEEK"), d2],
18245                                                        ),
18246                                                    ));
18247                                                    Ok(Expression::Function(Box::new(
18248                                                        Function::new(
18249                                                            "DATE_DIFF".to_string(),
18250                                                            vec![
18251                                                                Expression::string(&unit_str),
18252                                                                dt1,
18253                                                                dt2,
18254                                                            ],
18255                                                        ),
18256                                                    )))
18257                                                }
18258                                                _ => {
18259                                                    // YEAR, MONTH, QUARTER, DAY, etc.: CAST to DATE
18260                                                    let d1 = Self::force_cast_date(arg1);
18261                                                    let d2 = Self::force_cast_date(arg2);
18262                                                    Ok(Expression::Function(Box::new(
18263                                                        Function::new(
18264                                                            "DATE_DIFF".to_string(),
18265                                                            vec![
18266                                                                Expression::string(&unit_str),
18267                                                                d1,
18268                                                                d2,
18269                                                            ],
18270                                                        ),
18271                                                    )))
18272                                                }
18273                                            }
18274                                        } else if is_redshift_tsql {
18275                                            // For Redshift/TSQL source, CAST args to TIMESTAMP (always)
18276                                            let d1 = Self::force_cast_timestamp(arg1);
18277                                            let d2 = Self::force_cast_timestamp(arg2);
18278                                            Ok(Expression::Function(Box::new(Function::new(
18279                                                "DATE_DIFF".to_string(),
18280                                                vec![Expression::string(&unit_str), d1, d2],
18281                                            ))))
18282                                        } else {
18283                                            // Keep as DATEDIFF so DuckDB's transform_datediff handles
18284                                            // DATE_TRUNC for WEEK, CAST for string literals, etc.
18285                                            let unit =
18286                                                Expression::Identifier(Identifier::new(&unit_str));
18287                                            Ok(Expression::Function(Box::new(Function::new(
18288                                                "DATEDIFF".to_string(),
18289                                                vec![unit, arg1, arg2],
18290                                            ))))
18291                                        }
18292                                    }
18293                                    DialectType::BigQuery => {
18294                                        let is_redshift_tsql = matches!(
18295                                            source,
18296                                            DialectType::Redshift
18297                                                | DialectType::TSQL
18298                                                | DialectType::Snowflake
18299                                        );
18300                                        let cast_d1 = if is_hive_spark {
18301                                            Self::ensure_cast_date(arg1)
18302                                        } else if is_redshift_tsql {
18303                                            Self::force_cast_datetime(arg1)
18304                                        } else {
18305                                            Self::ensure_cast_datetime(arg1)
18306                                        };
18307                                        let cast_d2 = if is_hive_spark {
18308                                            Self::ensure_cast_date(arg2)
18309                                        } else if is_redshift_tsql {
18310                                            Self::force_cast_datetime(arg2)
18311                                        } else {
18312                                            Self::ensure_cast_datetime(arg2)
18313                                        };
18314                                        let unit =
18315                                            Expression::Identifier(Identifier::new(&unit_str));
18316                                        Ok(Expression::Function(Box::new(Function::new(
18317                                            "DATE_DIFF".to_string(),
18318                                            vec![cast_d2, cast_d1, unit],
18319                                        ))))
18320                                    }
18321                                    DialectType::Presto
18322                                    | DialectType::Trino
18323                                    | DialectType::Athena => {
18324                                        // For Hive/Spark source, string literals need double-cast: CAST(CAST(x AS TIMESTAMP) AS DATE)
18325                                        // For Redshift/TSQL source, args need CAST to TIMESTAMP (always)
18326                                        let is_redshift_tsql = matches!(
18327                                            source,
18328                                            DialectType::Redshift
18329                                                | DialectType::TSQL
18330                                                | DialectType::Snowflake
18331                                        );
18332                                        let d1 = if is_hive_spark {
18333                                            Self::double_cast_timestamp_date(arg1)
18334                                        } else if is_redshift_tsql {
18335                                            Self::force_cast_timestamp(arg1)
18336                                        } else {
18337                                            arg1
18338                                        };
18339                                        let d2 = if is_hive_spark {
18340                                            Self::double_cast_timestamp_date(arg2)
18341                                        } else if is_redshift_tsql {
18342                                            Self::force_cast_timestamp(arg2)
18343                                        } else {
18344                                            arg2
18345                                        };
18346                                        Ok(Expression::Function(Box::new(Function::new(
18347                                            "DATE_DIFF".to_string(),
18348                                            vec![Expression::string(&unit_str), d1, d2],
18349                                        ))))
18350                                    }
18351                                    DialectType::Hive => match unit_str.as_str() {
18352                                        "MONTH" => Ok(Expression::Cast(Box::new(Cast {
18353                                            this: Expression::Function(Box::new(Function::new(
18354                                                "MONTHS_BETWEEN".to_string(),
18355                                                vec![arg2, arg1],
18356                                            ))),
18357                                            to: DataType::Int {
18358                                                length: None,
18359                                                integer_spelling: false,
18360                                            },
18361                                            trailing_comments: vec![],
18362                                            double_colon_syntax: false,
18363                                            format: None,
18364                                            default: None,
18365                                            inferred_type: None,
18366                                        }))),
18367                                        "WEEK" => Ok(Expression::Cast(Box::new(Cast {
18368                                            this: Expression::Div(Box::new(
18369                                                crate::expressions::BinaryOp::new(
18370                                                    Expression::Function(Box::new(Function::new(
18371                                                        "DATEDIFF".to_string(),
18372                                                        vec![arg2, arg1],
18373                                                    ))),
18374                                                    Expression::number(7),
18375                                                ),
18376                                            )),
18377                                            to: DataType::Int {
18378                                                length: None,
18379                                                integer_spelling: false,
18380                                            },
18381                                            trailing_comments: vec![],
18382                                            double_colon_syntax: false,
18383                                            format: None,
18384                                            default: None,
18385                                            inferred_type: None,
18386                                        }))),
18387                                        _ => Ok(Expression::Function(Box::new(Function::new(
18388                                            "DATEDIFF".to_string(),
18389                                            vec![arg2, arg1],
18390                                        )))),
18391                                    },
18392                                    DialectType::Spark | DialectType::Databricks => {
18393                                        let unit =
18394                                            Expression::Identifier(Identifier::new(&unit_str));
18395                                        Ok(Expression::Function(Box::new(Function::new(
18396                                            "DATEDIFF".to_string(),
18397                                            vec![unit, arg1, arg2],
18398                                        ))))
18399                                    }
18400                                    _ => {
18401                                        // For Hive/Spark source targeting PostgreSQL etc., cast string literals to DATE
18402                                        let d1 = if is_hive_spark {
18403                                            Self::ensure_cast_date(arg1)
18404                                        } else {
18405                                            arg1
18406                                        };
18407                                        let d2 = if is_hive_spark {
18408                                            Self::ensure_cast_date(arg2)
18409                                        } else {
18410                                            arg2
18411                                        };
18412                                        let unit =
18413                                            Expression::Identifier(Identifier::new(&unit_str));
18414                                        Ok(Expression::Function(Box::new(Function::new(
18415                                            "DATEDIFF".to_string(),
18416                                            vec![unit, d1, d2],
18417                                        ))))
18418                                    }
18419                                }
18420                            }
18421                            // DATEDIFF(end, start) - 2-arg form from Hive/MySQL
18422                            "DATEDIFF" if f.args.len() == 2 => {
18423                                let mut args = f.args;
18424                                let arg0 = args.remove(0);
18425                                let arg1 = args.remove(0);
18426
18427                                // Helper: unwrap TO_DATE(x) -> x (extracts inner arg)
18428                                // Also recognizes TryCast/Cast to DATE that may have been produced by
18429                                // cross-dialect TO_DATE -> TRY_CAST conversion
18430                                let unwrap_to_date = |e: Expression| -> (Expression, bool) {
18431                                    if let Expression::Function(ref f) = e {
18432                                        if f.name.eq_ignore_ascii_case("TO_DATE")
18433                                            && f.args.len() == 1
18434                                        {
18435                                            return (f.args[0].clone(), true);
18436                                        }
18437                                    }
18438                                    // Also recognize TryCast(x, Date) as an already-converted TO_DATE
18439                                    if let Expression::TryCast(ref c) = e {
18440                                        if matches!(c.to, DataType::Date) {
18441                                            return (e, true); // Already properly cast, return as-is
18442                                        }
18443                                    }
18444                                    (e, false)
18445                                };
18446
18447                                match target {
18448                                    DialectType::DuckDB => {
18449                                        // For Hive source, always CAST to DATE
18450                                        // If arg is TO_DATE(x) or TRY_CAST(x AS DATE), use it directly
18451                                        let cast_d0 = if matches!(
18452                                            source,
18453                                            DialectType::Hive
18454                                                | DialectType::Spark
18455                                                | DialectType::Databricks
18456                                        ) {
18457                                            let (inner, was_to_date) = unwrap_to_date(arg1);
18458                                            if was_to_date {
18459                                                // Already a date expression, use directly
18460                                                if matches!(&inner, Expression::TryCast(_)) {
18461                                                    inner // Already TRY_CAST(x AS DATE)
18462                                                } else {
18463                                                    Self::try_cast_date(inner)
18464                                                }
18465                                            } else {
18466                                                Self::force_cast_date(inner)
18467                                            }
18468                                        } else {
18469                                            Self::ensure_cast_date(arg1)
18470                                        };
18471                                        let cast_d1 = if matches!(
18472                                            source,
18473                                            DialectType::Hive
18474                                                | DialectType::Spark
18475                                                | DialectType::Databricks
18476                                        ) {
18477                                            let (inner, was_to_date) = unwrap_to_date(arg0);
18478                                            if was_to_date {
18479                                                if matches!(&inner, Expression::TryCast(_)) {
18480                                                    inner
18481                                                } else {
18482                                                    Self::try_cast_date(inner)
18483                                                }
18484                                            } else {
18485                                                Self::force_cast_date(inner)
18486                                            }
18487                                        } else {
18488                                            Self::ensure_cast_date(arg0)
18489                                        };
18490                                        Ok(Expression::Function(Box::new(Function::new(
18491                                            "DATE_DIFF".to_string(),
18492                                            vec![Expression::string("DAY"), cast_d0, cast_d1],
18493                                        ))))
18494                                    }
18495                                    DialectType::Presto
18496                                    | DialectType::Trino
18497                                    | DialectType::Athena => {
18498                                        // For Hive/Spark source, apply double_cast_timestamp_date
18499                                        // For other sources (MySQL etc.), just swap args without casting
18500                                        if matches!(
18501                                            source,
18502                                            DialectType::Hive
18503                                                | DialectType::Spark
18504                                                | DialectType::Databricks
18505                                        ) {
18506                                            let cast_fn = |e: Expression| -> Expression {
18507                                                let (inner, was_to_date) = unwrap_to_date(e);
18508                                                if was_to_date {
18509                                                    let first_cast =
18510                                                        Self::double_cast_timestamp_date(inner);
18511                                                    Self::double_cast_timestamp_date(first_cast)
18512                                                } else {
18513                                                    Self::double_cast_timestamp_date(inner)
18514                                                }
18515                                            };
18516                                            Ok(Expression::Function(Box::new(Function::new(
18517                                                "DATE_DIFF".to_string(),
18518                                                vec![
18519                                                    Expression::string("DAY"),
18520                                                    cast_fn(arg1),
18521                                                    cast_fn(arg0),
18522                                                ],
18523                                            ))))
18524                                        } else {
18525                                            Ok(Expression::Function(Box::new(Function::new(
18526                                                "DATE_DIFF".to_string(),
18527                                                vec![Expression::string("DAY"), arg1, arg0],
18528                                            ))))
18529                                        }
18530                                    }
18531                                    DialectType::Redshift => {
18532                                        let unit = Expression::Identifier(Identifier::new("DAY"));
18533                                        Ok(Expression::Function(Box::new(Function::new(
18534                                            "DATEDIFF".to_string(),
18535                                            vec![unit, arg1, arg0],
18536                                        ))))
18537                                    }
18538                                    _ => Ok(Expression::Function(Box::new(Function::new(
18539                                        "DATEDIFF".to_string(),
18540                                        vec![arg0, arg1],
18541                                    )))),
18542                                }
18543                            }
18544                            // DATE_DIFF(unit, start, end) - 3-arg with string unit (ClickHouse/DuckDB style)
18545                            "DATE_DIFF" if f.args.len() == 3 => {
18546                                let mut args = f.args;
18547                                let arg0 = args.remove(0);
18548                                let arg1 = args.remove(0);
18549                                let arg2 = args.remove(0);
18550                                let unit_str = Self::get_unit_str_static(&arg0);
18551
18552                                match target {
18553                                    DialectType::DuckDB => {
18554                                        // DuckDB: DATE_DIFF('UNIT', start, end)
18555                                        Ok(Expression::Function(Box::new(Function::new(
18556                                            "DATE_DIFF".to_string(),
18557                                            vec![Expression::string(&unit_str), arg1, arg2],
18558                                        ))))
18559                                    }
18560                                    DialectType::Presto
18561                                    | DialectType::Trino
18562                                    | DialectType::Athena => {
18563                                        Ok(Expression::Function(Box::new(Function::new(
18564                                            "DATE_DIFF".to_string(),
18565                                            vec![Expression::string(&unit_str), arg1, arg2],
18566                                        ))))
18567                                    }
18568                                    DialectType::ClickHouse => {
18569                                        // ClickHouse: DATE_DIFF(UNIT, start, end) - identifier unit
18570                                        let unit =
18571                                            Expression::Identifier(Identifier::new(&unit_str));
18572                                        Ok(Expression::Function(Box::new(Function::new(
18573                                            "DATE_DIFF".to_string(),
18574                                            vec![unit, arg1, arg2],
18575                                        ))))
18576                                    }
18577                                    DialectType::Snowflake | DialectType::Redshift => {
18578                                        let unit =
18579                                            Expression::Identifier(Identifier::new(&unit_str));
18580                                        Ok(Expression::Function(Box::new(Function::new(
18581                                            "DATEDIFF".to_string(),
18582                                            vec![unit, arg1, arg2],
18583                                        ))))
18584                                    }
18585                                    _ => {
18586                                        let unit =
18587                                            Expression::Identifier(Identifier::new(&unit_str));
18588                                        Ok(Expression::Function(Box::new(Function::new(
18589                                            "DATEDIFF".to_string(),
18590                                            vec![unit, arg1, arg2],
18591                                        ))))
18592                                    }
18593                                }
18594                            }
18595                            // DATEADD(unit, val, date) - 3-arg form
18596                            "DATEADD" if f.args.len() == 3 => {
18597                                let mut args = f.args;
18598                                let arg0 = args.remove(0);
18599                                let arg1 = args.remove(0);
18600                                let arg2 = args.remove(0);
18601                                let unit_str = Self::get_unit_str_static(&arg0);
18602
18603                                // Normalize TSQL unit abbreviations to standard names
18604                                let unit_str = match unit_str.as_str() {
18605                                    "YY" | "YYYY" => "YEAR".to_string(),
18606                                    "QQ" | "Q" => "QUARTER".to_string(),
18607                                    "MM" | "M" => "MONTH".to_string(),
18608                                    "WK" | "WW" => "WEEK".to_string(),
18609                                    "DD" | "D" | "DY" => "DAY".to_string(),
18610                                    "HH" => "HOUR".to_string(),
18611                                    "MI" | "N" => "MINUTE".to_string(),
18612                                    "SS" | "S" => "SECOND".to_string(),
18613                                    "MS" => "MILLISECOND".to_string(),
18614                                    "MCS" | "US" => "MICROSECOND".to_string(),
18615                                    _ => unit_str,
18616                                };
18617                                match target {
18618                                    DialectType::Snowflake => {
18619                                        let unit =
18620                                            Expression::Identifier(Identifier::new(&unit_str));
18621                                        // Cast string literal to TIMESTAMP, but not for Snowflake source
18622                                        // (Snowflake natively accepts string literals in DATEADD)
18623                                        let arg2 = if matches!(
18624                                            &arg2,
18625                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18626                                        ) && !matches!(source, DialectType::Snowflake)
18627                                        {
18628                                            Expression::Cast(Box::new(Cast {
18629                                                this: arg2,
18630                                                to: DataType::Timestamp {
18631                                                    precision: None,
18632                                                    timezone: false,
18633                                                },
18634                                                trailing_comments: Vec::new(),
18635                                                double_colon_syntax: false,
18636                                                format: None,
18637                                                default: None,
18638                                                inferred_type: None,
18639                                            }))
18640                                        } else {
18641                                            arg2
18642                                        };
18643                                        Ok(Expression::Function(Box::new(Function::new(
18644                                            "DATEADD".to_string(),
18645                                            vec![unit, arg1, arg2],
18646                                        ))))
18647                                    }
18648                                    DialectType::TSQL => {
18649                                        let unit =
18650                                            Expression::Identifier(Identifier::new(&unit_str));
18651                                        // Cast string literal to DATETIME2, but not when source is Spark/Databricks family
18652                                        let arg2 = if matches!(
18653                                            &arg2,
18654                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18655                                        ) && !matches!(
18656                                            source,
18657                                            DialectType::Spark
18658                                                | DialectType::Databricks
18659                                                | DialectType::Hive
18660                                        ) {
18661                                            Expression::Cast(Box::new(Cast {
18662                                                this: arg2,
18663                                                to: DataType::Custom {
18664                                                    name: "DATETIME2".to_string(),
18665                                                },
18666                                                trailing_comments: Vec::new(),
18667                                                double_colon_syntax: false,
18668                                                format: None,
18669                                                default: None,
18670                                                inferred_type: None,
18671                                            }))
18672                                        } else {
18673                                            arg2
18674                                        };
18675                                        Ok(Expression::Function(Box::new(Function::new(
18676                                            "DATEADD".to_string(),
18677                                            vec![unit, arg1, arg2],
18678                                        ))))
18679                                    }
18680                                    DialectType::Redshift => {
18681                                        let unit =
18682                                            Expression::Identifier(Identifier::new(&unit_str));
18683                                        Ok(Expression::Function(Box::new(Function::new(
18684                                            "DATEADD".to_string(),
18685                                            vec![unit, arg1, arg2],
18686                                        ))))
18687                                    }
18688                                    DialectType::Databricks => {
18689                                        let unit =
18690                                            Expression::Identifier(Identifier::new(&unit_str));
18691                                        // Sources with native DATEADD (TSQL, Databricks, Snowflake) -> DATEADD
18692                                        // Other sources (Redshift TsOrDsAdd, etc.) -> DATE_ADD
18693                                        let func_name = if matches!(
18694                                            source,
18695                                            DialectType::TSQL
18696                                                | DialectType::Fabric
18697                                                | DialectType::Databricks
18698                                                | DialectType::Snowflake
18699                                        ) {
18700                                            "DATEADD"
18701                                        } else {
18702                                            "DATE_ADD"
18703                                        };
18704                                        Ok(Expression::Function(Box::new(Function::new(
18705                                            func_name.to_string(),
18706                                            vec![unit, arg1, arg2],
18707                                        ))))
18708                                    }
18709                                    DialectType::DuckDB => {
18710                                        // Special handling for NANOSECOND from Snowflake
18711                                        if unit_str == "NANOSECOND"
18712                                            && matches!(source, DialectType::Snowflake)
18713                                        {
18714                                            // DATEADD(NANOSECOND, offset, ts) -> MAKE_TIMESTAMP_NS(EPOCH_NS(CAST(ts AS TIMESTAMP_NS)) + offset)
18715                                            let cast_ts = Expression::Cast(Box::new(Cast {
18716                                                this: arg2,
18717                                                to: DataType::Custom {
18718                                                    name: "TIMESTAMP_NS".to_string(),
18719                                                },
18720                                                trailing_comments: vec![],
18721                                                double_colon_syntax: false,
18722                                                format: None,
18723                                                default: None,
18724                                                inferred_type: None,
18725                                            }));
18726                                            let epoch_ns =
18727                                                Expression::Function(Box::new(Function::new(
18728                                                    "EPOCH_NS".to_string(),
18729                                                    vec![cast_ts],
18730                                                )));
18731                                            let sum = Expression::Add(Box::new(BinaryOp::new(
18732                                                epoch_ns, arg1,
18733                                            )));
18734                                            Ok(Expression::Function(Box::new(Function::new(
18735                                                "MAKE_TIMESTAMP_NS".to_string(),
18736                                                vec![sum],
18737                                            ))))
18738                                        } else {
18739                                            // DuckDB: convert to date + INTERVAL syntax with CAST
18740                                            let iu = Self::parse_interval_unit_static(&unit_str);
18741                                            let interval = Expression::Interval(Box::new(crate::expressions::Interval {
18742                                                this: Some(arg1),
18743                                                unit: Some(crate::expressions::IntervalUnitSpec::Simple { unit: iu, use_plural: false }),
18744                                            }));
18745                                            // Cast string literal to TIMESTAMP
18746                                            let arg2 = if matches!(
18747                                                &arg2,
18748                                                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18749                                            ) {
18750                                                Expression::Cast(Box::new(Cast {
18751                                                    this: arg2,
18752                                                    to: DataType::Timestamp {
18753                                                        precision: None,
18754                                                        timezone: false,
18755                                                    },
18756                                                    trailing_comments: Vec::new(),
18757                                                    double_colon_syntax: false,
18758                                                    format: None,
18759                                                    default: None,
18760                                                    inferred_type: None,
18761                                                }))
18762                                            } else {
18763                                                arg2
18764                                            };
18765                                            Ok(Expression::Add(Box::new(
18766                                                crate::expressions::BinaryOp::new(arg2, interval),
18767                                            )))
18768                                        }
18769                                    }
18770                                    DialectType::Spark => {
18771                                        // For TSQL source: convert to ADD_MONTHS/DATE_ADD(date, val)
18772                                        // For other sources: keep 3-arg DATE_ADD(UNIT, val, date) form
18773                                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
18774                                        {
18775                                            fn multiply_expr_spark(
18776                                                expr: Expression,
18777                                                factor: i64,
18778                                            ) -> Expression
18779                                            {
18780                                                if let Expression::Literal(lit) = &expr {
18781                                                    if let crate::expressions::Literal::Number(n) =
18782                                                        lit.as_ref()
18783                                                    {
18784                                                        if let Ok(val) = n.parse::<i64>() {
18785                                                            return Expression::Literal(Box::new(
18786                                                                crate::expressions::Literal::Number(
18787                                                                    (val * factor).to_string(),
18788                                                                ),
18789                                                            ));
18790                                                        }
18791                                                    }
18792                                                }
18793                                                Expression::Mul(Box::new(
18794                                                    crate::expressions::BinaryOp::new(
18795                                                        expr,
18796                                                        Expression::Literal(Box::new(
18797                                                            crate::expressions::Literal::Number(
18798                                                                factor.to_string(),
18799                                                            ),
18800                                                        )),
18801                                                    ),
18802                                                ))
18803                                            }
18804                                            let normalized_unit = match unit_str.as_str() {
18805                                                "YEAR" | "YY" | "YYYY" => "YEAR",
18806                                                "QUARTER" | "QQ" | "Q" => "QUARTER",
18807                                                "MONTH" | "MM" | "M" => "MONTH",
18808                                                "WEEK" | "WK" | "WW" => "WEEK",
18809                                                "DAY" | "DD" | "D" | "DY" => "DAY",
18810                                                _ => &unit_str,
18811                                            };
18812                                            match normalized_unit {
18813                                                "YEAR" => {
18814                                                    let months = multiply_expr_spark(arg1, 12);
18815                                                    Ok(Expression::Function(Box::new(
18816                                                        Function::new(
18817                                                            "ADD_MONTHS".to_string(),
18818                                                            vec![arg2, months],
18819                                                        ),
18820                                                    )))
18821                                                }
18822                                                "QUARTER" => {
18823                                                    let months = multiply_expr_spark(arg1, 3);
18824                                                    Ok(Expression::Function(Box::new(
18825                                                        Function::new(
18826                                                            "ADD_MONTHS".to_string(),
18827                                                            vec![arg2, months],
18828                                                        ),
18829                                                    )))
18830                                                }
18831                                                "MONTH" => Ok(Expression::Function(Box::new(
18832                                                    Function::new(
18833                                                        "ADD_MONTHS".to_string(),
18834                                                        vec![arg2, arg1],
18835                                                    ),
18836                                                ))),
18837                                                "WEEK" => {
18838                                                    let days = multiply_expr_spark(arg1, 7);
18839                                                    Ok(Expression::Function(Box::new(
18840                                                        Function::new(
18841                                                            "DATE_ADD".to_string(),
18842                                                            vec![arg2, days],
18843                                                        ),
18844                                                    )))
18845                                                }
18846                                                "DAY" => Ok(Expression::Function(Box::new(
18847                                                    Function::new(
18848                                                        "DATE_ADD".to_string(),
18849                                                        vec![arg2, arg1],
18850                                                    ),
18851                                                ))),
18852                                                _ => {
18853                                                    let unit = Expression::Identifier(
18854                                                        Identifier::new(&unit_str),
18855                                                    );
18856                                                    Ok(Expression::Function(Box::new(
18857                                                        Function::new(
18858                                                            "DATE_ADD".to_string(),
18859                                                            vec![unit, arg1, arg2],
18860                                                        ),
18861                                                    )))
18862                                                }
18863                                            }
18864                                        } else {
18865                                            // Non-TSQL source: keep 3-arg DATE_ADD(UNIT, val, date)
18866                                            let unit =
18867                                                Expression::Identifier(Identifier::new(&unit_str));
18868                                            Ok(Expression::Function(Box::new(Function::new(
18869                                                "DATE_ADD".to_string(),
18870                                                vec![unit, arg1, arg2],
18871                                            ))))
18872                                        }
18873                                    }
18874                                    DialectType::Hive => match unit_str.as_str() {
18875                                        "MONTH" => {
18876                                            Ok(Expression::Function(Box::new(Function::new(
18877                                                "ADD_MONTHS".to_string(),
18878                                                vec![arg2, arg1],
18879                                            ))))
18880                                        }
18881                                        _ => Ok(Expression::Function(Box::new(Function::new(
18882                                            "DATE_ADD".to_string(),
18883                                            vec![arg2, arg1],
18884                                        )))),
18885                                    },
18886                                    DialectType::Presto
18887                                    | DialectType::Trino
18888                                    | DialectType::Athena => {
18889                                        // Cast string literal date to TIMESTAMP
18890                                        let arg2 = if matches!(
18891                                            &arg2,
18892                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18893                                        ) {
18894                                            Expression::Cast(Box::new(Cast {
18895                                                this: arg2,
18896                                                to: DataType::Timestamp {
18897                                                    precision: None,
18898                                                    timezone: false,
18899                                                },
18900                                                trailing_comments: Vec::new(),
18901                                                double_colon_syntax: false,
18902                                                format: None,
18903                                                default: None,
18904                                                inferred_type: None,
18905                                            }))
18906                                        } else {
18907                                            arg2
18908                                        };
18909                                        Ok(Expression::Function(Box::new(Function::new(
18910                                            "DATE_ADD".to_string(),
18911                                            vec![Expression::string(&unit_str), arg1, arg2],
18912                                        ))))
18913                                    }
18914                                    DialectType::MySQL => {
18915                                        let iu = Self::parse_interval_unit_static(&unit_str);
18916                                        Ok(Expression::DateAdd(Box::new(
18917                                            crate::expressions::DateAddFunc {
18918                                                this: arg2,
18919                                                interval: arg1,
18920                                                unit: iu,
18921                                            },
18922                                        )))
18923                                    }
18924                                    DialectType::PostgreSQL => {
18925                                        // Cast string literal date to TIMESTAMP
18926                                        let arg2 = if matches!(
18927                                            &arg2,
18928                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18929                                        ) {
18930                                            Expression::Cast(Box::new(Cast {
18931                                                this: arg2,
18932                                                to: DataType::Timestamp {
18933                                                    precision: None,
18934                                                    timezone: false,
18935                                                },
18936                                                trailing_comments: Vec::new(),
18937                                                double_colon_syntax: false,
18938                                                format: None,
18939                                                default: None,
18940                                                inferred_type: None,
18941                                            }))
18942                                        } else {
18943                                            arg2
18944                                        };
18945                                        let interval = Expression::Interval(Box::new(
18946                                            crate::expressions::Interval {
18947                                                this: Some(Expression::string(&format!(
18948                                                    "{} {}",
18949                                                    Self::expr_to_string_static(&arg1),
18950                                                    unit_str
18951                                                ))),
18952                                                unit: None,
18953                                            },
18954                                        ));
18955                                        Ok(Expression::Add(Box::new(
18956                                            crate::expressions::BinaryOp::new(arg2, interval),
18957                                        )))
18958                                    }
18959                                    DialectType::BigQuery => {
18960                                        let iu = Self::parse_interval_unit_static(&unit_str);
18961                                        let interval = Expression::Interval(Box::new(
18962                                            crate::expressions::Interval {
18963                                                this: Some(arg1),
18964                                                unit: Some(
18965                                                    crate::expressions::IntervalUnitSpec::Simple {
18966                                                        unit: iu,
18967                                                        use_plural: false,
18968                                                    },
18969                                                ),
18970                                            },
18971                                        ));
18972                                        // Non-TSQL sources: CAST string literal to DATETIME
18973                                        let arg2 = if !matches!(
18974                                            source,
18975                                            DialectType::TSQL | DialectType::Fabric
18976                                        ) && matches!(
18977                                            &arg2,
18978                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
18979                                        ) {
18980                                            Expression::Cast(Box::new(Cast {
18981                                                this: arg2,
18982                                                to: DataType::Custom {
18983                                                    name: "DATETIME".to_string(),
18984                                                },
18985                                                trailing_comments: Vec::new(),
18986                                                double_colon_syntax: false,
18987                                                format: None,
18988                                                default: None,
18989                                                inferred_type: None,
18990                                            }))
18991                                        } else {
18992                                            arg2
18993                                        };
18994                                        Ok(Expression::Function(Box::new(Function::new(
18995                                            "DATE_ADD".to_string(),
18996                                            vec![arg2, interval],
18997                                        ))))
18998                                    }
18999                                    _ => {
19000                                        let unit =
19001                                            Expression::Identifier(Identifier::new(&unit_str));
19002                                        Ok(Expression::Function(Box::new(Function::new(
19003                                            "DATEADD".to_string(),
19004                                            vec![unit, arg1, arg2],
19005                                        ))))
19006                                    }
19007                                }
19008                            }
19009                            // DATE_ADD - 3-arg: either (unit, val, date) from Presto/ClickHouse
19010                            // or (date, val, 'UNIT') from Generic canonical form
19011                            "DATE_ADD" if f.args.len() == 3 => {
19012                                let mut args = f.args;
19013                                let arg0 = args.remove(0);
19014                                let arg1 = args.remove(0);
19015                                let arg2 = args.remove(0);
19016                                // Detect Generic canonical form: DATE_ADD(date, amount, 'UNIT')
19017                                // where arg2 is a string literal matching a unit name
19018                                let arg2_unit = match &arg2 {
19019                                    Expression::Literal(lit)
19020                                        if matches!(lit.as_ref(), Literal::String(_)) =>
19021                                    {
19022                                        let Literal::String(s) = lit.as_ref() else {
19023                                            unreachable!()
19024                                        };
19025                                        let u = s.to_ascii_uppercase();
19026                                        if matches!(
19027                                            u.as_str(),
19028                                            "DAY"
19029                                                | "MONTH"
19030                                                | "YEAR"
19031                                                | "HOUR"
19032                                                | "MINUTE"
19033                                                | "SECOND"
19034                                                | "WEEK"
19035                                                | "QUARTER"
19036                                                | "MILLISECOND"
19037                                                | "MICROSECOND"
19038                                        ) {
19039                                            Some(u)
19040                                        } else {
19041                                            None
19042                                        }
19043                                    }
19044                                    _ => None,
19045                                };
19046                                // Reorder: if arg2 is the unit, swap to (unit, val, date) form
19047                                let (unit_str, val, date) = if let Some(u) = arg2_unit {
19048                                    (u, arg1, arg0)
19049                                } else {
19050                                    (Self::get_unit_str_static(&arg0), arg1, arg2)
19051                                };
19052                                // Alias for backward compat with the rest of the match
19053                                let arg1 = val;
19054                                let arg2 = date;
19055
19056                                match target {
19057                                    DialectType::Presto
19058                                    | DialectType::Trino
19059                                    | DialectType::Athena => {
19060                                        Ok(Expression::Function(Box::new(Function::new(
19061                                            "DATE_ADD".to_string(),
19062                                            vec![Expression::string(&unit_str), arg1, arg2],
19063                                        ))))
19064                                    }
19065                                    DialectType::DuckDB => {
19066                                        let iu = Self::parse_interval_unit_static(&unit_str);
19067                                        let interval = Expression::Interval(Box::new(
19068                                            crate::expressions::Interval {
19069                                                this: Some(arg1),
19070                                                unit: Some(
19071                                                    crate::expressions::IntervalUnitSpec::Simple {
19072                                                        unit: iu,
19073                                                        use_plural: false,
19074                                                    },
19075                                                ),
19076                                            },
19077                                        ));
19078                                        Ok(Expression::Add(Box::new(
19079                                            crate::expressions::BinaryOp::new(arg2, interval),
19080                                        )))
19081                                    }
19082                                    DialectType::PostgreSQL
19083                                    | DialectType::Materialize
19084                                    | DialectType::RisingWave => {
19085                                        // PostgreSQL: x + INTERVAL '1 DAY'
19086                                        let amount_str = Self::expr_to_string_static(&arg1);
19087                                        let interval = Expression::Interval(Box::new(
19088                                            crate::expressions::Interval {
19089                                                this: Some(Expression::string(&format!(
19090                                                    "{} {}",
19091                                                    amount_str, unit_str
19092                                                ))),
19093                                                unit: None,
19094                                            },
19095                                        ));
19096                                        Ok(Expression::Add(Box::new(
19097                                            crate::expressions::BinaryOp::new(arg2, interval),
19098                                        )))
19099                                    }
19100                                    DialectType::Snowflake
19101                                    | DialectType::TSQL
19102                                    | DialectType::Redshift => {
19103                                        let unit =
19104                                            Expression::Identifier(Identifier::new(&unit_str));
19105                                        Ok(Expression::Function(Box::new(Function::new(
19106                                            "DATEADD".to_string(),
19107                                            vec![unit, arg1, arg2],
19108                                        ))))
19109                                    }
19110                                    DialectType::BigQuery
19111                                    | DialectType::MySQL
19112                                    | DialectType::Doris
19113                                    | DialectType::StarRocks
19114                                    | DialectType::Drill => {
19115                                        // DATE_ADD(date, INTERVAL amount UNIT)
19116                                        let iu = Self::parse_interval_unit_static(&unit_str);
19117                                        let interval = Expression::Interval(Box::new(
19118                                            crate::expressions::Interval {
19119                                                this: Some(arg1),
19120                                                unit: Some(
19121                                                    crate::expressions::IntervalUnitSpec::Simple {
19122                                                        unit: iu,
19123                                                        use_plural: false,
19124                                                    },
19125                                                ),
19126                                            },
19127                                        ));
19128                                        Ok(Expression::Function(Box::new(Function::new(
19129                                            "DATE_ADD".to_string(),
19130                                            vec![arg2, interval],
19131                                        ))))
19132                                    }
19133                                    DialectType::SQLite => {
19134                                        // SQLite: DATE(x, '1 DAY')
19135                                        // Build the string '1 DAY' from amount and unit
19136                                        let amount_str = match &arg1 {
19137                                            Expression::Literal(lit)
19138                                                if matches!(lit.as_ref(), Literal::Number(_)) =>
19139                                            {
19140                                                let Literal::Number(n) = lit.as_ref() else {
19141                                                    unreachable!()
19142                                                };
19143                                                n.clone()
19144                                            }
19145                                            _ => "1".to_string(),
19146                                        };
19147                                        Ok(Expression::Function(Box::new(Function::new(
19148                                            "DATE".to_string(),
19149                                            vec![
19150                                                arg2,
19151                                                Expression::string(format!(
19152                                                    "{} {}",
19153                                                    amount_str, unit_str
19154                                                )),
19155                                            ],
19156                                        ))))
19157                                    }
19158                                    DialectType::Dremio => {
19159                                        // Dremio: DATE_ADD(date, amount) - drops unit
19160                                        Ok(Expression::Function(Box::new(Function::new(
19161                                            "DATE_ADD".to_string(),
19162                                            vec![arg2, arg1],
19163                                        ))))
19164                                    }
19165                                    DialectType::Spark => {
19166                                        // Spark: DATE_ADD(date, val) for DAY, or DATEADD(UNIT, val, date)
19167                                        if unit_str == "DAY" {
19168                                            Ok(Expression::Function(Box::new(Function::new(
19169                                                "DATE_ADD".to_string(),
19170                                                vec![arg2, arg1],
19171                                            ))))
19172                                        } else {
19173                                            let unit =
19174                                                Expression::Identifier(Identifier::new(&unit_str));
19175                                            Ok(Expression::Function(Box::new(Function::new(
19176                                                "DATE_ADD".to_string(),
19177                                                vec![unit, arg1, arg2],
19178                                            ))))
19179                                        }
19180                                    }
19181                                    DialectType::Databricks => {
19182                                        let unit =
19183                                            Expression::Identifier(Identifier::new(&unit_str));
19184                                        Ok(Expression::Function(Box::new(Function::new(
19185                                            "DATE_ADD".to_string(),
19186                                            vec![unit, arg1, arg2],
19187                                        ))))
19188                                    }
19189                                    DialectType::Hive => {
19190                                        // Hive: DATE_ADD(date, val) for DAY
19191                                        Ok(Expression::Function(Box::new(Function::new(
19192                                            "DATE_ADD".to_string(),
19193                                            vec![arg2, arg1],
19194                                        ))))
19195                                    }
19196                                    _ => {
19197                                        let unit =
19198                                            Expression::Identifier(Identifier::new(&unit_str));
19199                                        Ok(Expression::Function(Box::new(Function::new(
19200                                            "DATE_ADD".to_string(),
19201                                            vec![unit, arg1, arg2],
19202                                        ))))
19203                                    }
19204                                }
19205                            }
19206                            // DATE_ADD(date, days) - 2-arg Hive/Spark/Generic form (add days)
19207                            "DATE_ADD"
19208                                if f.args.len() == 2
19209                                    && matches!(
19210                                        source,
19211                                        DialectType::Hive
19212                                            | DialectType::Spark
19213                                            | DialectType::Databricks
19214                                            | DialectType::Generic
19215                                    ) =>
19216                            {
19217                                let mut args = f.args;
19218                                let date = args.remove(0);
19219                                let days = args.remove(0);
19220                                match target {
19221                                    DialectType::Hive | DialectType::Spark => {
19222                                        // Keep as DATE_ADD(date, days) for Hive/Spark
19223                                        Ok(Expression::Function(Box::new(Function::new(
19224                                            "DATE_ADD".to_string(),
19225                                            vec![date, days],
19226                                        ))))
19227                                    }
19228                                    DialectType::Databricks => Ok(Expression::Function(Box::new(
19229                                        Function::new("DATE_ADD".to_string(), vec![date, days]),
19230                                    ))),
19231                                    DialectType::DuckDB => {
19232                                        // DuckDB: CAST(date AS DATE) + INTERVAL days DAY
19233                                        let cast_date = Self::ensure_cast_date(date);
19234                                        // Wrap complex expressions (like Mul from DATE_SUB negation) in Paren
19235                                        let interval_val = if matches!(
19236                                            days,
19237                                            Expression::Mul(_)
19238                                                | Expression::Sub(_)
19239                                                | Expression::Add(_)
19240                                        ) {
19241                                            Expression::Paren(Box::new(crate::expressions::Paren {
19242                                                this: days,
19243                                                trailing_comments: vec![],
19244                                            }))
19245                                        } else {
19246                                            days
19247                                        };
19248                                        let interval = Expression::Interval(Box::new(
19249                                            crate::expressions::Interval {
19250                                                this: Some(interval_val),
19251                                                unit: Some(
19252                                                    crate::expressions::IntervalUnitSpec::Simple {
19253                                                        unit: crate::expressions::IntervalUnit::Day,
19254                                                        use_plural: false,
19255                                                    },
19256                                                ),
19257                                            },
19258                                        ));
19259                                        Ok(Expression::Add(Box::new(
19260                                            crate::expressions::BinaryOp::new(cast_date, interval),
19261                                        )))
19262                                    }
19263                                    DialectType::Snowflake => {
19264                                        // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
19265                                        let cast_date = if matches!(
19266                                            source,
19267                                            DialectType::Hive
19268                                                | DialectType::Spark
19269                                                | DialectType::Databricks
19270                                        ) {
19271                                            if matches!(
19272                                                date,
19273                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
19274                                            ) {
19275                                                Self::double_cast_timestamp_date(date)
19276                                            } else {
19277                                                date
19278                                            }
19279                                        } else {
19280                                            date
19281                                        };
19282                                        Ok(Expression::Function(Box::new(Function::new(
19283                                            "DATEADD".to_string(),
19284                                            vec![
19285                                                Expression::Identifier(Identifier::new("DAY")),
19286                                                days,
19287                                                cast_date,
19288                                            ],
19289                                        ))))
19290                                    }
19291                                    DialectType::Redshift => {
19292                                        Ok(Expression::Function(Box::new(Function::new(
19293                                            "DATEADD".to_string(),
19294                                            vec![
19295                                                Expression::Identifier(Identifier::new("DAY")),
19296                                                days,
19297                                                date,
19298                                            ],
19299                                        ))))
19300                                    }
19301                                    DialectType::TSQL | DialectType::Fabric => {
19302                                        // For Hive source with string literal date, use CAST(CAST(date AS DATETIME2) AS DATE)
19303                                        // But Databricks DATE_ADD doesn't need this wrapping for TSQL
19304                                        let cast_date = if matches!(
19305                                            source,
19306                                            DialectType::Hive
19307                                                | DialectType::Spark
19308                                                | DialectType::Databricks
19309                                        ) {
19310                                            if matches!(
19311                                                date,
19312                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
19313                                            ) {
19314                                                Self::double_cast_datetime2_date(date)
19315                                            } else {
19316                                                date
19317                                            }
19318                                        } else {
19319                                            date
19320                                        };
19321                                        Ok(Expression::Function(Box::new(Function::new(
19322                                            "DATEADD".to_string(),
19323                                            vec![
19324                                                Expression::Identifier(Identifier::new("DAY")),
19325                                                days,
19326                                                cast_date,
19327                                            ],
19328                                        ))))
19329                                    }
19330                                    DialectType::Presto
19331                                    | DialectType::Trino
19332                                    | DialectType::Athena => {
19333                                        // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
19334                                        let cast_date = if matches!(
19335                                            source,
19336                                            DialectType::Hive
19337                                                | DialectType::Spark
19338                                                | DialectType::Databricks
19339                                        ) {
19340                                            if matches!(
19341                                                date,
19342                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
19343                                            ) {
19344                                                Self::double_cast_timestamp_date(date)
19345                                            } else {
19346                                                date
19347                                            }
19348                                        } else {
19349                                            date
19350                                        };
19351                                        Ok(Expression::Function(Box::new(Function::new(
19352                                            "DATE_ADD".to_string(),
19353                                            vec![Expression::string("DAY"), days, cast_date],
19354                                        ))))
19355                                    }
19356                                    DialectType::BigQuery => {
19357                                        // For Hive/Spark source, wrap date in CAST(CAST(date AS DATETIME) AS DATE)
19358                                        let cast_date = if matches!(
19359                                            source,
19360                                            DialectType::Hive
19361                                                | DialectType::Spark
19362                                                | DialectType::Databricks
19363                                        ) {
19364                                            Self::double_cast_datetime_date(date)
19365                                        } else {
19366                                            date
19367                                        };
19368                                        // Wrap complex expressions in Paren for interval
19369                                        let interval_val = if matches!(
19370                                            days,
19371                                            Expression::Mul(_)
19372                                                | Expression::Sub(_)
19373                                                | Expression::Add(_)
19374                                        ) {
19375                                            Expression::Paren(Box::new(crate::expressions::Paren {
19376                                                this: days,
19377                                                trailing_comments: vec![],
19378                                            }))
19379                                        } else {
19380                                            days
19381                                        };
19382                                        let interval = Expression::Interval(Box::new(
19383                                            crate::expressions::Interval {
19384                                                this: Some(interval_val),
19385                                                unit: Some(
19386                                                    crate::expressions::IntervalUnitSpec::Simple {
19387                                                        unit: crate::expressions::IntervalUnit::Day,
19388                                                        use_plural: false,
19389                                                    },
19390                                                ),
19391                                            },
19392                                        ));
19393                                        Ok(Expression::Function(Box::new(Function::new(
19394                                            "DATE_ADD".to_string(),
19395                                            vec![cast_date, interval],
19396                                        ))))
19397                                    }
19398                                    DialectType::MySQL => {
19399                                        let iu = crate::expressions::IntervalUnit::Day;
19400                                        Ok(Expression::DateAdd(Box::new(
19401                                            crate::expressions::DateAddFunc {
19402                                                this: date,
19403                                                interval: days,
19404                                                unit: iu,
19405                                            },
19406                                        )))
19407                                    }
19408                                    DialectType::PostgreSQL => {
19409                                        let interval = Expression::Interval(Box::new(
19410                                            crate::expressions::Interval {
19411                                                this: Some(Expression::string(&format!(
19412                                                    "{} DAY",
19413                                                    Self::expr_to_string_static(&days)
19414                                                ))),
19415                                                unit: None,
19416                                            },
19417                                        ));
19418                                        Ok(Expression::Add(Box::new(
19419                                            crate::expressions::BinaryOp::new(date, interval),
19420                                        )))
19421                                    }
19422                                    DialectType::Doris
19423                                    | DialectType::StarRocks
19424                                    | DialectType::Drill => {
19425                                        // DATE_ADD(date, INTERVAL days DAY)
19426                                        let interval = Expression::Interval(Box::new(
19427                                            crate::expressions::Interval {
19428                                                this: Some(days),
19429                                                unit: Some(
19430                                                    crate::expressions::IntervalUnitSpec::Simple {
19431                                                        unit: crate::expressions::IntervalUnit::Day,
19432                                                        use_plural: false,
19433                                                    },
19434                                                ),
19435                                            },
19436                                        ));
19437                                        Ok(Expression::Function(Box::new(Function::new(
19438                                            "DATE_ADD".to_string(),
19439                                            vec![date, interval],
19440                                        ))))
19441                                    }
19442                                    _ => Ok(Expression::Function(Box::new(Function::new(
19443                                        "DATE_ADD".to_string(),
19444                                        vec![date, days],
19445                                    )))),
19446                                }
19447                            }
19448                            // DATE_ADD(date, INTERVAL val UNIT) - MySQL 2-arg form with INTERVAL as 2nd arg
19449                            "DATE_ADD"
19450                                if f.args.len() == 2
19451                                    && matches!(
19452                                        source,
19453                                        DialectType::MySQL | DialectType::SingleStore
19454                                    )
19455                                    && matches!(&f.args[1], Expression::Interval(_)) =>
19456                            {
19457                                let mut args = f.args;
19458                                let date = args.remove(0);
19459                                let interval_expr = args.remove(0);
19460                                let (val, unit) = Self::extract_interval_parts(&interval_expr)
19461                                    .unwrap_or_else(|| {
19462                                        (
19463                                            interval_expr.clone(),
19464                                            crate::expressions::IntervalUnit::Day,
19465                                        )
19466                                    });
19467                                let unit_str = Self::interval_unit_to_string(&unit);
19468                                let is_literal = matches!(&val,
19469                                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_) | Literal::String(_))
19470                                );
19471
19472                                match target {
19473                                    DialectType::MySQL | DialectType::SingleStore => {
19474                                        // Keep as DATE_ADD(date, INTERVAL val UNIT)
19475                                        Ok(Expression::Function(Box::new(Function::new(
19476                                            "DATE_ADD".to_string(),
19477                                            vec![date, interval_expr],
19478                                        ))))
19479                                    }
19480                                    DialectType::PostgreSQL => {
19481                                        if is_literal {
19482                                            // Literal: date + INTERVAL 'val UNIT'
19483                                            let interval = Expression::Interval(Box::new(
19484                                                crate::expressions::Interval {
19485                                                    this: Some(Expression::Literal(Box::new(
19486                                                        Literal::String(format!(
19487                                                            "{} {}",
19488                                                            Self::expr_to_string(&val),
19489                                                            unit_str
19490                                                        )),
19491                                                    ))),
19492                                                    unit: None,
19493                                                },
19494                                            ));
19495                                            Ok(Expression::Add(Box::new(
19496                                                crate::expressions::BinaryOp::new(date, interval),
19497                                            )))
19498                                        } else {
19499                                            // Non-literal (column ref): date + INTERVAL '1 UNIT' * val
19500                                            let interval_one = Expression::Interval(Box::new(
19501                                                crate::expressions::Interval {
19502                                                    this: Some(Expression::Literal(Box::new(
19503                                                        Literal::String(format!("1 {}", unit_str)),
19504                                                    ))),
19505                                                    unit: None,
19506                                                },
19507                                            ));
19508                                            let mul = Expression::Mul(Box::new(
19509                                                crate::expressions::BinaryOp::new(
19510                                                    interval_one,
19511                                                    val,
19512                                                ),
19513                                            ));
19514                                            Ok(Expression::Add(Box::new(
19515                                                crate::expressions::BinaryOp::new(date, mul),
19516                                            )))
19517                                        }
19518                                    }
19519                                    _ => {
19520                                        // Default: keep as DATE_ADD(date, interval)
19521                                        Ok(Expression::Function(Box::new(Function::new(
19522                                            "DATE_ADD".to_string(),
19523                                            vec![date, interval_expr],
19524                                        ))))
19525                                    }
19526                                }
19527                            }
19528                            // DATE_SUB(date, days) - 2-arg Hive/Spark form (subtract days)
19529                            "DATE_SUB"
19530                                if f.args.len() == 2
19531                                    && matches!(
19532                                        source,
19533                                        DialectType::Hive
19534                                            | DialectType::Spark
19535                                            | DialectType::Databricks
19536                                    ) =>
19537                            {
19538                                let mut args = f.args;
19539                                let date = args.remove(0);
19540                                let days = args.remove(0);
19541                                // Helper to create days * -1
19542                                let make_neg_days = |d: Expression| -> Expression {
19543                                    Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
19544                                        d,
19545                                        Expression::Literal(Box::new(Literal::Number(
19546                                            "-1".to_string(),
19547                                        ))),
19548                                    )))
19549                                };
19550                                let is_string_literal = matches!(date, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)));
19551                                match target {
19552                                    DialectType::Hive
19553                                    | DialectType::Spark
19554                                    | DialectType::Databricks => {
19555                                        // Keep as DATE_SUB(date, days) for Hive/Spark
19556                                        Ok(Expression::Function(Box::new(Function::new(
19557                                            "DATE_SUB".to_string(),
19558                                            vec![date, days],
19559                                        ))))
19560                                    }
19561                                    DialectType::DuckDB => {
19562                                        let cast_date = Self::ensure_cast_date(date);
19563                                        let neg = make_neg_days(days);
19564                                        let interval = Expression::Interval(Box::new(
19565                                            crate::expressions::Interval {
19566                                                this: Some(Expression::Paren(Box::new(
19567                                                    crate::expressions::Paren {
19568                                                        this: neg,
19569                                                        trailing_comments: vec![],
19570                                                    },
19571                                                ))),
19572                                                unit: Some(
19573                                                    crate::expressions::IntervalUnitSpec::Simple {
19574                                                        unit: crate::expressions::IntervalUnit::Day,
19575                                                        use_plural: false,
19576                                                    },
19577                                                ),
19578                                            },
19579                                        ));
19580                                        Ok(Expression::Add(Box::new(
19581                                            crate::expressions::BinaryOp::new(cast_date, interval),
19582                                        )))
19583                                    }
19584                                    DialectType::Snowflake => {
19585                                        let cast_date = if is_string_literal {
19586                                            Self::double_cast_timestamp_date(date)
19587                                        } else {
19588                                            date
19589                                        };
19590                                        let neg = make_neg_days(days);
19591                                        Ok(Expression::Function(Box::new(Function::new(
19592                                            "DATEADD".to_string(),
19593                                            vec![
19594                                                Expression::Identifier(Identifier::new("DAY")),
19595                                                neg,
19596                                                cast_date,
19597                                            ],
19598                                        ))))
19599                                    }
19600                                    DialectType::Redshift => {
19601                                        let neg = make_neg_days(days);
19602                                        Ok(Expression::Function(Box::new(Function::new(
19603                                            "DATEADD".to_string(),
19604                                            vec![
19605                                                Expression::Identifier(Identifier::new("DAY")),
19606                                                neg,
19607                                                date,
19608                                            ],
19609                                        ))))
19610                                    }
19611                                    DialectType::TSQL | DialectType::Fabric => {
19612                                        let cast_date = if is_string_literal {
19613                                            Self::double_cast_datetime2_date(date)
19614                                        } else {
19615                                            date
19616                                        };
19617                                        let neg = make_neg_days(days);
19618                                        Ok(Expression::Function(Box::new(Function::new(
19619                                            "DATEADD".to_string(),
19620                                            vec![
19621                                                Expression::Identifier(Identifier::new("DAY")),
19622                                                neg,
19623                                                cast_date,
19624                                            ],
19625                                        ))))
19626                                    }
19627                                    DialectType::Presto
19628                                    | DialectType::Trino
19629                                    | DialectType::Athena => {
19630                                        let cast_date = if is_string_literal {
19631                                            Self::double_cast_timestamp_date(date)
19632                                        } else {
19633                                            date
19634                                        };
19635                                        let neg = make_neg_days(days);
19636                                        Ok(Expression::Function(Box::new(Function::new(
19637                                            "DATE_ADD".to_string(),
19638                                            vec![Expression::string("DAY"), neg, cast_date],
19639                                        ))))
19640                                    }
19641                                    DialectType::BigQuery => {
19642                                        let cast_date = if is_string_literal {
19643                                            Self::double_cast_datetime_date(date)
19644                                        } else {
19645                                            date
19646                                        };
19647                                        let neg = make_neg_days(days);
19648                                        let interval = Expression::Interval(Box::new(
19649                                            crate::expressions::Interval {
19650                                                this: Some(Expression::Paren(Box::new(
19651                                                    crate::expressions::Paren {
19652                                                        this: neg,
19653                                                        trailing_comments: vec![],
19654                                                    },
19655                                                ))),
19656                                                unit: Some(
19657                                                    crate::expressions::IntervalUnitSpec::Simple {
19658                                                        unit: crate::expressions::IntervalUnit::Day,
19659                                                        use_plural: false,
19660                                                    },
19661                                                ),
19662                                            },
19663                                        ));
19664                                        Ok(Expression::Function(Box::new(Function::new(
19665                                            "DATE_ADD".to_string(),
19666                                            vec![cast_date, interval],
19667                                        ))))
19668                                    }
19669                                    _ => Ok(Expression::Function(Box::new(Function::new(
19670                                        "DATE_SUB".to_string(),
19671                                        vec![date, days],
19672                                    )))),
19673                                }
19674                            }
19675                            // ADD_MONTHS(date, val) -> target-specific
19676                            "ADD_MONTHS" if f.args.len() == 2 => {
19677                                let mut args = f.args;
19678                                let date = args.remove(0);
19679                                let val = args.remove(0);
19680                                match target {
19681                                    DialectType::TSQL => {
19682                                        let cast_date = Self::ensure_cast_datetime2(date);
19683                                        Ok(Expression::Function(Box::new(Function::new(
19684                                            "DATEADD".to_string(),
19685                                            vec![
19686                                                Expression::Identifier(Identifier::new("MONTH")),
19687                                                val,
19688                                                cast_date,
19689                                            ],
19690                                        ))))
19691                                    }
19692                                    DialectType::DuckDB => {
19693                                        let interval = Expression::Interval(Box::new(
19694                                            crate::expressions::Interval {
19695                                                this: Some(val),
19696                                                unit: Some(
19697                                                    crate::expressions::IntervalUnitSpec::Simple {
19698                                                        unit:
19699                                                            crate::expressions::IntervalUnit::Month,
19700                                                        use_plural: false,
19701                                                    },
19702                                                ),
19703                                            },
19704                                        ));
19705                                        Ok(Expression::Add(Box::new(
19706                                            crate::expressions::BinaryOp::new(date, interval),
19707                                        )))
19708                                    }
19709                                    DialectType::Snowflake => {
19710                                        // Keep ADD_MONTHS when source is Snowflake
19711                                        if matches!(source, DialectType::Snowflake) {
19712                                            Ok(Expression::Function(Box::new(Function::new(
19713                                                "ADD_MONTHS".to_string(),
19714                                                vec![date, val],
19715                                            ))))
19716                                        } else {
19717                                            Ok(Expression::Function(Box::new(Function::new(
19718                                                "DATEADD".to_string(),
19719                                                vec![
19720                                                    Expression::Identifier(Identifier::new(
19721                                                        "MONTH",
19722                                                    )),
19723                                                    val,
19724                                                    date,
19725                                                ],
19726                                            ))))
19727                                        }
19728                                    }
19729                                    DialectType::Redshift => {
19730                                        Ok(Expression::Function(Box::new(Function::new(
19731                                            "DATEADD".to_string(),
19732                                            vec![
19733                                                Expression::Identifier(Identifier::new("MONTH")),
19734                                                val,
19735                                                date,
19736                                            ],
19737                                        ))))
19738                                    }
19739                                    DialectType::Presto
19740                                    | DialectType::Trino
19741                                    | DialectType::Athena => {
19742                                        Ok(Expression::Function(Box::new(Function::new(
19743                                            "DATE_ADD".to_string(),
19744                                            vec![Expression::string("MONTH"), val, date],
19745                                        ))))
19746                                    }
19747                                    DialectType::BigQuery => {
19748                                        let interval = Expression::Interval(Box::new(
19749                                            crate::expressions::Interval {
19750                                                this: Some(val),
19751                                                unit: Some(
19752                                                    crate::expressions::IntervalUnitSpec::Simple {
19753                                                        unit:
19754                                                            crate::expressions::IntervalUnit::Month,
19755                                                        use_plural: false,
19756                                                    },
19757                                                ),
19758                                            },
19759                                        ));
19760                                        Ok(Expression::Function(Box::new(Function::new(
19761                                            "DATE_ADD".to_string(),
19762                                            vec![date, interval],
19763                                        ))))
19764                                    }
19765                                    _ => Ok(Expression::Function(Box::new(Function::new(
19766                                        "ADD_MONTHS".to_string(),
19767                                        vec![date, val],
19768                                    )))),
19769                                }
19770                            }
19771                            // DATETRUNC(unit, date) - TSQL form -> DATE_TRUNC for other targets
19772                            "DATETRUNC" if f.args.len() == 2 => {
19773                                let mut args = f.args;
19774                                let arg0 = args.remove(0);
19775                                let arg1 = args.remove(0);
19776                                let unit_str = Self::get_unit_str_static(&arg0);
19777                                match target {
19778                                    DialectType::TSQL | DialectType::Fabric => {
19779                                        // Keep as DATETRUNC for TSQL - the target handler will uppercase the unit
19780                                        Ok(Expression::Function(Box::new(Function::new(
19781                                            "DATETRUNC".to_string(),
19782                                            vec![
19783                                                Expression::Identifier(Identifier::new(&unit_str)),
19784                                                arg1,
19785                                            ],
19786                                        ))))
19787                                    }
19788                                    DialectType::DuckDB => {
19789                                        // DuckDB: DATE_TRUNC('UNIT', expr) with CAST for string literals
19790                                        let date = Self::ensure_cast_timestamp(arg1);
19791                                        Ok(Expression::Function(Box::new(Function::new(
19792                                            "DATE_TRUNC".to_string(),
19793                                            vec![Expression::string(&unit_str), date],
19794                                        ))))
19795                                    }
19796                                    DialectType::ClickHouse => {
19797                                        // ClickHouse: dateTrunc('UNIT', expr)
19798                                        Ok(Expression::Function(Box::new(Function::new(
19799                                            "dateTrunc".to_string(),
19800                                            vec![Expression::string(&unit_str), arg1],
19801                                        ))))
19802                                    }
19803                                    _ => {
19804                                        // Standard: DATE_TRUNC('UNIT', expr)
19805                                        let unit = Expression::string(&unit_str);
19806                                        Ok(Expression::Function(Box::new(Function::new(
19807                                            "DATE_TRUNC".to_string(),
19808                                            vec![unit, arg1],
19809                                        ))))
19810                                    }
19811                                }
19812                            }
19813                            // GETDATE() -> CURRENT_TIMESTAMP for non-TSQL targets
19814                            "GETDATE" if f.args.is_empty() => match target {
19815                                DialectType::TSQL => Ok(Expression::Function(f)),
19816                                DialectType::Redshift => Ok(Expression::Function(Box::new(
19817                                    Function::new("GETDATE".to_string(), vec![]),
19818                                ))),
19819                                _ => Ok(Expression::CurrentTimestamp(
19820                                    crate::expressions::CurrentTimestamp {
19821                                        precision: None,
19822                                        sysdate: false,
19823                                    },
19824                                )),
19825                            },
19826                            // TO_HEX(x) / HEX(x) -> target-specific hex function
19827                            "TO_HEX" | "HEX" if f.args.len() == 1 => {
19828                                let name = match target {
19829                                    DialectType::Presto | DialectType::Trino => "TO_HEX",
19830                                    DialectType::Spark
19831                                    | DialectType::Databricks
19832                                    | DialectType::Hive => "HEX",
19833                                    DialectType::DuckDB
19834                                    | DialectType::PostgreSQL
19835                                    | DialectType::Redshift => "TO_HEX",
19836                                    _ => &f.name,
19837                                };
19838                                Ok(Expression::Function(Box::new(Function::new(
19839                                    name.to_string(),
19840                                    f.args,
19841                                ))))
19842                            }
19843                            // FROM_HEX(x) / UNHEX(x) -> target-specific hex decode function
19844                            "FROM_HEX" | "UNHEX" if f.args.len() == 1 => {
19845                                match target {
19846                                    DialectType::BigQuery => {
19847                                        // BigQuery: UNHEX(x) -> FROM_HEX(x)
19848                                        // Special case: UNHEX(MD5(x)) -> FROM_HEX(TO_HEX(MD5(x)))
19849                                        // because BigQuery MD5 returns BYTES, not hex string
19850                                        let arg = &f.args[0];
19851                                        let wrapped_arg = match arg {
19852                                            Expression::Function(inner_f)
19853                                                if inner_f.name.eq_ignore_ascii_case("MD5")
19854                                                    || inner_f
19855                                                        .name
19856                                                        .eq_ignore_ascii_case("SHA1")
19857                                                    || inner_f
19858                                                        .name
19859                                                        .eq_ignore_ascii_case("SHA256")
19860                                                    || inner_f
19861                                                        .name
19862                                                        .eq_ignore_ascii_case("SHA512") =>
19863                                            {
19864                                                // Wrap hash function in TO_HEX for BigQuery
19865                                                Expression::Function(Box::new(Function::new(
19866                                                    "TO_HEX".to_string(),
19867                                                    vec![arg.clone()],
19868                                                )))
19869                                            }
19870                                            _ => f.args.into_iter().next().unwrap(),
19871                                        };
19872                                        Ok(Expression::Function(Box::new(Function::new(
19873                                            "FROM_HEX".to_string(),
19874                                            vec![wrapped_arg],
19875                                        ))))
19876                                    }
19877                                    _ => {
19878                                        let name = match target {
19879                                            DialectType::Presto | DialectType::Trino => "FROM_HEX",
19880                                            DialectType::Spark
19881                                            | DialectType::Databricks
19882                                            | DialectType::Hive => "UNHEX",
19883                                            _ => &f.name,
19884                                        };
19885                                        Ok(Expression::Function(Box::new(Function::new(
19886                                            name.to_string(),
19887                                            f.args,
19888                                        ))))
19889                                    }
19890                                }
19891                            }
19892                            // TO_UTF8(x) -> ENCODE(x, 'utf-8') for Spark
19893                            "TO_UTF8" if f.args.len() == 1 => match target {
19894                                DialectType::Spark | DialectType::Databricks => {
19895                                    let mut args = f.args;
19896                                    args.push(Expression::string("utf-8"));
19897                                    Ok(Expression::Function(Box::new(Function::new(
19898                                        "ENCODE".to_string(),
19899                                        args,
19900                                    ))))
19901                                }
19902                                _ => Ok(Expression::Function(f)),
19903                            },
19904                            // FROM_UTF8(x) -> DECODE(x, 'utf-8') for Spark
19905                            "FROM_UTF8" if f.args.len() == 1 => match target {
19906                                DialectType::Spark | DialectType::Databricks => {
19907                                    let mut args = f.args;
19908                                    args.push(Expression::string("utf-8"));
19909                                    Ok(Expression::Function(Box::new(Function::new(
19910                                        "DECODE".to_string(),
19911                                        args,
19912                                    ))))
19913                                }
19914                                _ => Ok(Expression::Function(f)),
19915                            },
19916                            // STARTS_WITH(x, y) / STARTSWITH(x, y) -> target-specific
19917                            "STARTS_WITH" | "STARTSWITH" if f.args.len() == 2 => {
19918                                let name = match target {
19919                                    DialectType::Spark | DialectType::Databricks => "STARTSWITH",
19920                                    DialectType::Presto | DialectType::Trino => "STARTS_WITH",
19921                                    DialectType::PostgreSQL | DialectType::Redshift => {
19922                                        "STARTS_WITH"
19923                                    }
19924                                    _ => &f.name,
19925                                };
19926                                Ok(Expression::Function(Box::new(Function::new(
19927                                    name.to_string(),
19928                                    f.args,
19929                                ))))
19930                            }
19931                            // APPROX_COUNT_DISTINCT(x) <-> APPROX_DISTINCT(x)
19932                            "APPROX_COUNT_DISTINCT" if f.args.len() >= 1 => {
19933                                let name = match target {
19934                                    DialectType::Presto
19935                                    | DialectType::Trino
19936                                    | DialectType::Athena => "APPROX_DISTINCT",
19937                                    _ => "APPROX_COUNT_DISTINCT",
19938                                };
19939                                Ok(Expression::Function(Box::new(Function::new(
19940                                    name.to_string(),
19941                                    f.args,
19942                                ))))
19943                            }
19944                            // JSON_EXTRACT -> GET_JSON_OBJECT for Spark/Hive
19945                            "JSON_EXTRACT"
19946                                if f.args.len() == 2
19947                                    && !matches!(source, DialectType::BigQuery)
19948                                    && matches!(
19949                                        target,
19950                                        DialectType::Spark
19951                                            | DialectType::Databricks
19952                                            | DialectType::Hive
19953                                    ) =>
19954                            {
19955                                Ok(Expression::Function(Box::new(Function::new(
19956                                    "GET_JSON_OBJECT".to_string(),
19957                                    f.args,
19958                                ))))
19959                            }
19960                            // JSON_EXTRACT(x, path) -> x -> path for SQLite (arrow syntax)
19961                            "JSON_EXTRACT"
19962                                if f.args.len() == 2 && matches!(target, DialectType::SQLite) =>
19963                            {
19964                                let mut args = f.args;
19965                                let path = args.remove(1);
19966                                let this = args.remove(0);
19967                                Ok(Expression::JsonExtract(Box::new(
19968                                    crate::expressions::JsonExtractFunc {
19969                                        this,
19970                                        path,
19971                                        returning: None,
19972                                        arrow_syntax: true,
19973                                        hash_arrow_syntax: false,
19974                                        wrapper_option: None,
19975                                        quotes_option: None,
19976                                        on_scalar_string: false,
19977                                        on_error: None,
19978                                    },
19979                                )))
19980                            }
19981                            // JSON_FORMAT(x) -> TO_JSON(x) for Spark, TO_JSON_STRING for BigQuery, CAST(TO_JSON(x) AS TEXT) for DuckDB
19982                            "JSON_FORMAT" if f.args.len() == 1 => {
19983                                match target {
19984                                    DialectType::Spark | DialectType::Databricks => {
19985                                        // Presto JSON_FORMAT(JSON '...') needs Spark's string-unquoting flow:
19986                                        // REGEXP_EXTRACT(TO_JSON(FROM_JSON('[...]', SCHEMA_OF_JSON('[...]'))), '^.(.*).$', 1)
19987                                        if matches!(
19988                                            source,
19989                                            DialectType::Presto
19990                                                | DialectType::Trino
19991                                                | DialectType::Athena
19992                                        ) {
19993                                            if let Some(Expression::ParseJson(pj)) = f.args.first()
19994                                            {
19995                                                if let Expression::Literal(lit) = &pj.this {
19996                                                    if let Literal::String(s) = lit.as_ref() {
19997                                                        let wrapped =
19998                                                            Expression::Literal(Box::new(
19999                                                                Literal::String(format!("[{}]", s)),
20000                                                            ));
20001                                                        let schema_of_json = Expression::Function(
20002                                                            Box::new(Function::new(
20003                                                                "SCHEMA_OF_JSON".to_string(),
20004                                                                vec![wrapped.clone()],
20005                                                            )),
20006                                                        );
20007                                                        let from_json = Expression::Function(
20008                                                            Box::new(Function::new(
20009                                                                "FROM_JSON".to_string(),
20010                                                                vec![wrapped, schema_of_json],
20011                                                            )),
20012                                                        );
20013                                                        let to_json = Expression::Function(
20014                                                            Box::new(Function::new(
20015                                                                "TO_JSON".to_string(),
20016                                                                vec![from_json],
20017                                                            )),
20018                                                        );
20019                                                        return Ok(Expression::Function(Box::new(
20020                                                            Function::new(
20021                                                                "REGEXP_EXTRACT".to_string(),
20022                                                                vec![
20023                                                                    to_json,
20024                                                                    Expression::Literal(Box::new(
20025                                                                        Literal::String(
20026                                                                            "^.(.*).$".to_string(),
20027                                                                        ),
20028                                                                    )),
20029                                                                    Expression::Literal(Box::new(
20030                                                                        Literal::Number(
20031                                                                            "1".to_string(),
20032                                                                        ),
20033                                                                    )),
20034                                                                ],
20035                                                            ),
20036                                                        )));
20037                                                    }
20038                                                }
20039                                            }
20040                                        }
20041
20042                                        // Strip inner CAST(... AS JSON) or TO_JSON() if present
20043                                        // The CastToJsonForSpark may have already converted CAST(x AS JSON) to TO_JSON(x)
20044                                        let mut args = f.args;
20045                                        if let Some(Expression::Cast(ref c)) = args.first() {
20046                                            if matches!(&c.to, DataType::Json | DataType::JsonB) {
20047                                                args = vec![c.this.clone()];
20048                                            }
20049                                        } else if let Some(Expression::Function(ref inner_f)) =
20050                                            args.first()
20051                                        {
20052                                            if inner_f.name.eq_ignore_ascii_case("TO_JSON")
20053                                                && inner_f.args.len() == 1
20054                                            {
20055                                                // Already TO_JSON(x) from CastToJsonForSpark, just use the inner arg
20056                                                args = inner_f.args.clone();
20057                                            }
20058                                        }
20059                                        Ok(Expression::Function(Box::new(Function::new(
20060                                            "TO_JSON".to_string(),
20061                                            args,
20062                                        ))))
20063                                    }
20064                                    DialectType::BigQuery => Ok(Expression::Function(Box::new(
20065                                        Function::new("TO_JSON_STRING".to_string(), f.args),
20066                                    ))),
20067                                    DialectType::DuckDB => {
20068                                        // CAST(TO_JSON(x) AS TEXT)
20069                                        let to_json = Expression::Function(Box::new(
20070                                            Function::new("TO_JSON".to_string(), f.args),
20071                                        ));
20072                                        Ok(Expression::Cast(Box::new(Cast {
20073                                            this: to_json,
20074                                            to: DataType::Text,
20075                                            trailing_comments: Vec::new(),
20076                                            double_colon_syntax: false,
20077                                            format: None,
20078                                            default: None,
20079                                            inferred_type: None,
20080                                        })))
20081                                    }
20082                                    _ => Ok(Expression::Function(f)),
20083                                }
20084                            }
20085                            // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift/Snowflake targets
20086                            "SYSDATE" if f.args.is_empty() => {
20087                                match target {
20088                                    DialectType::Oracle | DialectType::Redshift => {
20089                                        Ok(Expression::Function(f))
20090                                    }
20091                                    DialectType::Snowflake => {
20092                                        // Snowflake uses SYSDATE() with parens
20093                                        let mut f = *f;
20094                                        f.no_parens = false;
20095                                        Ok(Expression::Function(Box::new(f)))
20096                                    }
20097                                    DialectType::DuckDB => {
20098                                        // DuckDB: SYSDATE() -> CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
20099                                        Ok(Expression::AtTimeZone(Box::new(
20100                                            crate::expressions::AtTimeZone {
20101                                                this: Expression::CurrentTimestamp(
20102                                                    crate::expressions::CurrentTimestamp {
20103                                                        precision: None,
20104                                                        sysdate: false,
20105                                                    },
20106                                                ),
20107                                                zone: Expression::Literal(Box::new(
20108                                                    Literal::String("UTC".to_string()),
20109                                                )),
20110                                            },
20111                                        )))
20112                                    }
20113                                    _ => Ok(Expression::CurrentTimestamp(
20114                                        crate::expressions::CurrentTimestamp {
20115                                            precision: None,
20116                                            sysdate: true,
20117                                        },
20118                                    )),
20119                                }
20120                            }
20121                            // LOGICAL_OR(x) -> BOOL_OR(x)
20122                            "LOGICAL_OR" if f.args.len() == 1 => {
20123                                let name = match target {
20124                                    DialectType::Spark | DialectType::Databricks => "BOOL_OR",
20125                                    _ => &f.name,
20126                                };
20127                                Ok(Expression::Function(Box::new(Function::new(
20128                                    name.to_string(),
20129                                    f.args,
20130                                ))))
20131                            }
20132                            // LOGICAL_AND(x) -> BOOL_AND(x)
20133                            "LOGICAL_AND" if f.args.len() == 1 => {
20134                                let name = match target {
20135                                    DialectType::Spark | DialectType::Databricks => "BOOL_AND",
20136                                    _ => &f.name,
20137                                };
20138                                Ok(Expression::Function(Box::new(Function::new(
20139                                    name.to_string(),
20140                                    f.args,
20141                                ))))
20142                            }
20143                            // MONTHS_ADD(d, n) -> ADD_MONTHS(d, n) for Oracle
20144                            "MONTHS_ADD" if f.args.len() == 2 => match target {
20145                                DialectType::Oracle => Ok(Expression::Function(Box::new(
20146                                    Function::new("ADD_MONTHS".to_string(), f.args),
20147                                ))),
20148                                _ => Ok(Expression::Function(f)),
20149                            },
20150                            // ARRAY_JOIN(arr, sep[, null_replacement]) -> target-specific
20151                            "ARRAY_JOIN" if f.args.len() >= 2 => {
20152                                match target {
20153                                    DialectType::Spark | DialectType::Databricks => {
20154                                        // Keep as ARRAY_JOIN for Spark (it supports null_replacement)
20155                                        Ok(Expression::Function(f))
20156                                    }
20157                                    DialectType::Hive => {
20158                                        // ARRAY_JOIN(arr, sep[, null_rep]) -> CONCAT_WS(sep, arr) (drop null_replacement)
20159                                        let mut args = f.args;
20160                                        let arr = args.remove(0);
20161                                        let sep = args.remove(0);
20162                                        // Drop any remaining args (null_replacement)
20163                                        Ok(Expression::Function(Box::new(Function::new(
20164                                            "CONCAT_WS".to_string(),
20165                                            vec![sep, arr],
20166                                        ))))
20167                                    }
20168                                    DialectType::Presto | DialectType::Trino => {
20169                                        Ok(Expression::Function(f))
20170                                    }
20171                                    _ => Ok(Expression::Function(f)),
20172                                }
20173                            }
20174                            // LOCATE(substr, str, pos) 3-arg -> target-specific
20175                            // For Presto/DuckDB: STRPOS doesn't support 3-arg, need complex expansion
20176                            "LOCATE"
20177                                if f.args.len() == 3
20178                                    && matches!(
20179                                        target,
20180                                        DialectType::Presto
20181                                            | DialectType::Trino
20182                                            | DialectType::Athena
20183                                            | DialectType::DuckDB
20184                                    ) =>
20185                            {
20186                                let mut args = f.args;
20187                                let substr = args.remove(0);
20188                                let string = args.remove(0);
20189                                let pos = args.remove(0);
20190                                // STRPOS(SUBSTRING(string, pos), substr)
20191                                let substring_call = Expression::Function(Box::new(Function::new(
20192                                    "SUBSTRING".to_string(),
20193                                    vec![string.clone(), pos.clone()],
20194                                )));
20195                                let strpos_call = Expression::Function(Box::new(Function::new(
20196                                    "STRPOS".to_string(),
20197                                    vec![substring_call, substr.clone()],
20198                                )));
20199                                // STRPOS(...) + pos - 1
20200                                let pos_adjusted =
20201                                    Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
20202                                        Expression::Add(Box::new(
20203                                            crate::expressions::BinaryOp::new(
20204                                                strpos_call.clone(),
20205                                                pos.clone(),
20206                                            ),
20207                                        )),
20208                                        Expression::number(1),
20209                                    )));
20210                                // STRPOS(...) = 0
20211                                let is_zero =
20212                                    Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
20213                                        strpos_call.clone(),
20214                                        Expression::number(0),
20215                                    )));
20216
20217                                match target {
20218                                    DialectType::Presto
20219                                    | DialectType::Trino
20220                                    | DialectType::Athena => {
20221                                        // IF(STRPOS(...) = 0, 0, STRPOS(...) + pos - 1)
20222                                        Ok(Expression::Function(Box::new(Function::new(
20223                                            "IF".to_string(),
20224                                            vec![is_zero, Expression::number(0), pos_adjusted],
20225                                        ))))
20226                                    }
20227                                    DialectType::DuckDB => {
20228                                        // CASE WHEN STRPOS(...) = 0 THEN 0 ELSE STRPOS(...) + pos - 1 END
20229                                        Ok(Expression::Case(Box::new(crate::expressions::Case {
20230                                            operand: None,
20231                                            whens: vec![(is_zero, Expression::number(0))],
20232                                            else_: Some(pos_adjusted),
20233                                            comments: Vec::new(),
20234                                            inferred_type: None,
20235                                        })))
20236                                    }
20237                                    _ => Ok(Expression::Function(Box::new(Function::new(
20238                                        "LOCATE".to_string(),
20239                                        vec![substr, string, pos],
20240                                    )))),
20241                                }
20242                            }
20243                            // STRPOS(haystack, needle, occurrence) 3-arg -> INSTR(haystack, needle, 1, occurrence)
20244                            "STRPOS"
20245                                if f.args.len() == 3
20246                                    && matches!(
20247                                        target,
20248                                        DialectType::BigQuery
20249                                            | DialectType::Oracle
20250                                            | DialectType::Teradata
20251                                    ) =>
20252                            {
20253                                let mut args = f.args;
20254                                let haystack = args.remove(0);
20255                                let needle = args.remove(0);
20256                                let occurrence = args.remove(0);
20257                                Ok(Expression::Function(Box::new(Function::new(
20258                                    "INSTR".to_string(),
20259                                    vec![haystack, needle, Expression::number(1), occurrence],
20260                                ))))
20261                            }
20262                            // SCHEMA_NAME(id) -> target-specific
20263                            "SCHEMA_NAME" if f.args.len() <= 1 => match target {
20264                                DialectType::MySQL | DialectType::SingleStore => {
20265                                    Ok(Expression::Function(Box::new(Function::new(
20266                                        "SCHEMA".to_string(),
20267                                        vec![],
20268                                    ))))
20269                                }
20270                                DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
20271                                    crate::expressions::CurrentSchema { this: None },
20272                                ))),
20273                                DialectType::SQLite => Ok(Expression::string("main")),
20274                                _ => Ok(Expression::Function(f)),
20275                            },
20276                            // STRTOL(str, base) -> FROM_BASE(str, base) for Trino/Presto
20277                            "STRTOL" if f.args.len() == 2 => match target {
20278                                DialectType::Presto | DialectType::Trino => {
20279                                    Ok(Expression::Function(Box::new(Function::new(
20280                                        "FROM_BASE".to_string(),
20281                                        f.args,
20282                                    ))))
20283                                }
20284                                _ => Ok(Expression::Function(f)),
20285                            },
20286                            // EDITDIST3(a, b) -> LEVENSHTEIN(a, b) for Spark
20287                            "EDITDIST3" if f.args.len() == 2 => match target {
20288                                DialectType::Spark | DialectType::Databricks => {
20289                                    Ok(Expression::Function(Box::new(Function::new(
20290                                        "LEVENSHTEIN".to_string(),
20291                                        f.args,
20292                                    ))))
20293                                }
20294                                _ => Ok(Expression::Function(f)),
20295                            },
20296                            // FORMAT(num, decimals) from MySQL -> DuckDB FORMAT('{:,.Xf}', num)
20297                            "FORMAT"
20298                                if f.args.len() == 2
20299                                    && matches!(
20300                                        source,
20301                                        DialectType::MySQL | DialectType::SingleStore
20302                                    )
20303                                    && matches!(target, DialectType::DuckDB) =>
20304                            {
20305                                let mut args = f.args;
20306                                let num_expr = args.remove(0);
20307                                let decimals_expr = args.remove(0);
20308                                // Extract decimal count
20309                                let dec_count = match &decimals_expr {
20310                                    Expression::Literal(lit)
20311                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
20312                                    {
20313                                        let Literal::Number(n) = lit.as_ref() else {
20314                                            unreachable!()
20315                                        };
20316                                        n.clone()
20317                                    }
20318                                    _ => "0".to_string(),
20319                                };
20320                                let fmt_str = format!("{{:,.{}f}}", dec_count);
20321                                Ok(Expression::Function(Box::new(Function::new(
20322                                    "FORMAT".to_string(),
20323                                    vec![Expression::string(&fmt_str), num_expr],
20324                                ))))
20325                            }
20326                            // FORMAT(x, fmt) from TSQL -> DATE_FORMAT for Spark, or expand short codes
20327                            "FORMAT"
20328                                if f.args.len() == 2
20329                                    && matches!(
20330                                        source,
20331                                        DialectType::TSQL | DialectType::Fabric
20332                                    ) =>
20333                            {
20334                                let val_expr = f.args[0].clone();
20335                                let fmt_expr = f.args[1].clone();
20336                                // Expand unambiguous .NET single-char date format shortcodes to full patterns.
20337                                // Only expand shortcodes that are NOT also valid numeric format specifiers.
20338                                // Ambiguous: d, D, f, F, g, G (used for both dates and numbers)
20339                                // Unambiguous date: m/M (Month day), t/T (Time), y/Y (Year month)
20340                                let (expanded_fmt, is_shortcode) = match &fmt_expr {
20341                                    Expression::Literal(lit)
20342                                        if matches!(
20343                                            lit.as_ref(),
20344                                            crate::expressions::Literal::String(_)
20345                                        ) =>
20346                                    {
20347                                        let crate::expressions::Literal::String(s) = lit.as_ref()
20348                                        else {
20349                                            unreachable!()
20350                                        };
20351                                        match s.as_str() {
20352                                            "m" | "M" => (Expression::string("MMMM d"), true),
20353                                            "t" => (Expression::string("h:mm tt"), true),
20354                                            "T" => (Expression::string("h:mm:ss tt"), true),
20355                                            "y" | "Y" => (Expression::string("MMMM yyyy"), true),
20356                                            _ => (fmt_expr.clone(), false),
20357                                        }
20358                                    }
20359                                    _ => (fmt_expr.clone(), false),
20360                                };
20361                                // Check if the format looks like a date format
20362                                let is_date_format = is_shortcode
20363                                    || match &expanded_fmt {
20364                                        Expression::Literal(lit)
20365                                            if matches!(
20366                                                lit.as_ref(),
20367                                                crate::expressions::Literal::String(_)
20368                                            ) =>
20369                                        {
20370                                            let crate::expressions::Literal::String(s) =
20371                                                lit.as_ref()
20372                                            else {
20373                                                unreachable!()
20374                                            };
20375                                            // Date formats typically contain yyyy, MM, dd, MMMM, HH, etc.
20376                                            s.contains("yyyy")
20377                                                || s.contains("YYYY")
20378                                                || s.contains("MM")
20379                                                || s.contains("dd")
20380                                                || s.contains("MMMM")
20381                                                || s.contains("HH")
20382                                                || s.contains("hh")
20383                                                || s.contains("ss")
20384                                        }
20385                                        _ => false,
20386                                    };
20387                                match target {
20388                                    DialectType::Spark | DialectType::Databricks => {
20389                                        let func_name = if is_date_format {
20390                                            "DATE_FORMAT"
20391                                        } else {
20392                                            "FORMAT_NUMBER"
20393                                        };
20394                                        Ok(Expression::Function(Box::new(Function::new(
20395                                            func_name.to_string(),
20396                                            vec![val_expr, expanded_fmt],
20397                                        ))))
20398                                    }
20399                                    _ => {
20400                                        // For TSQL and other targets, expand shortcodes but keep FORMAT
20401                                        if is_shortcode {
20402                                            Ok(Expression::Function(Box::new(Function::new(
20403                                                "FORMAT".to_string(),
20404                                                vec![val_expr, expanded_fmt],
20405                                            ))))
20406                                        } else {
20407                                            Ok(Expression::Function(f))
20408                                        }
20409                                    }
20410                                }
20411                            }
20412                            // FORMAT('%s', x) from Trino/Presto -> target-specific
20413                            "FORMAT"
20414                                if f.args.len() >= 2
20415                                    && matches!(
20416                                        source,
20417                                        DialectType::Trino
20418                                            | DialectType::Presto
20419                                            | DialectType::Athena
20420                                    ) =>
20421                            {
20422                                let fmt_expr = f.args[0].clone();
20423                                let value_args: Vec<Expression> = f.args[1..].to_vec();
20424                                match target {
20425                                    // DuckDB: replace %s with {} in format string
20426                                    DialectType::DuckDB => {
20427                                        let new_fmt = match &fmt_expr {
20428                                            Expression::Literal(lit)
20429                                                if matches!(lit.as_ref(), Literal::String(_)) =>
20430                                            {
20431                                                let Literal::String(s) = lit.as_ref() else {
20432                                                    unreachable!()
20433                                                };
20434                                                Expression::Literal(Box::new(Literal::String(
20435                                                    s.replace("%s", "{}"),
20436                                                )))
20437                                            }
20438                                            _ => fmt_expr,
20439                                        };
20440                                        let mut args = vec![new_fmt];
20441                                        args.extend(value_args);
20442                                        Ok(Expression::Function(Box::new(Function::new(
20443                                            "FORMAT".to_string(),
20444                                            args,
20445                                        ))))
20446                                    }
20447                                    // Snowflake: FORMAT('%s', x) -> TO_CHAR(x) when just %s
20448                                    DialectType::Snowflake => match &fmt_expr {
20449                                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "%s" && value_args.len() == 1) =>
20450                                        {
20451                                            let Literal::String(_) = lit.as_ref() else {
20452                                                unreachable!()
20453                                            };
20454                                            Ok(Expression::Function(Box::new(Function::new(
20455                                                "TO_CHAR".to_string(),
20456                                                value_args,
20457                                            ))))
20458                                        }
20459                                        _ => Ok(Expression::Function(f)),
20460                                    },
20461                                    // Default: keep FORMAT as-is
20462                                    _ => Ok(Expression::Function(f)),
20463                                }
20464                            }
20465                            // LIST_CONTAINS / LIST_HAS / ARRAY_CONTAINS -> target-specific
20466                            "LIST_CONTAINS" | "LIST_HAS" | "ARRAY_CONTAINS"
20467                                if f.args.len() == 2 =>
20468                            {
20469                                // When coming from Snowflake source: ARRAY_CONTAINS(value, array)
20470                                // args[0]=value, args[1]=array. For DuckDB target, swap and add NULL-aware CASE.
20471                                if matches!(target, DialectType::DuckDB)
20472                                    && matches!(source, DialectType::Snowflake)
20473                                    && f.name.eq_ignore_ascii_case("ARRAY_CONTAINS")
20474                                {
20475                                    let value = f.args[0].clone();
20476                                    let array = f.args[1].clone();
20477
20478                                    // value IS NULL
20479                                    let value_is_null =
20480                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
20481                                            this: value.clone(),
20482                                            not: false,
20483                                            postfix_form: false,
20484                                        }));
20485
20486                                    // ARRAY_LENGTH(array)
20487                                    let array_length =
20488                                        Expression::Function(Box::new(Function::new(
20489                                            "ARRAY_LENGTH".to_string(),
20490                                            vec![array.clone()],
20491                                        )));
20492                                    // LIST_COUNT(array)
20493                                    let list_count = Expression::Function(Box::new(Function::new(
20494                                        "LIST_COUNT".to_string(),
20495                                        vec![array.clone()],
20496                                    )));
20497                                    // ARRAY_LENGTH(array) <> LIST_COUNT(array)
20498                                    let neq =
20499                                        Expression::Neq(Box::new(crate::expressions::BinaryOp {
20500                                            left: array_length,
20501                                            right: list_count,
20502                                            left_comments: vec![],
20503                                            operator_comments: vec![],
20504                                            trailing_comments: vec![],
20505                                            inferred_type: None,
20506                                        }));
20507                                    // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
20508                                    let nullif =
20509                                        Expression::Nullif(Box::new(crate::expressions::Nullif {
20510                                            this: Box::new(neq),
20511                                            expression: Box::new(Expression::Boolean(
20512                                                crate::expressions::BooleanLiteral { value: false },
20513                                            )),
20514                                        }));
20515
20516                                    // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
20517                                    let array_contains =
20518                                        Expression::Function(Box::new(Function::new(
20519                                            "ARRAY_CONTAINS".to_string(),
20520                                            vec![array, value],
20521                                        )));
20522
20523                                    // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
20524                                    return Ok(Expression::Case(Box::new(Case {
20525                                        operand: None,
20526                                        whens: vec![(value_is_null, nullif)],
20527                                        else_: Some(array_contains),
20528                                        comments: Vec::new(),
20529                                        inferred_type: None,
20530                                    })));
20531                                }
20532                                match target {
20533                                    DialectType::PostgreSQL | DialectType::Redshift => {
20534                                        // CASE WHEN needle IS NULL THEN NULL ELSE COALESCE(needle = ANY(arr), FALSE) END
20535                                        let arr = f.args[0].clone();
20536                                        let needle = f.args[1].clone();
20537                                        // Convert [] to ARRAY[] for PostgreSQL
20538                                        let pg_arr = match arr {
20539                                            Expression::Array(a) => Expression::ArrayFunc(
20540                                                Box::new(crate::expressions::ArrayConstructor {
20541                                                    expressions: a.expressions,
20542                                                    bracket_notation: false,
20543                                                    use_list_keyword: false,
20544                                                }),
20545                                            ),
20546                                            _ => arr,
20547                                        };
20548                                        // needle = ANY(arr) using the Any quantified expression
20549                                        let any_expr = Expression::Any(Box::new(
20550                                            crate::expressions::QuantifiedExpr {
20551                                                this: needle.clone(),
20552                                                subquery: pg_arr,
20553                                                op: Some(crate::expressions::QuantifiedOp::Eq),
20554                                            },
20555                                        ));
20556                                        let coalesce = Expression::Coalesce(Box::new(
20557                                            crate::expressions::VarArgFunc {
20558                                                expressions: vec![
20559                                                    any_expr,
20560                                                    Expression::Boolean(
20561                                                        crate::expressions::BooleanLiteral {
20562                                                            value: false,
20563                                                        },
20564                                                    ),
20565                                                ],
20566                                                original_name: None,
20567                                                inferred_type: None,
20568                                            },
20569                                        ));
20570                                        let is_null_check = Expression::IsNull(Box::new(
20571                                            crate::expressions::IsNull {
20572                                                this: needle,
20573                                                not: false,
20574                                                postfix_form: false,
20575                                            },
20576                                        ));
20577                                        Ok(Expression::Case(Box::new(Case {
20578                                            operand: None,
20579                                            whens: vec![(
20580                                                is_null_check,
20581                                                Expression::Null(crate::expressions::Null),
20582                                            )],
20583                                            else_: Some(coalesce),
20584                                            comments: Vec::new(),
20585                                            inferred_type: None,
20586                                        })))
20587                                    }
20588                                    _ => Ok(Expression::Function(Box::new(Function::new(
20589                                        "ARRAY_CONTAINS".to_string(),
20590                                        f.args,
20591                                    )))),
20592                                }
20593                            }
20594                            // LIST_HAS_ANY / ARRAY_HAS_ANY -> target-specific overlap operator
20595                            "LIST_HAS_ANY" | "ARRAY_HAS_ANY" if f.args.len() == 2 => {
20596                                match target {
20597                                    DialectType::PostgreSQL | DialectType::Redshift => {
20598                                        // arr1 && arr2 with ARRAY[] syntax
20599                                        let mut args = f.args;
20600                                        let arr1 = args.remove(0);
20601                                        let arr2 = args.remove(0);
20602                                        let pg_arr1 = match arr1 {
20603                                            Expression::Array(a) => Expression::ArrayFunc(
20604                                                Box::new(crate::expressions::ArrayConstructor {
20605                                                    expressions: a.expressions,
20606                                                    bracket_notation: false,
20607                                                    use_list_keyword: false,
20608                                                }),
20609                                            ),
20610                                            _ => arr1,
20611                                        };
20612                                        let pg_arr2 = match arr2 {
20613                                            Expression::Array(a) => Expression::ArrayFunc(
20614                                                Box::new(crate::expressions::ArrayConstructor {
20615                                                    expressions: a.expressions,
20616                                                    bracket_notation: false,
20617                                                    use_list_keyword: false,
20618                                                }),
20619                                            ),
20620                                            _ => arr2,
20621                                        };
20622                                        Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
20623                                            pg_arr1, pg_arr2,
20624                                        ))))
20625                                    }
20626                                    DialectType::DuckDB => {
20627                                        // DuckDB: arr1 && arr2 (native support)
20628                                        let mut args = f.args;
20629                                        let arr1 = args.remove(0);
20630                                        let arr2 = args.remove(0);
20631                                        Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
20632                                            arr1, arr2,
20633                                        ))))
20634                                    }
20635                                    _ => Ok(Expression::Function(Box::new(Function::new(
20636                                        "LIST_HAS_ANY".to_string(),
20637                                        f.args,
20638                                    )))),
20639                                }
20640                            }
20641                            // APPROX_QUANTILE(x, q) -> target-specific
20642                            "APPROX_QUANTILE" if f.args.len() == 2 => match target {
20643                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
20644                                    Function::new("APPROX_PERCENTILE".to_string(), f.args),
20645                                ))),
20646                                DialectType::DuckDB => Ok(Expression::Function(f)),
20647                                _ => Ok(Expression::Function(f)),
20648                            },
20649                            // MAKE_DATE(y, m, d) -> DATE(y, m, d) for BigQuery
20650                            "MAKE_DATE" if f.args.len() == 3 => match target {
20651                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
20652                                    Function::new("DATE".to_string(), f.args),
20653                                ))),
20654                                _ => Ok(Expression::Function(f)),
20655                            },
20656                            // RANGE(start, end[, step]) -> target-specific
20657                            "RANGE"
20658                                if f.args.len() >= 2 && !matches!(target, DialectType::DuckDB) =>
20659                            {
20660                                let start = f.args[0].clone();
20661                                let end = f.args[1].clone();
20662                                let step = f.args.get(2).cloned();
20663                                match target {
20664                                    // Snowflake ARRAY_GENERATE_RANGE uses exclusive end (same as DuckDB RANGE),
20665                                    // so just rename without adjusting the end argument.
20666                                    DialectType::Snowflake => {
20667                                        let mut args = vec![start, end];
20668                                        if let Some(s) = step {
20669                                            args.push(s);
20670                                        }
20671                                        Ok(Expression::Function(Box::new(Function::new(
20672                                            "ARRAY_GENERATE_RANGE".to_string(),
20673                                            args,
20674                                        ))))
20675                                    }
20676                                    DialectType::Spark | DialectType::Databricks => {
20677                                        // RANGE(start, end) -> SEQUENCE(start, end-1)
20678                                        // RANGE(start, end, step) -> SEQUENCE(start, end-step, step) when step constant
20679                                        // RANGE(start, start) -> ARRAY() (empty)
20680                                        // RANGE(start, end, 0) -> ARRAY() (empty)
20681                                        // When end is variable: IF((end - 1) <= start, ARRAY(), SEQUENCE(start, (end - 1)))
20682
20683                                        // Check for constant args
20684                                        fn extract_i64(e: &Expression) -> Option<i64> {
20685                                            match e {
20686                                                Expression::Literal(lit)
20687                                                    if matches!(
20688                                                        lit.as_ref(),
20689                                                        Literal::Number(_)
20690                                                    ) =>
20691                                                {
20692                                                    let Literal::Number(n) = lit.as_ref() else {
20693                                                        unreachable!()
20694                                                    };
20695                                                    n.parse::<i64>().ok()
20696                                                }
20697                                                Expression::Neg(u) => {
20698                                                    if let Expression::Literal(lit) = &u.this {
20699                                                        if let Literal::Number(n) = lit.as_ref() {
20700                                                            n.parse::<i64>().ok().map(|v| -v)
20701                                                        } else {
20702                                                            None
20703                                                        }
20704                                                    } else {
20705                                                        None
20706                                                    }
20707                                                }
20708                                                _ => None,
20709                                            }
20710                                        }
20711                                        let start_val = extract_i64(&start);
20712                                        let end_val = extract_i64(&end);
20713                                        let step_val = step.as_ref().and_then(|s| extract_i64(s));
20714
20715                                        // Check for RANGE(x, x) or RANGE(x, y, 0) -> empty array
20716                                        if step_val == Some(0) {
20717                                            return Ok(Expression::Function(Box::new(
20718                                                Function::new("ARRAY".to_string(), vec![]),
20719                                            )));
20720                                        }
20721                                        if let (Some(s), Some(e_val)) = (start_val, end_val) {
20722                                            if s == e_val {
20723                                                return Ok(Expression::Function(Box::new(
20724                                                    Function::new("ARRAY".to_string(), vec![]),
20725                                                )));
20726                                            }
20727                                        }
20728
20729                                        if let (Some(_s_val), Some(e_val)) = (start_val, end_val) {
20730                                            // All constants - compute new end = end - step (if step provided) or end - 1
20731                                            match step_val {
20732                                                Some(st) if st < 0 => {
20733                                                    // Negative step: SEQUENCE(start, end - step, step)
20734                                                    let new_end = e_val - st; // end - step (= end + |step|)
20735                                                    let mut args =
20736                                                        vec![start, Expression::number(new_end)];
20737                                                    if let Some(s) = step {
20738                                                        args.push(s);
20739                                                    }
20740                                                    Ok(Expression::Function(Box::new(
20741                                                        Function::new("SEQUENCE".to_string(), args),
20742                                                    )))
20743                                                }
20744                                                Some(st) => {
20745                                                    let new_end = e_val - st;
20746                                                    let mut args =
20747                                                        vec![start, Expression::number(new_end)];
20748                                                    if let Some(s) = step {
20749                                                        args.push(s);
20750                                                    }
20751                                                    Ok(Expression::Function(Box::new(
20752                                                        Function::new("SEQUENCE".to_string(), args),
20753                                                    )))
20754                                                }
20755                                                None => {
20756                                                    // No step: SEQUENCE(start, end - 1)
20757                                                    let new_end = e_val - 1;
20758                                                    Ok(Expression::Function(Box::new(
20759                                                        Function::new(
20760                                                            "SEQUENCE".to_string(),
20761                                                            vec![
20762                                                                start,
20763                                                                Expression::number(new_end),
20764                                                            ],
20765                                                        ),
20766                                                    )))
20767                                                }
20768                                            }
20769                                        } else {
20770                                            // Variable end: IF((end - 1) < start, ARRAY(), SEQUENCE(start, (end - 1)))
20771                                            let end_m1 = Expression::Sub(Box::new(BinaryOp::new(
20772                                                end.clone(),
20773                                                Expression::number(1),
20774                                            )));
20775                                            let cond = Expression::Lt(Box::new(BinaryOp::new(
20776                                                Expression::Paren(Box::new(Paren {
20777                                                    this: end_m1.clone(),
20778                                                    trailing_comments: Vec::new(),
20779                                                })),
20780                                                start.clone(),
20781                                            )));
20782                                            let empty = Expression::Function(Box::new(
20783                                                Function::new("ARRAY".to_string(), vec![]),
20784                                            ));
20785                                            let mut seq_args = vec![
20786                                                start,
20787                                                Expression::Paren(Box::new(Paren {
20788                                                    this: end_m1,
20789                                                    trailing_comments: Vec::new(),
20790                                                })),
20791                                            ];
20792                                            if let Some(s) = step {
20793                                                seq_args.push(s);
20794                                            }
20795                                            let seq = Expression::Function(Box::new(
20796                                                Function::new("SEQUENCE".to_string(), seq_args),
20797                                            ));
20798                                            Ok(Expression::IfFunc(Box::new(
20799                                                crate::expressions::IfFunc {
20800                                                    condition: cond,
20801                                                    true_value: empty,
20802                                                    false_value: Some(seq),
20803                                                    original_name: None,
20804                                                    inferred_type: None,
20805                                                },
20806                                            )))
20807                                        }
20808                                    }
20809                                    DialectType::SQLite => {
20810                                        // RANGE(start, end) -> GENERATE_SERIES(start, end)
20811                                        // The subquery wrapping is handled at the Alias level
20812                                        let mut args = vec![start, end];
20813                                        if let Some(s) = step {
20814                                            args.push(s);
20815                                        }
20816                                        Ok(Expression::Function(Box::new(Function::new(
20817                                            "GENERATE_SERIES".to_string(),
20818                                            args,
20819                                        ))))
20820                                    }
20821                                    _ => Ok(Expression::Function(f)),
20822                                }
20823                            }
20824                            // ARRAY_REVERSE_SORT -> target-specific
20825                            // (handled above as well, but also need DuckDB self-normalization)
20826                            // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
20827                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
20828                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
20829                                    Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
20830                                ))),
20831                                DialectType::Spark | DialectType::Databricks => {
20832                                    Ok(Expression::Function(Box::new(Function::new(
20833                                        "MAP_FROM_ARRAYS".to_string(),
20834                                        f.args,
20835                                    ))))
20836                                }
20837                                _ => Ok(Expression::Function(Box::new(Function::new(
20838                                    "MAP".to_string(),
20839                                    f.args,
20840                                )))),
20841                            },
20842                            // VARIANCE(x) -> varSamp(x) for ClickHouse
20843                            "VARIANCE" if f.args.len() == 1 => match target {
20844                                DialectType::ClickHouse => Ok(Expression::Function(Box::new(
20845                                    Function::new("varSamp".to_string(), f.args),
20846                                ))),
20847                                _ => Ok(Expression::Function(f)),
20848                            },
20849                            // STDDEV(x) -> stddevSamp(x) for ClickHouse
20850                            "STDDEV" if f.args.len() == 1 => match target {
20851                                DialectType::ClickHouse => Ok(Expression::Function(Box::new(
20852                                    Function::new("stddevSamp".to_string(), f.args),
20853                                ))),
20854                                _ => Ok(Expression::Function(f)),
20855                            },
20856                            // ISINF(x) -> IS_INF(x) for BigQuery
20857                            "ISINF" if f.args.len() == 1 => match target {
20858                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
20859                                    Function::new("IS_INF".to_string(), f.args),
20860                                ))),
20861                                _ => Ok(Expression::Function(f)),
20862                            },
20863                            // CONTAINS(arr, x) -> ARRAY_CONTAINS(arr, x) for Spark/Hive
20864                            "CONTAINS" if f.args.len() == 2 => match target {
20865                                DialectType::Spark
20866                                | DialectType::Databricks
20867                                | DialectType::Hive => Ok(Expression::Function(Box::new(
20868                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
20869                                ))),
20870                                _ => Ok(Expression::Function(f)),
20871                            },
20872                            // ARRAY_CONTAINS(arr, x) -> CONTAINS(arr, x) for Presto
20873                            "ARRAY_CONTAINS" if f.args.len() == 2 => match target {
20874                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
20875                                    Ok(Expression::Function(Box::new(Function::new(
20876                                        "CONTAINS".to_string(),
20877                                        f.args,
20878                                    ))))
20879                                }
20880                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
20881                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
20882                                ))),
20883                                _ => Ok(Expression::Function(f)),
20884                            },
20885                            // TO_UNIXTIME(x) -> UNIX_TIMESTAMP(x) for Hive/Spark
20886                            "TO_UNIXTIME" if f.args.len() == 1 => match target {
20887                                DialectType::Hive
20888                                | DialectType::Spark
20889                                | DialectType::Databricks => Ok(Expression::Function(Box::new(
20890                                    Function::new("UNIX_TIMESTAMP".to_string(), f.args),
20891                                ))),
20892                                _ => Ok(Expression::Function(f)),
20893                            },
20894                            // FROM_UNIXTIME(x) -> target-specific
20895                            "FROM_UNIXTIME" if f.args.len() == 1 => {
20896                                match target {
20897                                    DialectType::Hive
20898                                    | DialectType::Spark
20899                                    | DialectType::Databricks
20900                                    | DialectType::Presto
20901                                    | DialectType::Trino => Ok(Expression::Function(f)),
20902                                    DialectType::DuckDB => {
20903                                        // DuckDB: TO_TIMESTAMP(x)
20904                                        let arg = f.args.into_iter().next().unwrap();
20905                                        Ok(Expression::Function(Box::new(Function::new(
20906                                            "TO_TIMESTAMP".to_string(),
20907                                            vec![arg],
20908                                        ))))
20909                                    }
20910                                    DialectType::PostgreSQL => {
20911                                        // PG: TO_TIMESTAMP(col)
20912                                        let arg = f.args.into_iter().next().unwrap();
20913                                        Ok(Expression::Function(Box::new(Function::new(
20914                                            "TO_TIMESTAMP".to_string(),
20915                                            vec![arg],
20916                                        ))))
20917                                    }
20918                                    DialectType::Redshift => {
20919                                        // Redshift: (TIMESTAMP 'epoch' + col * INTERVAL '1 SECOND')
20920                                        let arg = f.args.into_iter().next().unwrap();
20921                                        let epoch_ts = Expression::Literal(Box::new(
20922                                            Literal::Timestamp("epoch".to_string()),
20923                                        ));
20924                                        let interval = Expression::Interval(Box::new(
20925                                            crate::expressions::Interval {
20926                                                this: Some(Expression::string("1 SECOND")),
20927                                                unit: None,
20928                                            },
20929                                        ));
20930                                        let mul =
20931                                            Expression::Mul(Box::new(BinaryOp::new(arg, interval)));
20932                                        let add =
20933                                            Expression::Add(Box::new(BinaryOp::new(epoch_ts, mul)));
20934                                        Ok(Expression::Paren(Box::new(crate::expressions::Paren {
20935                                            this: add,
20936                                            trailing_comments: Vec::new(),
20937                                        })))
20938                                    }
20939                                    _ => Ok(Expression::Function(f)),
20940                                }
20941                            }
20942                            // FROM_UNIXTIME(x, fmt) with 2 args from Hive/Spark -> target-specific
20943                            "FROM_UNIXTIME"
20944                                if f.args.len() == 2
20945                                    && matches!(
20946                                        source,
20947                                        DialectType::Hive
20948                                            | DialectType::Spark
20949                                            | DialectType::Databricks
20950                                    ) =>
20951                            {
20952                                let mut args = f.args;
20953                                let unix_ts = args.remove(0);
20954                                let fmt_expr = args.remove(0);
20955                                match target {
20956                                    DialectType::DuckDB => {
20957                                        // DuckDB: STRFTIME(TO_TIMESTAMP(x), c_fmt)
20958                                        let to_ts = Expression::Function(Box::new(Function::new(
20959                                            "TO_TIMESTAMP".to_string(),
20960                                            vec![unix_ts],
20961                                        )));
20962                                        if let Expression::Literal(lit) = &fmt_expr {
20963                                            if let crate::expressions::Literal::String(s) =
20964                                                lit.as_ref()
20965                                            {
20966                                                let c_fmt = Self::hive_format_to_c_format(s);
20967                                                Ok(Expression::Function(Box::new(Function::new(
20968                                                    "STRFTIME".to_string(),
20969                                                    vec![to_ts, Expression::string(&c_fmt)],
20970                                                ))))
20971                                            } else {
20972                                                Ok(Expression::Function(Box::new(Function::new(
20973                                                    "STRFTIME".to_string(),
20974                                                    vec![to_ts, fmt_expr],
20975                                                ))))
20976                                            }
20977                                        } else {
20978                                            Ok(Expression::Function(Box::new(Function::new(
20979                                                "STRFTIME".to_string(),
20980                                                vec![to_ts, fmt_expr],
20981                                            ))))
20982                                        }
20983                                    }
20984                                    DialectType::Presto
20985                                    | DialectType::Trino
20986                                    | DialectType::Athena => {
20987                                        // Presto: DATE_FORMAT(FROM_UNIXTIME(x), presto_fmt)
20988                                        let from_unix =
20989                                            Expression::Function(Box::new(Function::new(
20990                                                "FROM_UNIXTIME".to_string(),
20991                                                vec![unix_ts],
20992                                            )));
20993                                        if let Expression::Literal(lit) = &fmt_expr {
20994                                            if let crate::expressions::Literal::String(s) =
20995                                                lit.as_ref()
20996                                            {
20997                                                let p_fmt = Self::hive_format_to_presto_format(s);
20998                                                Ok(Expression::Function(Box::new(Function::new(
20999                                                    "DATE_FORMAT".to_string(),
21000                                                    vec![from_unix, Expression::string(&p_fmt)],
21001                                                ))))
21002                                            } else {
21003                                                Ok(Expression::Function(Box::new(Function::new(
21004                                                    "DATE_FORMAT".to_string(),
21005                                                    vec![from_unix, fmt_expr],
21006                                                ))))
21007                                            }
21008                                        } else {
21009                                            Ok(Expression::Function(Box::new(Function::new(
21010                                                "DATE_FORMAT".to_string(),
21011                                                vec![from_unix, fmt_expr],
21012                                            ))))
21013                                        }
21014                                    }
21015                                    _ => {
21016                                        // Keep as FROM_UNIXTIME(x, fmt) for other targets
21017                                        Ok(Expression::Function(Box::new(Function::new(
21018                                            "FROM_UNIXTIME".to_string(),
21019                                            vec![unix_ts, fmt_expr],
21020                                        ))))
21021                                    }
21022                                }
21023                            }
21024                            // DATEPART(unit, expr) -> EXTRACT(unit FROM expr) for Spark
21025                            "DATEPART" | "DATE_PART" if f.args.len() == 2 => {
21026                                let unit_str = Self::get_unit_str_static(&f.args[0]);
21027                                // Get the raw unit text preserving original case
21028                                let raw_unit = match &f.args[0] {
21029                                    Expression::Identifier(id) => id.name.clone(),
21030                                    Expression::Var(v) => v.this.clone(),
21031                                    Expression::Literal(lit)
21032                                        if matches!(
21033                                            lit.as_ref(),
21034                                            crate::expressions::Literal::String(_)
21035                                        ) =>
21036                                    {
21037                                        let crate::expressions::Literal::String(s) = lit.as_ref()
21038                                        else {
21039                                            unreachable!()
21040                                        };
21041                                        s.clone()
21042                                    }
21043                                    Expression::Column(col) => col.name.name.clone(),
21044                                    _ => unit_str.clone(),
21045                                };
21046                                match target {
21047                                    DialectType::TSQL | DialectType::Fabric => {
21048                                        // Preserve original case of unit for TSQL
21049                                        let unit_name = match unit_str.as_str() {
21050                                            "YY" | "YYYY" => "YEAR".to_string(),
21051                                            "QQ" | "Q" => "QUARTER".to_string(),
21052                                            "MM" | "M" => "MONTH".to_string(),
21053                                            "WK" | "WW" => "WEEK".to_string(),
21054                                            "DD" | "D" | "DY" => "DAY".to_string(),
21055                                            "HH" => "HOUR".to_string(),
21056                                            "MI" | "N" => "MINUTE".to_string(),
21057                                            "SS" | "S" => "SECOND".to_string(),
21058                                            _ => raw_unit.clone(), // preserve original case
21059                                        };
21060                                        let mut args = f.args;
21061                                        args[0] =
21062                                            Expression::Identifier(Identifier::new(&unit_name));
21063                                        Ok(Expression::Function(Box::new(Function::new(
21064                                            "DATEPART".to_string(),
21065                                            args,
21066                                        ))))
21067                                    }
21068                                    DialectType::Spark | DialectType::Databricks => {
21069                                        // DATEPART(unit, expr) -> EXTRACT(unit FROM expr)
21070                                        // Preserve original case for non-abbreviation units
21071                                        let unit = match unit_str.as_str() {
21072                                            "YY" | "YYYY" => "YEAR".to_string(),
21073                                            "QQ" | "Q" => "QUARTER".to_string(),
21074                                            "MM" | "M" => "MONTH".to_string(),
21075                                            "WK" | "WW" => "WEEK".to_string(),
21076                                            "DD" | "D" | "DY" => "DAY".to_string(),
21077                                            "HH" => "HOUR".to_string(),
21078                                            "MI" | "N" => "MINUTE".to_string(),
21079                                            "SS" | "S" => "SECOND".to_string(),
21080                                            _ => raw_unit, // preserve original case
21081                                        };
21082                                        Ok(Expression::Extract(Box::new(
21083                                            crate::expressions::ExtractFunc {
21084                                                this: f.args[1].clone(),
21085                                                field: crate::expressions::DateTimeField::Custom(
21086                                                    unit,
21087                                                ),
21088                                            },
21089                                        )))
21090                                    }
21091                                    _ => Ok(Expression::Function(Box::new(Function::new(
21092                                        "DATE_PART".to_string(),
21093                                        f.args,
21094                                    )))),
21095                                }
21096                            }
21097                            // DATENAME(mm, date) -> FORMAT(CAST(date AS DATETIME2), 'MMMM') for TSQL
21098                            // DATENAME(dw, date) -> FORMAT(CAST(date AS DATETIME2), 'dddd') for TSQL
21099                            // DATENAME(mm, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'MMMM') for Spark
21100                            // DATENAME(dw, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'EEEE') for Spark
21101                            "DATENAME" if f.args.len() == 2 => {
21102                                let unit_str = Self::get_unit_str_static(&f.args[0]);
21103                                let date_expr = f.args[1].clone();
21104                                match unit_str.as_str() {
21105                                    "MM" | "M" | "MONTH" => match target {
21106                                        DialectType::TSQL => {
21107                                            let cast_date = Expression::Cast(Box::new(
21108                                                crate::expressions::Cast {
21109                                                    this: date_expr,
21110                                                    to: DataType::Custom {
21111                                                        name: "DATETIME2".to_string(),
21112                                                    },
21113                                                    trailing_comments: Vec::new(),
21114                                                    double_colon_syntax: false,
21115                                                    format: None,
21116                                                    default: None,
21117                                                    inferred_type: None,
21118                                                },
21119                                            ));
21120                                            Ok(Expression::Function(Box::new(Function::new(
21121                                                "FORMAT".to_string(),
21122                                                vec![cast_date, Expression::string("MMMM")],
21123                                            ))))
21124                                        }
21125                                        DialectType::Spark | DialectType::Databricks => {
21126                                            let cast_date = Expression::Cast(Box::new(
21127                                                crate::expressions::Cast {
21128                                                    this: date_expr,
21129                                                    to: DataType::Timestamp {
21130                                                        timezone: false,
21131                                                        precision: None,
21132                                                    },
21133                                                    trailing_comments: Vec::new(),
21134                                                    double_colon_syntax: false,
21135                                                    format: None,
21136                                                    default: None,
21137                                                    inferred_type: None,
21138                                                },
21139                                            ));
21140                                            Ok(Expression::Function(Box::new(Function::new(
21141                                                "DATE_FORMAT".to_string(),
21142                                                vec![cast_date, Expression::string("MMMM")],
21143                                            ))))
21144                                        }
21145                                        _ => Ok(Expression::Function(f)),
21146                                    },
21147                                    "DW" | "WEEKDAY" => match target {
21148                                        DialectType::TSQL => {
21149                                            let cast_date = Expression::Cast(Box::new(
21150                                                crate::expressions::Cast {
21151                                                    this: date_expr,
21152                                                    to: DataType::Custom {
21153                                                        name: "DATETIME2".to_string(),
21154                                                    },
21155                                                    trailing_comments: Vec::new(),
21156                                                    double_colon_syntax: false,
21157                                                    format: None,
21158                                                    default: None,
21159                                                    inferred_type: None,
21160                                                },
21161                                            ));
21162                                            Ok(Expression::Function(Box::new(Function::new(
21163                                                "FORMAT".to_string(),
21164                                                vec![cast_date, Expression::string("dddd")],
21165                                            ))))
21166                                        }
21167                                        DialectType::Spark | DialectType::Databricks => {
21168                                            let cast_date = Expression::Cast(Box::new(
21169                                                crate::expressions::Cast {
21170                                                    this: date_expr,
21171                                                    to: DataType::Timestamp {
21172                                                        timezone: false,
21173                                                        precision: None,
21174                                                    },
21175                                                    trailing_comments: Vec::new(),
21176                                                    double_colon_syntax: false,
21177                                                    format: None,
21178                                                    default: None,
21179                                                    inferred_type: None,
21180                                                },
21181                                            ));
21182                                            Ok(Expression::Function(Box::new(Function::new(
21183                                                "DATE_FORMAT".to_string(),
21184                                                vec![cast_date, Expression::string("EEEE")],
21185                                            ))))
21186                                        }
21187                                        _ => Ok(Expression::Function(f)),
21188                                    },
21189                                    _ => Ok(Expression::Function(f)),
21190                                }
21191                            }
21192                            // STRING_AGG(x, sep) without WITHIN GROUP -> target-specific
21193                            "STRING_AGG" if f.args.len() >= 2 => {
21194                                let x = f.args[0].clone();
21195                                let sep = f.args[1].clone();
21196                                match target {
21197                                    DialectType::MySQL
21198                                    | DialectType::SingleStore
21199                                    | DialectType::Doris
21200                                    | DialectType::StarRocks => Ok(Expression::GroupConcat(
21201                                        Box::new(crate::expressions::GroupConcatFunc {
21202                                            this: x,
21203                                            separator: Some(sep),
21204                                            order_by: None,
21205                                            distinct: false,
21206                                            filter: None,
21207                                            limit: None,
21208                                            inferred_type: None,
21209                                        }),
21210                                    )),
21211                                    DialectType::SQLite => Ok(Expression::GroupConcat(Box::new(
21212                                        crate::expressions::GroupConcatFunc {
21213                                            this: x,
21214                                            separator: Some(sep),
21215                                            order_by: None,
21216                                            distinct: false,
21217                                            filter: None,
21218                                            limit: None,
21219                                            inferred_type: None,
21220                                        },
21221                                    ))),
21222                                    DialectType::PostgreSQL | DialectType::Redshift => {
21223                                        Ok(Expression::StringAgg(Box::new(
21224                                            crate::expressions::StringAggFunc {
21225                                                this: x,
21226                                                separator: Some(sep),
21227                                                order_by: None,
21228                                                distinct: false,
21229                                                filter: None,
21230                                                limit: None,
21231                                                inferred_type: None,
21232                                            },
21233                                        )))
21234                                    }
21235                                    _ => Ok(Expression::Function(f)),
21236                                }
21237                            }
21238                            "TRY_DIVIDE" if f.args.len() == 2 => {
21239                                let mut args = f.args;
21240                                let x = args.remove(0);
21241                                let y = args.remove(0);
21242                                match target {
21243                                    DialectType::Spark | DialectType::Databricks => {
21244                                        Ok(Expression::Function(Box::new(Function::new(
21245                                            "TRY_DIVIDE".to_string(),
21246                                            vec![x, y],
21247                                        ))))
21248                                    }
21249                                    DialectType::Snowflake => {
21250                                        let y_ref = match &y {
21251                                            Expression::Column(_)
21252                                            | Expression::Literal(_)
21253                                            | Expression::Identifier(_) => y.clone(),
21254                                            _ => Expression::Paren(Box::new(Paren {
21255                                                this: y.clone(),
21256                                                trailing_comments: vec![],
21257                                            })),
21258                                        };
21259                                        let x_ref = match &x {
21260                                            Expression::Column(_)
21261                                            | Expression::Literal(_)
21262                                            | Expression::Identifier(_) => x.clone(),
21263                                            _ => Expression::Paren(Box::new(Paren {
21264                                                this: x.clone(),
21265                                                trailing_comments: vec![],
21266                                            })),
21267                                        };
21268                                        let condition = Expression::Neq(Box::new(
21269                                            crate::expressions::BinaryOp::new(
21270                                                y_ref.clone(),
21271                                                Expression::number(0),
21272                                            ),
21273                                        ));
21274                                        let div_expr = Expression::Div(Box::new(
21275                                            crate::expressions::BinaryOp::new(x_ref, y_ref),
21276                                        ));
21277                                        Ok(Expression::IfFunc(Box::new(
21278                                            crate::expressions::IfFunc {
21279                                                condition,
21280                                                true_value: div_expr,
21281                                                false_value: Some(Expression::Null(Null)),
21282                                                original_name: Some("IFF".to_string()),
21283                                                inferred_type: None,
21284                                            },
21285                                        )))
21286                                    }
21287                                    DialectType::DuckDB => {
21288                                        let y_ref = match &y {
21289                                            Expression::Column(_)
21290                                            | Expression::Literal(_)
21291                                            | Expression::Identifier(_) => y.clone(),
21292                                            _ => Expression::Paren(Box::new(Paren {
21293                                                this: y.clone(),
21294                                                trailing_comments: vec![],
21295                                            })),
21296                                        };
21297                                        let x_ref = match &x {
21298                                            Expression::Column(_)
21299                                            | Expression::Literal(_)
21300                                            | Expression::Identifier(_) => x.clone(),
21301                                            _ => Expression::Paren(Box::new(Paren {
21302                                                this: x.clone(),
21303                                                trailing_comments: vec![],
21304                                            })),
21305                                        };
21306                                        let condition = Expression::Neq(Box::new(
21307                                            crate::expressions::BinaryOp::new(
21308                                                y_ref.clone(),
21309                                                Expression::number(0),
21310                                            ),
21311                                        ));
21312                                        let div_expr = Expression::Div(Box::new(
21313                                            crate::expressions::BinaryOp::new(x_ref, y_ref),
21314                                        ));
21315                                        Ok(Expression::Case(Box::new(Case {
21316                                            operand: None,
21317                                            whens: vec![(condition, div_expr)],
21318                                            else_: Some(Expression::Null(Null)),
21319                                            comments: Vec::new(),
21320                                            inferred_type: None,
21321                                        })))
21322                                    }
21323                                    _ => Ok(Expression::Function(Box::new(Function::new(
21324                                        "TRY_DIVIDE".to_string(),
21325                                        vec![x, y],
21326                                    )))),
21327                                }
21328                            }
21329                            // JSON_ARRAYAGG -> JSON_AGG for PostgreSQL
21330                            "JSON_ARRAYAGG" => match target {
21331                                DialectType::PostgreSQL => {
21332                                    Ok(Expression::Function(Box::new(Function {
21333                                        name: "JSON_AGG".to_string(),
21334                                        ..(*f)
21335                                    })))
21336                                }
21337                                _ => Ok(Expression::Function(f)),
21338                            },
21339                            // SCHEMA_NAME(id) -> CURRENT_SCHEMA for PostgreSQL, 'main' for SQLite
21340                            "SCHEMA_NAME" => match target {
21341                                DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
21342                                    crate::expressions::CurrentSchema { this: None },
21343                                ))),
21344                                DialectType::SQLite => Ok(Expression::string("main")),
21345                                _ => Ok(Expression::Function(f)),
21346                            },
21347                            // TO_TIMESTAMP(x, fmt) 2-arg from Spark/Hive: convert Java format to target format
21348                            "TO_TIMESTAMP"
21349                                if f.args.len() == 2
21350                                    && matches!(
21351                                        source,
21352                                        DialectType::Spark
21353                                            | DialectType::Databricks
21354                                            | DialectType::Hive
21355                                    )
21356                                    && matches!(target, DialectType::DuckDB) =>
21357                            {
21358                                let mut args = f.args;
21359                                let val = args.remove(0);
21360                                let fmt_expr = args.remove(0);
21361                                if let Expression::Literal(ref lit) = fmt_expr {
21362                                    if let Literal::String(ref s) = lit.as_ref() {
21363                                        // Convert Java/Spark format to C strptime format
21364                                        fn java_to_c_fmt(fmt: &str) -> String {
21365                                            let result = fmt
21366                                                .replace("yyyy", "%Y")
21367                                                .replace("SSSSSS", "%f")
21368                                                .replace("EEEE", "%W")
21369                                                .replace("MM", "%m")
21370                                                .replace("dd", "%d")
21371                                                .replace("HH", "%H")
21372                                                .replace("mm", "%M")
21373                                                .replace("ss", "%S")
21374                                                .replace("yy", "%y");
21375                                            let mut out = String::new();
21376                                            let chars: Vec<char> = result.chars().collect();
21377                                            let mut i = 0;
21378                                            while i < chars.len() {
21379                                                if chars[i] == '%' && i + 1 < chars.len() {
21380                                                    out.push(chars[i]);
21381                                                    out.push(chars[i + 1]);
21382                                                    i += 2;
21383                                                } else if chars[i] == 'z' {
21384                                                    out.push_str("%Z");
21385                                                    i += 1;
21386                                                } else if chars[i] == 'Z' {
21387                                                    out.push_str("%z");
21388                                                    i += 1;
21389                                                } else {
21390                                                    out.push(chars[i]);
21391                                                    i += 1;
21392                                                }
21393                                            }
21394                                            out
21395                                        }
21396                                        let c_fmt = java_to_c_fmt(s);
21397                                        Ok(Expression::Function(Box::new(Function::new(
21398                                            "STRPTIME".to_string(),
21399                                            vec![val, Expression::string(&c_fmt)],
21400                                        ))))
21401                                    } else {
21402                                        Ok(Expression::Function(Box::new(Function::new(
21403                                            "STRPTIME".to_string(),
21404                                            vec![val, fmt_expr],
21405                                        ))))
21406                                    }
21407                                } else {
21408                                    Ok(Expression::Function(Box::new(Function::new(
21409                                        "STRPTIME".to_string(),
21410                                        vec![val, fmt_expr],
21411                                    ))))
21412                                }
21413                            }
21414                            // TO_DATE(x) 1-arg from Doris: date conversion
21415                            "TO_DATE"
21416                                if f.args.len() == 1
21417                                    && matches!(
21418                                        source,
21419                                        DialectType::Doris | DialectType::StarRocks
21420                                    ) =>
21421                            {
21422                                let arg = f.args.into_iter().next().unwrap();
21423                                match target {
21424                                    DialectType::Oracle
21425                                    | DialectType::DuckDB
21426                                    | DialectType::TSQL => {
21427                                        // CAST(x AS DATE)
21428                                        Ok(Expression::Cast(Box::new(Cast {
21429                                            this: arg,
21430                                            to: DataType::Date,
21431                                            double_colon_syntax: false,
21432                                            trailing_comments: vec![],
21433                                            format: None,
21434                                            default: None,
21435                                            inferred_type: None,
21436                                        })))
21437                                    }
21438                                    DialectType::MySQL | DialectType::SingleStore => {
21439                                        // DATE(x)
21440                                        Ok(Expression::Function(Box::new(Function::new(
21441                                            "DATE".to_string(),
21442                                            vec![arg],
21443                                        ))))
21444                                    }
21445                                    _ => {
21446                                        // Default: keep as TO_DATE(x) (Spark, PostgreSQL, etc.)
21447                                        Ok(Expression::Function(Box::new(Function::new(
21448                                            "TO_DATE".to_string(),
21449                                            vec![arg],
21450                                        ))))
21451                                    }
21452                                }
21453                            }
21454                            // TO_DATE(x) 1-arg from Spark/Hive: safe date conversion
21455                            "TO_DATE"
21456                                if f.args.len() == 1
21457                                    && matches!(
21458                                        source,
21459                                        DialectType::Spark
21460                                            | DialectType::Databricks
21461                                            | DialectType::Hive
21462                                    ) =>
21463                            {
21464                                let arg = f.args.into_iter().next().unwrap();
21465                                match target {
21466                                    DialectType::DuckDB => {
21467                                        // Spark TO_DATE is safe -> TRY_CAST(x AS DATE)
21468                                        Ok(Expression::TryCast(Box::new(Cast {
21469                                            this: arg,
21470                                            to: DataType::Date,
21471                                            double_colon_syntax: false,
21472                                            trailing_comments: vec![],
21473                                            format: None,
21474                                            default: None,
21475                                            inferred_type: None,
21476                                        })))
21477                                    }
21478                                    DialectType::Presto
21479                                    | DialectType::Trino
21480                                    | DialectType::Athena => {
21481                                        // CAST(CAST(x AS TIMESTAMP) AS DATE)
21482                                        Ok(Self::double_cast_timestamp_date(arg))
21483                                    }
21484                                    DialectType::Snowflake => {
21485                                        // Spark's TO_DATE is safe -> TRY_TO_DATE(x, 'yyyy-mm-DD')
21486                                        // The default Spark format 'yyyy-MM-dd' maps to Snowflake 'yyyy-mm-DD'
21487                                        Ok(Expression::Function(Box::new(Function::new(
21488                                            "TRY_TO_DATE".to_string(),
21489                                            vec![arg, Expression::string("yyyy-mm-DD")],
21490                                        ))))
21491                                    }
21492                                    _ => {
21493                                        // Default: keep as TO_DATE(x)
21494                                        Ok(Expression::Function(Box::new(Function::new(
21495                                            "TO_DATE".to_string(),
21496                                            vec![arg],
21497                                        ))))
21498                                    }
21499                                }
21500                            }
21501                            // TO_DATE(x, fmt) 2-arg from Spark/Hive: format-based date conversion
21502                            "TO_DATE"
21503                                if f.args.len() == 2
21504                                    && matches!(
21505                                        source,
21506                                        DialectType::Spark
21507                                            | DialectType::Databricks
21508                                            | DialectType::Hive
21509                                    ) =>
21510                            {
21511                                let mut args = f.args;
21512                                let val = args.remove(0);
21513                                let fmt_expr = args.remove(0);
21514                                let is_default_format = matches!(&fmt_expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "yyyy-MM-dd"));
21515
21516                                if is_default_format {
21517                                    // Default format: same as 1-arg form
21518                                    match target {
21519                                        DialectType::DuckDB => {
21520                                            Ok(Expression::TryCast(Box::new(Cast {
21521                                                this: val,
21522                                                to: DataType::Date,
21523                                                double_colon_syntax: false,
21524                                                trailing_comments: vec![],
21525                                                format: None,
21526                                                default: None,
21527                                                inferred_type: None,
21528                                            })))
21529                                        }
21530                                        DialectType::Presto
21531                                        | DialectType::Trino
21532                                        | DialectType::Athena => {
21533                                            Ok(Self::double_cast_timestamp_date(val))
21534                                        }
21535                                        DialectType::Snowflake => {
21536                                            // TRY_TO_DATE(x, format) with Snowflake format mapping
21537                                            let sf_fmt = "yyyy-MM-dd"
21538                                                .replace("yyyy", "yyyy")
21539                                                .replace("MM", "mm")
21540                                                .replace("dd", "DD");
21541                                            Ok(Expression::Function(Box::new(Function::new(
21542                                                "TRY_TO_DATE".to_string(),
21543                                                vec![val, Expression::string(&sf_fmt)],
21544                                            ))))
21545                                        }
21546                                        _ => Ok(Expression::Function(Box::new(Function::new(
21547                                            "TO_DATE".to_string(),
21548                                            vec![val],
21549                                        )))),
21550                                    }
21551                                } else {
21552                                    // Non-default format: use format-based parsing
21553                                    if let Expression::Literal(ref lit) = fmt_expr {
21554                                        if let Literal::String(ref s) = lit.as_ref() {
21555                                            match target {
21556                                                DialectType::DuckDB => {
21557                                                    // CAST(CAST(TRY_STRPTIME(x, c_fmt) AS TIMESTAMP) AS DATE)
21558                                                    fn java_to_c_fmt_todate(fmt: &str) -> String {
21559                                                        let result = fmt
21560                                                            .replace("yyyy", "%Y")
21561                                                            .replace("SSSSSS", "%f")
21562                                                            .replace("EEEE", "%W")
21563                                                            .replace("MM", "%m")
21564                                                            .replace("dd", "%d")
21565                                                            .replace("HH", "%H")
21566                                                            .replace("mm", "%M")
21567                                                            .replace("ss", "%S")
21568                                                            .replace("yy", "%y");
21569                                                        let mut out = String::new();
21570                                                        let chars: Vec<char> =
21571                                                            result.chars().collect();
21572                                                        let mut i = 0;
21573                                                        while i < chars.len() {
21574                                                            if chars[i] == '%'
21575                                                                && i + 1 < chars.len()
21576                                                            {
21577                                                                out.push(chars[i]);
21578                                                                out.push(chars[i + 1]);
21579                                                                i += 2;
21580                                                            } else if chars[i] == 'z' {
21581                                                                out.push_str("%Z");
21582                                                                i += 1;
21583                                                            } else if chars[i] == 'Z' {
21584                                                                out.push_str("%z");
21585                                                                i += 1;
21586                                                            } else {
21587                                                                out.push(chars[i]);
21588                                                                i += 1;
21589                                                            }
21590                                                        }
21591                                                        out
21592                                                    }
21593                                                    let c_fmt = java_to_c_fmt_todate(s);
21594                                                    // CAST(CAST(TRY_STRPTIME(x, fmt) AS TIMESTAMP) AS DATE)
21595                                                    let try_strptime = Expression::Function(
21596                                                        Box::new(Function::new(
21597                                                            "TRY_STRPTIME".to_string(),
21598                                                            vec![val, Expression::string(&c_fmt)],
21599                                                        )),
21600                                                    );
21601                                                    let cast_ts =
21602                                                        Expression::Cast(Box::new(Cast {
21603                                                            this: try_strptime,
21604                                                            to: DataType::Timestamp {
21605                                                                precision: None,
21606                                                                timezone: false,
21607                                                            },
21608                                                            double_colon_syntax: false,
21609                                                            trailing_comments: vec![],
21610                                                            format: None,
21611                                                            default: None,
21612                                                            inferred_type: None,
21613                                                        }));
21614                                                    Ok(Expression::Cast(Box::new(Cast {
21615                                                        this: cast_ts,
21616                                                        to: DataType::Date,
21617                                                        double_colon_syntax: false,
21618                                                        trailing_comments: vec![],
21619                                                        format: None,
21620                                                        default: None,
21621                                                        inferred_type: None,
21622                                                    })))
21623                                                }
21624                                                DialectType::Presto
21625                                                | DialectType::Trino
21626                                                | DialectType::Athena => {
21627                                                    // CAST(DATE_PARSE(x, presto_fmt) AS DATE)
21628                                                    let p_fmt = s
21629                                                        .replace("yyyy", "%Y")
21630                                                        .replace("SSSSSS", "%f")
21631                                                        .replace("MM", "%m")
21632                                                        .replace("dd", "%d")
21633                                                        .replace("HH", "%H")
21634                                                        .replace("mm", "%M")
21635                                                        .replace("ss", "%S")
21636                                                        .replace("yy", "%y");
21637                                                    let date_parse = Expression::Function(
21638                                                        Box::new(Function::new(
21639                                                            "DATE_PARSE".to_string(),
21640                                                            vec![val, Expression::string(&p_fmt)],
21641                                                        )),
21642                                                    );
21643                                                    Ok(Expression::Cast(Box::new(Cast {
21644                                                        this: date_parse,
21645                                                        to: DataType::Date,
21646                                                        double_colon_syntax: false,
21647                                                        trailing_comments: vec![],
21648                                                        format: None,
21649                                                        default: None,
21650                                                        inferred_type: None,
21651                                                    })))
21652                                                }
21653                                                DialectType::Snowflake => {
21654                                                    // TRY_TO_DATE(x, snowflake_fmt)
21655                                                    Ok(Expression::Function(Box::new(
21656                                                        Function::new(
21657                                                            "TRY_TO_DATE".to_string(),
21658                                                            vec![val, Expression::string(s)],
21659                                                        ),
21660                                                    )))
21661                                                }
21662                                                _ => Ok(Expression::Function(Box::new(
21663                                                    Function::new(
21664                                                        "TO_DATE".to_string(),
21665                                                        vec![val, fmt_expr],
21666                                                    ),
21667                                                ))),
21668                                            }
21669                                        } else {
21670                                            Ok(Expression::Function(Box::new(Function::new(
21671                                                "TO_DATE".to_string(),
21672                                                vec![val, fmt_expr],
21673                                            ))))
21674                                        }
21675                                    } else {
21676                                        Ok(Expression::Function(Box::new(Function::new(
21677                                            "TO_DATE".to_string(),
21678                                            vec![val, fmt_expr],
21679                                        ))))
21680                                    }
21681                                }
21682                            }
21683                            // TO_TIMESTAMP(x) 1-arg: epoch conversion
21684                            "TO_TIMESTAMP"
21685                                if f.args.len() == 1
21686                                    && matches!(source, DialectType::DuckDB)
21687                                    && matches!(
21688                                        target,
21689                                        DialectType::BigQuery
21690                                            | DialectType::Presto
21691                                            | DialectType::Trino
21692                                            | DialectType::Hive
21693                                            | DialectType::Spark
21694                                            | DialectType::Databricks
21695                                            | DialectType::Athena
21696                                    ) =>
21697                            {
21698                                let arg = f.args.into_iter().next().unwrap();
21699                                let func_name = match target {
21700                                    DialectType::BigQuery => "TIMESTAMP_SECONDS",
21701                                    DialectType::Presto
21702                                    | DialectType::Trino
21703                                    | DialectType::Athena
21704                                    | DialectType::Hive
21705                                    | DialectType::Spark
21706                                    | DialectType::Databricks => "FROM_UNIXTIME",
21707                                    _ => "TO_TIMESTAMP",
21708                                };
21709                                Ok(Expression::Function(Box::new(Function::new(
21710                                    func_name.to_string(),
21711                                    vec![arg],
21712                                ))))
21713                            }
21714                            // CONCAT(x) single-arg: -> CONCAT(COALESCE(x, '')) for Spark
21715                            "CONCAT" if f.args.len() == 1 => {
21716                                let arg = f.args.into_iter().next().unwrap();
21717                                match target {
21718                                    DialectType::Presto
21719                                    | DialectType::Trino
21720                                    | DialectType::Athena => {
21721                                        // CONCAT(a) -> CAST(a AS VARCHAR)
21722                                        Ok(Expression::Cast(Box::new(Cast {
21723                                            this: arg,
21724                                            to: DataType::VarChar {
21725                                                length: None,
21726                                                parenthesized_length: false,
21727                                            },
21728                                            trailing_comments: vec![],
21729                                            double_colon_syntax: false,
21730                                            format: None,
21731                                            default: None,
21732                                            inferred_type: None,
21733                                        })))
21734                                    }
21735                                    DialectType::TSQL => {
21736                                        // CONCAT(a) -> a
21737                                        Ok(arg)
21738                                    }
21739                                    DialectType::DuckDB => {
21740                                        // Keep CONCAT(a) for DuckDB (native support)
21741                                        Ok(Expression::Function(Box::new(Function::new(
21742                                            "CONCAT".to_string(),
21743                                            vec![arg],
21744                                        ))))
21745                                    }
21746                                    DialectType::Spark | DialectType::Databricks => {
21747                                        let coalesced = Expression::Coalesce(Box::new(
21748                                            crate::expressions::VarArgFunc {
21749                                                expressions: vec![arg, Expression::string("")],
21750                                                original_name: None,
21751                                                inferred_type: None,
21752                                            },
21753                                        ));
21754                                        Ok(Expression::Function(Box::new(Function::new(
21755                                            "CONCAT".to_string(),
21756                                            vec![coalesced],
21757                                        ))))
21758                                    }
21759                                    _ => Ok(Expression::Function(Box::new(Function::new(
21760                                        "CONCAT".to_string(),
21761                                        vec![arg],
21762                                    )))),
21763                                }
21764                            }
21765                            // REGEXP_EXTRACT(a, p) 2-arg: BigQuery default group is 0 (no 3rd arg needed)
21766                            "REGEXP_EXTRACT"
21767                                if f.args.len() == 3 && matches!(target, DialectType::BigQuery) =>
21768                            {
21769                                // If group_index is 0, drop it
21770                                let drop_group = match &f.args[2] {
21771                                    Expression::Literal(lit)
21772                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
21773                                    {
21774                                        let Literal::Number(n) = lit.as_ref() else {
21775                                            unreachable!()
21776                                        };
21777                                        n == "0"
21778                                    }
21779                                    _ => false,
21780                                };
21781                                if drop_group {
21782                                    let mut args = f.args;
21783                                    args.truncate(2);
21784                                    Ok(Expression::Function(Box::new(Function::new(
21785                                        "REGEXP_EXTRACT".to_string(),
21786                                        args,
21787                                    ))))
21788                                } else {
21789                                    Ok(Expression::Function(f))
21790                                }
21791                            }
21792                            // REGEXP_EXTRACT(a, pattern, group, flags) 4-arg -> REGEXP_SUBSTR for Snowflake
21793                            "REGEXP_EXTRACT"
21794                                if f.args.len() == 4
21795                                    && matches!(target, DialectType::Snowflake) =>
21796                            {
21797                                // REGEXP_EXTRACT(a, 'pattern', 2, 'i') -> REGEXP_SUBSTR(a, 'pattern', 1, 1, 'i', 2)
21798                                let mut args = f.args;
21799                                let this = args.remove(0);
21800                                let pattern = args.remove(0);
21801                                let group = args.remove(0);
21802                                let flags = args.remove(0);
21803                                Ok(Expression::Function(Box::new(Function::new(
21804                                    "REGEXP_SUBSTR".to_string(),
21805                                    vec![
21806                                        this,
21807                                        pattern,
21808                                        Expression::number(1),
21809                                        Expression::number(1),
21810                                        flags,
21811                                        group,
21812                                    ],
21813                                ))))
21814                            }
21815                            // REGEXP_SUBSTR(a, pattern, position) 3-arg -> REGEXP_EXTRACT(SUBSTRING(a, pos), pattern)
21816                            "REGEXP_SUBSTR"
21817                                if f.args.len() == 3
21818                                    && matches!(
21819                                        target,
21820                                        DialectType::DuckDB
21821                                            | DialectType::Presto
21822                                            | DialectType::Trino
21823                                            | DialectType::Spark
21824                                            | DialectType::Databricks
21825                                    ) =>
21826                            {
21827                                let mut args = f.args;
21828                                let this = args.remove(0);
21829                                let pattern = args.remove(0);
21830                                let position = args.remove(0);
21831                                // Wrap subject in SUBSTRING(this, position) to apply the offset
21832                                let substring_expr = Expression::Function(Box::new(Function::new(
21833                                    "SUBSTRING".to_string(),
21834                                    vec![this, position],
21835                                )));
21836                                let target_name = match target {
21837                                    DialectType::DuckDB => "REGEXP_EXTRACT",
21838                                    _ => "REGEXP_EXTRACT",
21839                                };
21840                                Ok(Expression::Function(Box::new(Function::new(
21841                                    target_name.to_string(),
21842                                    vec![substring_expr, pattern],
21843                                ))))
21844                            }
21845                            // TO_DAYS(x) -> (DATEDIFF(x, '0000-01-01') + 1) or target-specific
21846                            "TO_DAYS" if f.args.len() == 1 => {
21847                                let x = f.args.into_iter().next().unwrap();
21848                                let epoch = Expression::string("0000-01-01");
21849                                // Build the final target-specific expression directly
21850                                let datediff_expr = match target {
21851                                    DialectType::MySQL | DialectType::SingleStore => {
21852                                        // MySQL: (DATEDIFF(x, '0000-01-01') + 1)
21853                                        Expression::Function(Box::new(Function::new(
21854                                            "DATEDIFF".to_string(),
21855                                            vec![x, epoch],
21856                                        )))
21857                                    }
21858                                    DialectType::DuckDB => {
21859                                        // DuckDB: (DATE_DIFF('DAY', CAST('0000-01-01' AS DATE), CAST(x AS DATE)) + 1)
21860                                        let cast_epoch = Expression::Cast(Box::new(Cast {
21861                                            this: epoch,
21862                                            to: DataType::Date,
21863                                            trailing_comments: Vec::new(),
21864                                            double_colon_syntax: false,
21865                                            format: None,
21866                                            default: None,
21867                                            inferred_type: None,
21868                                        }));
21869                                        let cast_x = Expression::Cast(Box::new(Cast {
21870                                            this: x,
21871                                            to: DataType::Date,
21872                                            trailing_comments: Vec::new(),
21873                                            double_colon_syntax: false,
21874                                            format: None,
21875                                            default: None,
21876                                            inferred_type: None,
21877                                        }));
21878                                        Expression::Function(Box::new(Function::new(
21879                                            "DATE_DIFF".to_string(),
21880                                            vec![Expression::string("DAY"), cast_epoch, cast_x],
21881                                        )))
21882                                    }
21883                                    DialectType::Presto
21884                                    | DialectType::Trino
21885                                    | DialectType::Athena => {
21886                                        // Presto: (DATE_DIFF('DAY', CAST(CAST('0000-01-01' AS TIMESTAMP) AS DATE), CAST(CAST(x AS TIMESTAMP) AS DATE)) + 1)
21887                                        let cast_epoch = Self::double_cast_timestamp_date(epoch);
21888                                        let cast_x = Self::double_cast_timestamp_date(x);
21889                                        Expression::Function(Box::new(Function::new(
21890                                            "DATE_DIFF".to_string(),
21891                                            vec![Expression::string("DAY"), cast_epoch, cast_x],
21892                                        )))
21893                                    }
21894                                    _ => {
21895                                        // Default: (DATEDIFF(x, '0000-01-01') + 1)
21896                                        Expression::Function(Box::new(Function::new(
21897                                            "DATEDIFF".to_string(),
21898                                            vec![x, epoch],
21899                                        )))
21900                                    }
21901                                };
21902                                let add_one = Expression::Add(Box::new(BinaryOp::new(
21903                                    datediff_expr,
21904                                    Expression::number(1),
21905                                )));
21906                                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
21907                                    this: add_one,
21908                                    trailing_comments: Vec::new(),
21909                                })))
21910                            }
21911                            // STR_TO_DATE(x, format) -> DATE_PARSE / STRPTIME / TO_DATE etc.
21912                            "STR_TO_DATE"
21913                                if f.args.len() == 2
21914                                    && matches!(
21915                                        target,
21916                                        DialectType::Presto | DialectType::Trino
21917                                    ) =>
21918                            {
21919                                let mut args = f.args;
21920                                let x = args.remove(0);
21921                                let format_expr = args.remove(0);
21922                                // Check if the format contains time components
21923                                let has_time = if let Expression::Literal(ref lit) = format_expr {
21924                                    if let Literal::String(ref fmt) = lit.as_ref() {
21925                                        fmt.contains("%H")
21926                                            || fmt.contains("%T")
21927                                            || fmt.contains("%M")
21928                                            || fmt.contains("%S")
21929                                            || fmt.contains("%I")
21930                                            || fmt.contains("%p")
21931                                    } else {
21932                                        false
21933                                    }
21934                                } else {
21935                                    false
21936                                };
21937                                let date_parse = Expression::Function(Box::new(Function::new(
21938                                    "DATE_PARSE".to_string(),
21939                                    vec![x, format_expr],
21940                                )));
21941                                if has_time {
21942                                    // Has time components: just DATE_PARSE
21943                                    Ok(date_parse)
21944                                } else {
21945                                    // Date-only: CAST(DATE_PARSE(...) AS DATE)
21946                                    Ok(Expression::Cast(Box::new(Cast {
21947                                        this: date_parse,
21948                                        to: DataType::Date,
21949                                        trailing_comments: Vec::new(),
21950                                        double_colon_syntax: false,
21951                                        format: None,
21952                                        default: None,
21953                                        inferred_type: None,
21954                                    })))
21955                                }
21956                            }
21957                            "STR_TO_DATE"
21958                                if f.args.len() == 2
21959                                    && matches!(
21960                                        target,
21961                                        DialectType::PostgreSQL | DialectType::Redshift
21962                                    ) =>
21963                            {
21964                                let mut args = f.args;
21965                                let x = args.remove(0);
21966                                let fmt = args.remove(0);
21967                                let pg_fmt = match fmt {
21968                                    Expression::Literal(lit)
21969                                        if matches!(lit.as_ref(), Literal::String(_)) =>
21970                                    {
21971                                        let Literal::String(s) = lit.as_ref() else {
21972                                            unreachable!()
21973                                        };
21974                                        Expression::string(
21975                                            &s.replace("%Y", "YYYY")
21976                                                .replace("%m", "MM")
21977                                                .replace("%d", "DD")
21978                                                .replace("%H", "HH24")
21979                                                .replace("%M", "MI")
21980                                                .replace("%S", "SS"),
21981                                        )
21982                                    }
21983                                    other => other,
21984                                };
21985                                let to_date = Expression::Function(Box::new(Function::new(
21986                                    "TO_DATE".to_string(),
21987                                    vec![x, pg_fmt],
21988                                )));
21989                                Ok(Expression::Cast(Box::new(Cast {
21990                                    this: to_date,
21991                                    to: DataType::Timestamp {
21992                                        timezone: false,
21993                                        precision: None,
21994                                    },
21995                                    trailing_comments: Vec::new(),
21996                                    double_colon_syntax: false,
21997                                    format: None,
21998                                    default: None,
21999                                    inferred_type: None,
22000                                })))
22001                            }
22002                            // RANGE(start, end) -> GENERATE_SERIES for SQLite
22003                            "RANGE"
22004                                if (f.args.len() == 1 || f.args.len() == 2)
22005                                    && matches!(target, DialectType::SQLite) =>
22006                            {
22007                                if f.args.len() == 2 {
22008                                    // RANGE(start, end) -> (SELECT value AS col_alias FROM GENERATE_SERIES(start, end))
22009                                    // For SQLite, RANGE is exclusive on end, GENERATE_SERIES is inclusive
22010                                    let mut args = f.args;
22011                                    let start = args.remove(0);
22012                                    let end = args.remove(0);
22013                                    Ok(Expression::Function(Box::new(Function::new(
22014                                        "GENERATE_SERIES".to_string(),
22015                                        vec![start, end],
22016                                    ))))
22017                                } else {
22018                                    Ok(Expression::Function(f))
22019                                }
22020                            }
22021                            // UNIFORM(low, high[, seed]) -> UNIFORM(low, high, RANDOM([seed])) for Snowflake
22022                            // When source is Snowflake, keep as-is (args already in correct form)
22023                            "UNIFORM"
22024                                if matches!(target, DialectType::Snowflake)
22025                                    && (f.args.len() == 2 || f.args.len() == 3) =>
22026                            {
22027                                if matches!(source, DialectType::Snowflake) {
22028                                    // Snowflake -> Snowflake: keep as-is
22029                                    Ok(Expression::Function(f))
22030                                } else {
22031                                    let mut args = f.args;
22032                                    let low = args.remove(0);
22033                                    let high = args.remove(0);
22034                                    let random = if !args.is_empty() {
22035                                        let seed = args.remove(0);
22036                                        Expression::Function(Box::new(Function::new(
22037                                            "RANDOM".to_string(),
22038                                            vec![seed],
22039                                        )))
22040                                    } else {
22041                                        Expression::Function(Box::new(Function::new(
22042                                            "RANDOM".to_string(),
22043                                            vec![],
22044                                        )))
22045                                    };
22046                                    Ok(Expression::Function(Box::new(Function::new(
22047                                        "UNIFORM".to_string(),
22048                                        vec![low, high, random],
22049                                    ))))
22050                                }
22051                            }
22052                            // TO_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
22053                            "TO_UTC_TIMESTAMP" if f.args.len() == 2 => {
22054                                let mut args = f.args;
22055                                let ts_arg = args.remove(0);
22056                                let tz_arg = args.remove(0);
22057                                // Cast string literal to TIMESTAMP for all targets
22058                                let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
22059                                {
22060                                    Expression::Cast(Box::new(Cast {
22061                                        this: ts_arg,
22062                                        to: DataType::Timestamp {
22063                                            timezone: false,
22064                                            precision: None,
22065                                        },
22066                                        trailing_comments: vec![],
22067                                        double_colon_syntax: false,
22068                                        format: None,
22069                                        default: None,
22070                                        inferred_type: None,
22071                                    }))
22072                                } else {
22073                                    ts_arg
22074                                };
22075                                match target {
22076                                    DialectType::Spark | DialectType::Databricks => {
22077                                        Ok(Expression::Function(Box::new(Function::new(
22078                                            "TO_UTC_TIMESTAMP".to_string(),
22079                                            vec![ts_cast, tz_arg],
22080                                        ))))
22081                                    }
22082                                    DialectType::Snowflake => {
22083                                        // CONVERT_TIMEZONE(tz, 'UTC', CAST(ts AS TIMESTAMP))
22084                                        Ok(Expression::Function(Box::new(Function::new(
22085                                            "CONVERT_TIMEZONE".to_string(),
22086                                            vec![tz_arg, Expression::string("UTC"), ts_cast],
22087                                        ))))
22088                                    }
22089                                    DialectType::Presto
22090                                    | DialectType::Trino
22091                                    | DialectType::Athena => {
22092                                        // WITH_TIMEZONE(CAST(ts AS TIMESTAMP), tz) AT TIME ZONE 'UTC'
22093                                        let wtz = Expression::Function(Box::new(Function::new(
22094                                            "WITH_TIMEZONE".to_string(),
22095                                            vec![ts_cast, tz_arg],
22096                                        )));
22097                                        Ok(Expression::AtTimeZone(Box::new(
22098                                            crate::expressions::AtTimeZone {
22099                                                this: wtz,
22100                                                zone: Expression::string("UTC"),
22101                                            },
22102                                        )))
22103                                    }
22104                                    DialectType::BigQuery => {
22105                                        // DATETIME(TIMESTAMP(CAST(ts AS DATETIME), tz), 'UTC')
22106                                        let cast_dt = Expression::Cast(Box::new(Cast {
22107                                            this: if let Expression::Cast(c) = ts_cast {
22108                                                c.this
22109                                            } else {
22110                                                ts_cast.clone()
22111                                            },
22112                                            to: DataType::Custom {
22113                                                name: "DATETIME".to_string(),
22114                                            },
22115                                            trailing_comments: vec![],
22116                                            double_colon_syntax: false,
22117                                            format: None,
22118                                            default: None,
22119                                            inferred_type: None,
22120                                        }));
22121                                        let ts_func =
22122                                            Expression::Function(Box::new(Function::new(
22123                                                "TIMESTAMP".to_string(),
22124                                                vec![cast_dt, tz_arg],
22125                                            )));
22126                                        Ok(Expression::Function(Box::new(Function::new(
22127                                            "DATETIME".to_string(),
22128                                            vec![ts_func, Expression::string("UTC")],
22129                                        ))))
22130                                    }
22131                                    _ => {
22132                                        // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz AT TIME ZONE 'UTC'
22133                                        let atz1 = Expression::AtTimeZone(Box::new(
22134                                            crate::expressions::AtTimeZone {
22135                                                this: ts_cast,
22136                                                zone: tz_arg,
22137                                            },
22138                                        ));
22139                                        Ok(Expression::AtTimeZone(Box::new(
22140                                            crate::expressions::AtTimeZone {
22141                                                this: atz1,
22142                                                zone: Expression::string("UTC"),
22143                                            },
22144                                        )))
22145                                    }
22146                                }
22147                            }
22148                            // FROM_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
22149                            "FROM_UTC_TIMESTAMP" if f.args.len() == 2 => {
22150                                let mut args = f.args;
22151                                let ts_arg = args.remove(0);
22152                                let tz_arg = args.remove(0);
22153                                // Cast string literal to TIMESTAMP
22154                                let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
22155                                {
22156                                    Expression::Cast(Box::new(Cast {
22157                                        this: ts_arg,
22158                                        to: DataType::Timestamp {
22159                                            timezone: false,
22160                                            precision: None,
22161                                        },
22162                                        trailing_comments: vec![],
22163                                        double_colon_syntax: false,
22164                                        format: None,
22165                                        default: None,
22166                                        inferred_type: None,
22167                                    }))
22168                                } else {
22169                                    ts_arg
22170                                };
22171                                match target {
22172                                    DialectType::Spark | DialectType::Databricks => {
22173                                        Ok(Expression::Function(Box::new(Function::new(
22174                                            "FROM_UTC_TIMESTAMP".to_string(),
22175                                            vec![ts_cast, tz_arg],
22176                                        ))))
22177                                    }
22178                                    DialectType::Presto
22179                                    | DialectType::Trino
22180                                    | DialectType::Athena => {
22181                                        // AT_TIMEZONE(CAST(ts AS TIMESTAMP), tz)
22182                                        Ok(Expression::Function(Box::new(Function::new(
22183                                            "AT_TIMEZONE".to_string(),
22184                                            vec![ts_cast, tz_arg],
22185                                        ))))
22186                                    }
22187                                    DialectType::Snowflake => {
22188                                        // CONVERT_TIMEZONE('UTC', tz, CAST(ts AS TIMESTAMP))
22189                                        Ok(Expression::Function(Box::new(Function::new(
22190                                            "CONVERT_TIMEZONE".to_string(),
22191                                            vec![Expression::string("UTC"), tz_arg, ts_cast],
22192                                        ))))
22193                                    }
22194                                    _ => {
22195                                        // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz
22196                                        Ok(Expression::AtTimeZone(Box::new(
22197                                            crate::expressions::AtTimeZone {
22198                                                this: ts_cast,
22199                                                zone: tz_arg,
22200                                            },
22201                                        )))
22202                                    }
22203                                }
22204                            }
22205                            // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
22206                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => {
22207                                let name = match target {
22208                                    DialectType::Snowflake => "OBJECT_CONSTRUCT",
22209                                    _ => "MAP",
22210                                };
22211                                Ok(Expression::Function(Box::new(Function::new(
22212                                    name.to_string(),
22213                                    f.args,
22214                                ))))
22215                            }
22216                            // STR_TO_MAP(s, pair_delim, kv_delim) -> SPLIT_TO_MAP for Presto
22217                            "STR_TO_MAP" if f.args.len() >= 1 => match target {
22218                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
22219                                    Ok(Expression::Function(Box::new(Function::new(
22220                                        "SPLIT_TO_MAP".to_string(),
22221                                        f.args,
22222                                    ))))
22223                                }
22224                                _ => Ok(Expression::Function(f)),
22225                            },
22226                            // TIME_TO_STR(x, fmt) -> Expression::TimeToStr for proper generation
22227                            "TIME_TO_STR" if f.args.len() == 2 => {
22228                                let mut args = f.args;
22229                                let this = args.remove(0);
22230                                let fmt_expr = args.remove(0);
22231                                let format = if let Expression::Literal(lit) = fmt_expr {
22232                                    if let Literal::String(s) = lit.as_ref() {
22233                                        s.clone()
22234                                    } else {
22235                                        String::new()
22236                                    }
22237                                } else {
22238                                    "%Y-%m-%d %H:%M:%S".to_string()
22239                                };
22240                                Ok(Expression::TimeToStr(Box::new(
22241                                    crate::expressions::TimeToStr {
22242                                        this: Box::new(this),
22243                                        format,
22244                                        culture: None,
22245                                        zone: None,
22246                                    },
22247                                )))
22248                            }
22249                            // STR_TO_TIME(x, fmt) -> Expression::StrToTime for proper generation
22250                            "STR_TO_TIME" if f.args.len() == 2 => {
22251                                let mut args = f.args;
22252                                let this = args.remove(0);
22253                                let fmt_expr = args.remove(0);
22254                                let format = if let Expression::Literal(lit) = fmt_expr {
22255                                    if let Literal::String(s) = lit.as_ref() {
22256                                        s.clone()
22257                                    } else {
22258                                        String::new()
22259                                    }
22260                                } else {
22261                                    "%Y-%m-%d %H:%M:%S".to_string()
22262                                };
22263                                Ok(Expression::StrToTime(Box::new(
22264                                    crate::expressions::StrToTime {
22265                                        this: Box::new(this),
22266                                        format,
22267                                        zone: None,
22268                                        safe: None,
22269                                        target_type: None,
22270                                    },
22271                                )))
22272                            }
22273                            // STR_TO_UNIX(x, fmt) -> Expression::StrToUnix for proper generation
22274                            "STR_TO_UNIX" if f.args.len() >= 1 => {
22275                                let mut args = f.args;
22276                                let this = args.remove(0);
22277                                let format = if !args.is_empty() {
22278                                    if let Expression::Literal(lit) = args.remove(0) {
22279                                        if let Literal::String(s) = lit.as_ref() {
22280                                            Some(s.clone())
22281                                        } else {
22282                                            None
22283                                        }
22284                                    } else {
22285                                        None
22286                                    }
22287                                } else {
22288                                    None
22289                                };
22290                                Ok(Expression::StrToUnix(Box::new(
22291                                    crate::expressions::StrToUnix {
22292                                        this: Some(Box::new(this)),
22293                                        format,
22294                                    },
22295                                )))
22296                            }
22297                            // TIME_TO_UNIX(x) -> Expression::TimeToUnix for proper generation
22298                            "TIME_TO_UNIX" if f.args.len() == 1 => {
22299                                let mut args = f.args;
22300                                let this = args.remove(0);
22301                                Ok(Expression::TimeToUnix(Box::new(
22302                                    crate::expressions::UnaryFunc {
22303                                        this,
22304                                        original_name: None,
22305                                        inferred_type: None,
22306                                    },
22307                                )))
22308                            }
22309                            // UNIX_TO_STR(x, fmt) -> Expression::UnixToStr for proper generation
22310                            "UNIX_TO_STR" if f.args.len() >= 1 => {
22311                                let mut args = f.args;
22312                                let this = args.remove(0);
22313                                let format = if !args.is_empty() {
22314                                    if let Expression::Literal(lit) = args.remove(0) {
22315                                        if let Literal::String(s) = lit.as_ref() {
22316                                            Some(s.clone())
22317                                        } else {
22318                                            None
22319                                        }
22320                                    } else {
22321                                        None
22322                                    }
22323                                } else {
22324                                    None
22325                                };
22326                                Ok(Expression::UnixToStr(Box::new(
22327                                    crate::expressions::UnixToStr {
22328                                        this: Box::new(this),
22329                                        format,
22330                                    },
22331                                )))
22332                            }
22333                            // UNIX_TO_TIME(x) -> Expression::UnixToTime for proper generation
22334                            "UNIX_TO_TIME" if f.args.len() == 1 => {
22335                                let mut args = f.args;
22336                                let this = args.remove(0);
22337                                Ok(Expression::UnixToTime(Box::new(
22338                                    crate::expressions::UnixToTime {
22339                                        this: Box::new(this),
22340                                        scale: None,
22341                                        zone: None,
22342                                        hours: None,
22343                                        minutes: None,
22344                                        format: None,
22345                                        target_type: None,
22346                                    },
22347                                )))
22348                            }
22349                            // TIME_STR_TO_DATE(x) -> Expression::TimeStrToDate for proper generation
22350                            "TIME_STR_TO_DATE" if f.args.len() == 1 => {
22351                                let mut args = f.args;
22352                                let this = args.remove(0);
22353                                Ok(Expression::TimeStrToDate(Box::new(
22354                                    crate::expressions::UnaryFunc {
22355                                        this,
22356                                        original_name: None,
22357                                        inferred_type: None,
22358                                    },
22359                                )))
22360                            }
22361                            // TIME_STR_TO_TIME(x) -> Expression::TimeStrToTime for proper generation
22362                            "TIME_STR_TO_TIME" if f.args.len() == 1 => {
22363                                let mut args = f.args;
22364                                let this = args.remove(0);
22365                                Ok(Expression::TimeStrToTime(Box::new(
22366                                    crate::expressions::TimeStrToTime {
22367                                        this: Box::new(this),
22368                                        zone: None,
22369                                    },
22370                                )))
22371                            }
22372                            // MONTHS_BETWEEN(end, start) -> DuckDB complex expansion
22373                            "MONTHS_BETWEEN" if f.args.len() == 2 => {
22374                                match target {
22375                                    DialectType::DuckDB => {
22376                                        let mut args = f.args;
22377                                        let end_date = args.remove(0);
22378                                        let start_date = args.remove(0);
22379                                        let cast_end = Self::ensure_cast_date(end_date);
22380                                        let cast_start = Self::ensure_cast_date(start_date);
22381                                        // DATE_DIFF('MONTH', start, end) + CASE WHEN DAY(end) = DAY(LAST_DAY(end)) AND DAY(start) = DAY(LAST_DAY(start)) THEN 0 ELSE (DAY(end) - DAY(start)) / 31.0 END
22382                                        let dd = Expression::Function(Box::new(Function::new(
22383                                            "DATE_DIFF".to_string(),
22384                                            vec![
22385                                                Expression::string("MONTH"),
22386                                                cast_start.clone(),
22387                                                cast_end.clone(),
22388                                            ],
22389                                        )));
22390                                        let day_end =
22391                                            Expression::Function(Box::new(Function::new(
22392                                                "DAY".to_string(),
22393                                                vec![cast_end.clone()],
22394                                            )));
22395                                        let day_start =
22396                                            Expression::Function(Box::new(Function::new(
22397                                                "DAY".to_string(),
22398                                                vec![cast_start.clone()],
22399                                            )));
22400                                        let last_day_end =
22401                                            Expression::Function(Box::new(Function::new(
22402                                                "LAST_DAY".to_string(),
22403                                                vec![cast_end.clone()],
22404                                            )));
22405                                        let last_day_start =
22406                                            Expression::Function(Box::new(Function::new(
22407                                                "LAST_DAY".to_string(),
22408                                                vec![cast_start.clone()],
22409                                            )));
22410                                        let day_last_end = Expression::Function(Box::new(
22411                                            Function::new("DAY".to_string(), vec![last_day_end]),
22412                                        ));
22413                                        let day_last_start = Expression::Function(Box::new(
22414                                            Function::new("DAY".to_string(), vec![last_day_start]),
22415                                        ));
22416                                        let cond1 = Expression::Eq(Box::new(BinaryOp::new(
22417                                            day_end.clone(),
22418                                            day_last_end,
22419                                        )));
22420                                        let cond2 = Expression::Eq(Box::new(BinaryOp::new(
22421                                            day_start.clone(),
22422                                            day_last_start,
22423                                        )));
22424                                        let both_cond =
22425                                            Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
22426                                        let day_diff = Expression::Sub(Box::new(BinaryOp::new(
22427                                            day_end, day_start,
22428                                        )));
22429                                        let day_diff_paren = Expression::Paren(Box::new(
22430                                            crate::expressions::Paren {
22431                                                this: day_diff,
22432                                                trailing_comments: Vec::new(),
22433                                            },
22434                                        ));
22435                                        let frac = Expression::Div(Box::new(BinaryOp::new(
22436                                            day_diff_paren,
22437                                            Expression::Literal(Box::new(Literal::Number(
22438                                                "31.0".to_string(),
22439                                            ))),
22440                                        )));
22441                                        let case_expr = Expression::Case(Box::new(Case {
22442                                            operand: None,
22443                                            whens: vec![(both_cond, Expression::number(0))],
22444                                            else_: Some(frac),
22445                                            comments: Vec::new(),
22446                                            inferred_type: None,
22447                                        }));
22448                                        Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
22449                                    }
22450                                    DialectType::Snowflake | DialectType::Redshift => {
22451                                        let mut args = f.args;
22452                                        let end_date = args.remove(0);
22453                                        let start_date = args.remove(0);
22454                                        let unit = Expression::Identifier(Identifier::new("MONTH"));
22455                                        Ok(Expression::Function(Box::new(Function::new(
22456                                            "DATEDIFF".to_string(),
22457                                            vec![unit, start_date, end_date],
22458                                        ))))
22459                                    }
22460                                    DialectType::Presto
22461                                    | DialectType::Trino
22462                                    | DialectType::Athena => {
22463                                        let mut args = f.args;
22464                                        let end_date = args.remove(0);
22465                                        let start_date = args.remove(0);
22466                                        Ok(Expression::Function(Box::new(Function::new(
22467                                            "DATE_DIFF".to_string(),
22468                                            vec![Expression::string("MONTH"), start_date, end_date],
22469                                        ))))
22470                                    }
22471                                    _ => Ok(Expression::Function(f)),
22472                                }
22473                            }
22474                            // MONTHS_BETWEEN(end, start, roundOff) - 3-arg form (Spark-specific)
22475                            // Drop the roundOff arg for non-Spark targets, keep it for Spark
22476                            "MONTHS_BETWEEN" if f.args.len() == 3 => {
22477                                match target {
22478                                    DialectType::Spark | DialectType::Databricks => {
22479                                        Ok(Expression::Function(f))
22480                                    }
22481                                    _ => {
22482                                        // Drop the 3rd arg and delegate to the 2-arg logic
22483                                        let mut args = f.args;
22484                                        let end_date = args.remove(0);
22485                                        let start_date = args.remove(0);
22486                                        // Re-create as 2-arg and process
22487                                        let f2 = Function::new(
22488                                            "MONTHS_BETWEEN".to_string(),
22489                                            vec![end_date, start_date],
22490                                        );
22491                                        let e2 = Expression::Function(Box::new(f2));
22492                                        Self::cross_dialect_normalize(e2, source, target)
22493                                    }
22494                                }
22495                            }
22496                            // TO_TIMESTAMP(x) with 1 arg -> CAST(x AS TIMESTAMP) for most targets
22497                            "TO_TIMESTAMP"
22498                                if f.args.len() == 1
22499                                    && matches!(
22500                                        source,
22501                                        DialectType::Spark
22502                                            | DialectType::Databricks
22503                                            | DialectType::Hive
22504                                    ) =>
22505                            {
22506                                let arg = f.args.into_iter().next().unwrap();
22507                                Ok(Expression::Cast(Box::new(Cast {
22508                                    this: arg,
22509                                    to: DataType::Timestamp {
22510                                        timezone: false,
22511                                        precision: None,
22512                                    },
22513                                    trailing_comments: vec![],
22514                                    double_colon_syntax: false,
22515                                    format: None,
22516                                    default: None,
22517                                    inferred_type: None,
22518                                })))
22519                            }
22520                            // STRING(x) -> CAST(x AS STRING) for Spark target
22521                            "STRING"
22522                                if f.args.len() == 1
22523                                    && matches!(
22524                                        source,
22525                                        DialectType::Spark | DialectType::Databricks
22526                                    ) =>
22527                            {
22528                                let arg = f.args.into_iter().next().unwrap();
22529                                let dt = match target {
22530                                    DialectType::Spark
22531                                    | DialectType::Databricks
22532                                    | DialectType::Hive => DataType::Custom {
22533                                        name: "STRING".to_string(),
22534                                    },
22535                                    _ => DataType::Text,
22536                                };
22537                                Ok(Expression::Cast(Box::new(Cast {
22538                                    this: arg,
22539                                    to: dt,
22540                                    trailing_comments: vec![],
22541                                    double_colon_syntax: false,
22542                                    format: None,
22543                                    default: None,
22544                                    inferred_type: None,
22545                                })))
22546                            }
22547                            // LOGICAL_OR(x) -> BOOL_OR(x) for Spark target
22548                            "LOGICAL_OR" if f.args.len() == 1 => {
22549                                let name = match target {
22550                                    DialectType::Spark | DialectType::Databricks => "BOOL_OR",
22551                                    _ => "LOGICAL_OR",
22552                                };
22553                                Ok(Expression::Function(Box::new(Function::new(
22554                                    name.to_string(),
22555                                    f.args,
22556                                ))))
22557                            }
22558                            // SPLIT(x, pattern) from Spark -> STR_SPLIT_REGEX for DuckDB, REGEXP_SPLIT for Presto
22559                            "SPLIT"
22560                                if f.args.len() == 2
22561                                    && matches!(
22562                                        source,
22563                                        DialectType::Spark
22564                                            | DialectType::Databricks
22565                                            | DialectType::Hive
22566                                    ) =>
22567                            {
22568                                let name = match target {
22569                                    DialectType::DuckDB => "STR_SPLIT_REGEX",
22570                                    DialectType::Presto
22571                                    | DialectType::Trino
22572                                    | DialectType::Athena => "REGEXP_SPLIT",
22573                                    DialectType::Spark
22574                                    | DialectType::Databricks
22575                                    | DialectType::Hive => "SPLIT",
22576                                    _ => "SPLIT",
22577                                };
22578                                Ok(Expression::Function(Box::new(Function::new(
22579                                    name.to_string(),
22580                                    f.args,
22581                                ))))
22582                            }
22583                            // TRY_ELEMENT_AT -> ELEMENT_AT for Presto, array[idx] for DuckDB
22584                            "TRY_ELEMENT_AT" if f.args.len() == 2 => match target {
22585                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
22586                                    Ok(Expression::Function(Box::new(Function::new(
22587                                        "ELEMENT_AT".to_string(),
22588                                        f.args,
22589                                    ))))
22590                                }
22591                                DialectType::DuckDB => {
22592                                    let mut args = f.args;
22593                                    let arr = args.remove(0);
22594                                    let idx = args.remove(0);
22595                                    Ok(Expression::Subscript(Box::new(
22596                                        crate::expressions::Subscript {
22597                                            this: arr,
22598                                            index: idx,
22599                                        },
22600                                    )))
22601                                }
22602                                _ => Ok(Expression::Function(f)),
22603                            },
22604                            // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, LIST_FILTER for DuckDB
22605                            "ARRAY_FILTER" if f.args.len() == 2 => {
22606                                let name = match target {
22607                                    DialectType::DuckDB => "LIST_FILTER",
22608                                    DialectType::StarRocks => "ARRAY_FILTER",
22609                                    _ => "FILTER",
22610                                };
22611                                Ok(Expression::Function(Box::new(Function::new(
22612                                    name.to_string(),
22613                                    f.args,
22614                                ))))
22615                            }
22616                            // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
22617                            "FILTER" if f.args.len() == 2 => {
22618                                let name = match target {
22619                                    DialectType::DuckDB => "LIST_FILTER",
22620                                    DialectType::StarRocks => "ARRAY_FILTER",
22621                                    _ => "FILTER",
22622                                };
22623                                Ok(Expression::Function(Box::new(Function::new(
22624                                    name.to_string(),
22625                                    f.args,
22626                                ))))
22627                            }
22628                            // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
22629                            "REDUCE" if f.args.len() >= 3 => {
22630                                let name = match target {
22631                                    DialectType::Spark | DialectType::Databricks => "AGGREGATE",
22632                                    _ => "REDUCE",
22633                                };
22634                                Ok(Expression::Function(Box::new(Function::new(
22635                                    name.to_string(),
22636                                    f.args,
22637                                ))))
22638                            }
22639                            // CURRENT_SCHEMA() -> dialect-specific
22640                            "CURRENT_SCHEMA" => {
22641                                match target {
22642                                    DialectType::PostgreSQL => {
22643                                        // PostgreSQL: CURRENT_SCHEMA (no parens)
22644                                        Ok(Expression::Function(Box::new(Function {
22645                                            name: "CURRENT_SCHEMA".to_string(),
22646                                            args: vec![],
22647                                            distinct: false,
22648                                            trailing_comments: vec![],
22649                                            use_bracket_syntax: false,
22650                                            no_parens: true,
22651                                            quoted: false,
22652                                            span: None,
22653                                            inferred_type: None,
22654                                        })))
22655                                    }
22656                                    DialectType::MySQL
22657                                    | DialectType::Doris
22658                                    | DialectType::StarRocks => Ok(Expression::Function(Box::new(
22659                                        Function::new("SCHEMA".to_string(), vec![]),
22660                                    ))),
22661                                    DialectType::TSQL => Ok(Expression::Function(Box::new(
22662                                        Function::new("SCHEMA_NAME".to_string(), vec![]),
22663                                    ))),
22664                                    DialectType::SQLite => Ok(Expression::Literal(Box::new(
22665                                        Literal::String("main".to_string()),
22666                                    ))),
22667                                    _ => Ok(Expression::Function(f)),
22668                                }
22669                            }
22670                            // LTRIM(str, chars) 2-arg -> TRIM(LEADING chars FROM str) for Spark/Hive/Databricks/ClickHouse
22671                            "LTRIM" if f.args.len() == 2 => match target {
22672                                DialectType::Spark
22673                                | DialectType::Hive
22674                                | DialectType::Databricks
22675                                | DialectType::ClickHouse => {
22676                                    let mut args = f.args;
22677                                    let str_expr = args.remove(0);
22678                                    let chars = args.remove(0);
22679                                    Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
22680                                        this: str_expr,
22681                                        characters: Some(chars),
22682                                        position: crate::expressions::TrimPosition::Leading,
22683                                        sql_standard_syntax: true,
22684                                        position_explicit: true,
22685                                    })))
22686                                }
22687                                _ => Ok(Expression::Function(f)),
22688                            },
22689                            // RTRIM(str, chars) 2-arg -> TRIM(TRAILING chars FROM str) for Spark/Hive/Databricks/ClickHouse
22690                            "RTRIM" if f.args.len() == 2 => match target {
22691                                DialectType::Spark
22692                                | DialectType::Hive
22693                                | DialectType::Databricks
22694                                | DialectType::ClickHouse => {
22695                                    let mut args = f.args;
22696                                    let str_expr = args.remove(0);
22697                                    let chars = args.remove(0);
22698                                    Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
22699                                        this: str_expr,
22700                                        characters: Some(chars),
22701                                        position: crate::expressions::TrimPosition::Trailing,
22702                                        sql_standard_syntax: true,
22703                                        position_explicit: true,
22704                                    })))
22705                                }
22706                                _ => Ok(Expression::Function(f)),
22707                            },
22708                            // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
22709                            "ARRAY_REVERSE" if f.args.len() == 1 => match target {
22710                                DialectType::ClickHouse => {
22711                                    let mut new_f = *f;
22712                                    new_f.name = "arrayReverse".to_string();
22713                                    Ok(Expression::Function(Box::new(new_f)))
22714                                }
22715                                _ => Ok(Expression::Function(f)),
22716                            },
22717                            // UUID() -> NEWID() for TSQL
22718                            "UUID" if f.args.is_empty() => match target {
22719                                DialectType::TSQL | DialectType::Fabric => {
22720                                    Ok(Expression::Function(Box::new(Function::new(
22721                                        "NEWID".to_string(),
22722                                        vec![],
22723                                    ))))
22724                                }
22725                                _ => Ok(Expression::Function(f)),
22726                            },
22727                            // FARM_FINGERPRINT(x) -> farmFingerprint64(x) for ClickHouse, FARMFINGERPRINT64(x) for Redshift
22728                            "FARM_FINGERPRINT" if f.args.len() == 1 => match target {
22729                                DialectType::ClickHouse => {
22730                                    let mut new_f = *f;
22731                                    new_f.name = "farmFingerprint64".to_string();
22732                                    Ok(Expression::Function(Box::new(new_f)))
22733                                }
22734                                DialectType::Redshift => {
22735                                    let mut new_f = *f;
22736                                    new_f.name = "FARMFINGERPRINT64".to_string();
22737                                    Ok(Expression::Function(Box::new(new_f)))
22738                                }
22739                                _ => Ok(Expression::Function(f)),
22740                            },
22741                            // JSON_KEYS(x) -> JSON_OBJECT_KEYS(x) for Databricks/Spark, OBJECT_KEYS(x) for Snowflake
22742                            "JSON_KEYS" => match target {
22743                                DialectType::Databricks | DialectType::Spark => {
22744                                    let mut new_f = *f;
22745                                    new_f.name = "JSON_OBJECT_KEYS".to_string();
22746                                    Ok(Expression::Function(Box::new(new_f)))
22747                                }
22748                                DialectType::Snowflake => {
22749                                    let mut new_f = *f;
22750                                    new_f.name = "OBJECT_KEYS".to_string();
22751                                    Ok(Expression::Function(Box::new(new_f)))
22752                                }
22753                                _ => Ok(Expression::Function(f)),
22754                            },
22755                            // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake
22756                            "WEEKOFYEAR" => match target {
22757                                DialectType::Snowflake => {
22758                                    let mut new_f = *f;
22759                                    new_f.name = "WEEKISO".to_string();
22760                                    Ok(Expression::Function(Box::new(new_f)))
22761                                }
22762                                _ => Ok(Expression::Function(f)),
22763                            },
22764                            // FORMAT(fmt, args...) -> FORMAT_STRING(fmt, args...) for Databricks
22765                            "FORMAT"
22766                                if f.args.len() >= 2 && matches!(source, DialectType::Generic) =>
22767                            {
22768                                match target {
22769                                    DialectType::Databricks | DialectType::Spark => {
22770                                        let mut new_f = *f;
22771                                        new_f.name = "FORMAT_STRING".to_string();
22772                                        Ok(Expression::Function(Box::new(new_f)))
22773                                    }
22774                                    _ => Ok(Expression::Function(f)),
22775                                }
22776                            }
22777                            // CONCAT_WS from Generic is null-propagating in SQLGlot fixtures.
22778                            // Trino also requires non-separator arguments cast to VARCHAR.
22779                            "CONCAT_WS" if f.args.len() >= 2 => {
22780                                fn concat_ws_null_case(
22781                                    args: Vec<Expression>,
22782                                    else_expr: Expression,
22783                                ) -> Expression {
22784                                    let mut null_checks = args.iter().cloned().map(|arg| {
22785                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
22786                                            this: arg,
22787                                            not: false,
22788                                            postfix_form: false,
22789                                        }))
22790                                    });
22791                                    let first_null_check = null_checks
22792                                        .next()
22793                                        .expect("CONCAT_WS with >= 2 args must yield a null check");
22794                                    let null_check =
22795                                        null_checks.fold(first_null_check, |left, right| {
22796                                            Expression::Or(Box::new(BinaryOp {
22797                                                left,
22798                                                right,
22799                                                left_comments: Vec::new(),
22800                                                operator_comments: Vec::new(),
22801                                                trailing_comments: Vec::new(),
22802                                                inferred_type: None,
22803                                            }))
22804                                        });
22805                                    Expression::Case(Box::new(Case {
22806                                        operand: None,
22807                                        whens: vec![(null_check, Expression::Null(Null))],
22808                                        else_: Some(else_expr),
22809                                        comments: vec![],
22810                                        inferred_type: None,
22811                                    }))
22812                                }
22813
22814                                match target {
22815                                    DialectType::Trino
22816                                        if matches!(source, DialectType::Generic) =>
22817                                    {
22818                                        let original_args = f.args.clone();
22819                                        let mut args = f.args;
22820                                        let sep = args.remove(0);
22821                                        let cast_args: Vec<Expression> = args
22822                                            .into_iter()
22823                                            .map(|a| {
22824                                                Expression::Cast(Box::new(Cast {
22825                                                    this: a,
22826                                                    to: DataType::VarChar {
22827                                                        length: None,
22828                                                        parenthesized_length: false,
22829                                                    },
22830                                                    double_colon_syntax: false,
22831                                                    trailing_comments: Vec::new(),
22832                                                    format: None,
22833                                                    default: None,
22834                                                    inferred_type: None,
22835                                                }))
22836                                            })
22837                                            .collect();
22838                                        let mut new_args = vec![sep];
22839                                        new_args.extend(cast_args);
22840                                        let else_expr = Expression::Function(Box::new(
22841                                            Function::new("CONCAT_WS".to_string(), new_args),
22842                                        ));
22843                                        Ok(concat_ws_null_case(original_args, else_expr))
22844                                    }
22845                                    DialectType::Presto
22846                                    | DialectType::Trino
22847                                    | DialectType::Athena => {
22848                                        let mut args = f.args;
22849                                        let sep = args.remove(0);
22850                                        let cast_args: Vec<Expression> = args
22851                                            .into_iter()
22852                                            .map(|a| {
22853                                                Expression::Cast(Box::new(Cast {
22854                                                    this: a,
22855                                                    to: DataType::VarChar {
22856                                                        length: None,
22857                                                        parenthesized_length: false,
22858                                                    },
22859                                                    double_colon_syntax: false,
22860                                                    trailing_comments: Vec::new(),
22861                                                    format: None,
22862                                                    default: None,
22863                                                    inferred_type: None,
22864                                                }))
22865                                            })
22866                                            .collect();
22867                                        let mut new_args = vec![sep];
22868                                        new_args.extend(cast_args);
22869                                        Ok(Expression::Function(Box::new(Function::new(
22870                                            "CONCAT_WS".to_string(),
22871                                            new_args,
22872                                        ))))
22873                                    }
22874                                    DialectType::Spark
22875                                    | DialectType::Hive
22876                                    | DialectType::DuckDB
22877                                        if matches!(source, DialectType::Generic) =>
22878                                    {
22879                                        let args = f.args;
22880                                        let else_expr = Expression::Function(Box::new(
22881                                            Function::new("CONCAT_WS".to_string(), args.clone()),
22882                                        ));
22883                                        Ok(concat_ws_null_case(args, else_expr))
22884                                    }
22885                                    _ => Ok(Expression::Function(f)),
22886                                }
22887                            }
22888                            // ARRAY_SLICE(x, start, end) -> SLICE(x, start, end) for Presto/Trino/Databricks, arraySlice for ClickHouse
22889                            "ARRAY_SLICE" if f.args.len() >= 2 => match target {
22890                                DialectType::DuckDB
22891                                    if f.args.len() == 3
22892                                        && matches!(source, DialectType::Snowflake) =>
22893                                {
22894                                    // Snowflake ARRAY_SLICE (0-indexed, exclusive end)
22895                                    // -> DuckDB ARRAY_SLICE (1-indexed, inclusive end)
22896                                    let mut args = f.args;
22897                                    let arr = args.remove(0);
22898                                    let start = args.remove(0);
22899                                    let end = args.remove(0);
22900
22901                                    // CASE WHEN start >= 0 THEN start + 1 ELSE start END
22902                                    let adjusted_start = Expression::Case(Box::new(Case {
22903                                        operand: None,
22904                                        whens: vec![(
22905                                            Expression::Gte(Box::new(BinaryOp {
22906                                                left: start.clone(),
22907                                                right: Expression::number(0),
22908                                                left_comments: vec![],
22909                                                operator_comments: vec![],
22910                                                trailing_comments: vec![],
22911                                                inferred_type: None,
22912                                            })),
22913                                            Expression::Add(Box::new(BinaryOp {
22914                                                left: start.clone(),
22915                                                right: Expression::number(1),
22916                                                left_comments: vec![],
22917                                                operator_comments: vec![],
22918                                                trailing_comments: vec![],
22919                                                inferred_type: None,
22920                                            })),
22921                                        )],
22922                                        else_: Some(start),
22923                                        comments: vec![],
22924                                        inferred_type: None,
22925                                    }));
22926
22927                                    // CASE WHEN end < 0 THEN end - 1 ELSE end END
22928                                    let adjusted_end = Expression::Case(Box::new(Case {
22929                                        operand: None,
22930                                        whens: vec![(
22931                                            Expression::Lt(Box::new(BinaryOp {
22932                                                left: end.clone(),
22933                                                right: Expression::number(0),
22934                                                left_comments: vec![],
22935                                                operator_comments: vec![],
22936                                                trailing_comments: vec![],
22937                                                inferred_type: None,
22938                                            })),
22939                                            Expression::Sub(Box::new(BinaryOp {
22940                                                left: end.clone(),
22941                                                right: Expression::number(1),
22942                                                left_comments: vec![],
22943                                                operator_comments: vec![],
22944                                                trailing_comments: vec![],
22945                                                inferred_type: None,
22946                                            })),
22947                                        )],
22948                                        else_: Some(end),
22949                                        comments: vec![],
22950                                        inferred_type: None,
22951                                    }));
22952
22953                                    Ok(Expression::Function(Box::new(Function::new(
22954                                        "ARRAY_SLICE".to_string(),
22955                                        vec![arr, adjusted_start, adjusted_end],
22956                                    ))))
22957                                }
22958                                DialectType::Presto
22959                                | DialectType::Trino
22960                                | DialectType::Athena
22961                                | DialectType::Databricks
22962                                | DialectType::Spark => {
22963                                    let mut new_f = *f;
22964                                    new_f.name = "SLICE".to_string();
22965                                    Ok(Expression::Function(Box::new(new_f)))
22966                                }
22967                                DialectType::ClickHouse => {
22968                                    let mut new_f = *f;
22969                                    new_f.name = "arraySlice".to_string();
22970                                    Ok(Expression::Function(Box::new(new_f)))
22971                                }
22972                                _ => Ok(Expression::Function(f)),
22973                            },
22974                            // ARRAY_PREPEND(arr, x) -> LIST_PREPEND(x, arr) for DuckDB (swap args)
22975                            "ARRAY_PREPEND" if f.args.len() == 2 => match target {
22976                                DialectType::DuckDB => {
22977                                    let mut args = f.args;
22978                                    let arr = args.remove(0);
22979                                    let val = args.remove(0);
22980                                    Ok(Expression::Function(Box::new(Function::new(
22981                                        "LIST_PREPEND".to_string(),
22982                                        vec![val, arr],
22983                                    ))))
22984                                }
22985                                _ => Ok(Expression::Function(f)),
22986                            },
22987                            // ARRAY_REMOVE(arr, target) -> dialect-specific
22988                            "ARRAY_REMOVE" if f.args.len() == 2 => {
22989                                match target {
22990                                    DialectType::DuckDB => {
22991                                        let mut args = f.args;
22992                                        let arr = args.remove(0);
22993                                        let target_val = args.remove(0);
22994                                        let u_id = crate::expressions::Identifier::new("_u");
22995                                        // LIST_FILTER(arr, _u -> _u <> target)
22996                                        let lambda = Expression::Lambda(Box::new(
22997                                            crate::expressions::LambdaExpr {
22998                                                parameters: vec![u_id.clone()],
22999                                                body: Expression::Neq(Box::new(BinaryOp {
23000                                                    left: Expression::Identifier(u_id),
23001                                                    right: target_val,
23002                                                    left_comments: Vec::new(),
23003                                                    operator_comments: Vec::new(),
23004                                                    trailing_comments: Vec::new(),
23005                                                    inferred_type: None,
23006                                                })),
23007                                                colon: false,
23008                                                parameter_types: Vec::new(),
23009                                            },
23010                                        ));
23011                                        Ok(Expression::Function(Box::new(Function::new(
23012                                            "LIST_FILTER".to_string(),
23013                                            vec![arr, lambda],
23014                                        ))))
23015                                    }
23016                                    DialectType::ClickHouse => {
23017                                        let mut args = f.args;
23018                                        let arr = args.remove(0);
23019                                        let target_val = args.remove(0);
23020                                        let u_id = crate::expressions::Identifier::new("_u");
23021                                        // arrayFilter(_u -> _u <> target, arr)
23022                                        let lambda = Expression::Lambda(Box::new(
23023                                            crate::expressions::LambdaExpr {
23024                                                parameters: vec![u_id.clone()],
23025                                                body: Expression::Neq(Box::new(BinaryOp {
23026                                                    left: Expression::Identifier(u_id),
23027                                                    right: target_val,
23028                                                    left_comments: Vec::new(),
23029                                                    operator_comments: Vec::new(),
23030                                                    trailing_comments: Vec::new(),
23031                                                    inferred_type: None,
23032                                                })),
23033                                                colon: false,
23034                                                parameter_types: Vec::new(),
23035                                            },
23036                                        ));
23037                                        Ok(Expression::Function(Box::new(Function::new(
23038                                            "arrayFilter".to_string(),
23039                                            vec![lambda, arr],
23040                                        ))))
23041                                    }
23042                                    DialectType::BigQuery => {
23043                                        // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
23044                                        let mut args = f.args;
23045                                        let arr = args.remove(0);
23046                                        let target_val = args.remove(0);
23047                                        let u_id = crate::expressions::Identifier::new("_u");
23048                                        let u_col = Expression::Column(Box::new(
23049                                            crate::expressions::Column {
23050                                                name: u_id.clone(),
23051                                                table: None,
23052                                                join_mark: false,
23053                                                trailing_comments: Vec::new(),
23054                                                span: None,
23055                                                inferred_type: None,
23056                                            },
23057                                        ));
23058                                        // UNNEST(the_array) AS _u
23059                                        let unnest_expr = Expression::Unnest(Box::new(
23060                                            crate::expressions::UnnestFunc {
23061                                                this: arr,
23062                                                expressions: Vec::new(),
23063                                                with_ordinality: false,
23064                                                alias: None,
23065                                                offset_alias: None,
23066                                            },
23067                                        ));
23068                                        let aliased_unnest = Expression::Alias(Box::new(
23069                                            crate::expressions::Alias {
23070                                                this: unnest_expr,
23071                                                alias: u_id.clone(),
23072                                                column_aliases: Vec::new(),
23073                                                alias_explicit_as: false,
23074                                                alias_keyword: None,
23075                                                pre_alias_comments: Vec::new(),
23076                                                trailing_comments: Vec::new(),
23077                                                inferred_type: None,
23078                                            },
23079                                        ));
23080                                        // _u <> target
23081                                        let where_cond = Expression::Neq(Box::new(BinaryOp {
23082                                            left: u_col.clone(),
23083                                            right: target_val,
23084                                            left_comments: Vec::new(),
23085                                            operator_comments: Vec::new(),
23086                                            trailing_comments: Vec::new(),
23087                                            inferred_type: None,
23088                                        }));
23089                                        // SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target
23090                                        let subquery = Expression::Select(Box::new(
23091                                            crate::expressions::Select::new()
23092                                                .column(u_col)
23093                                                .from(aliased_unnest)
23094                                                .where_(where_cond),
23095                                        ));
23096                                        // ARRAY(subquery) -- use ArrayFunc with subquery as single element
23097                                        Ok(Expression::ArrayFunc(Box::new(
23098                                            crate::expressions::ArrayConstructor {
23099                                                expressions: vec![subquery],
23100                                                bracket_notation: false,
23101                                                use_list_keyword: false,
23102                                            },
23103                                        )))
23104                                    }
23105                                    _ => Ok(Expression::Function(f)),
23106                                }
23107                            }
23108                            // PARSE_JSON(str) -> remove for SQLite/Doris (just use the string literal)
23109                            "PARSE_JSON" if f.args.len() == 1 => {
23110                                match target {
23111                                    DialectType::SQLite
23112                                    | DialectType::Doris
23113                                    | DialectType::MySQL
23114                                    | DialectType::StarRocks => {
23115                                        // Strip PARSE_JSON, return the inner argument
23116                                        Ok(f.args.into_iter().next().unwrap())
23117                                    }
23118                                    _ => Ok(Expression::Function(f)),
23119                                }
23120                            }
23121                            // JSON_REMOVE(PARSE_JSON(str), path...) -> for SQLite strip PARSE_JSON
23122                            // This is handled by PARSE_JSON stripping above; JSON_REMOVE is passed through
23123                            "JSON_REMOVE" => Ok(Expression::Function(f)),
23124                            // JSON_SET(PARSE_JSON(str), path, PARSE_JSON(val)) -> for SQLite strip PARSE_JSON
23125                            // This is handled by PARSE_JSON stripping above; JSON_SET is passed through
23126                            "JSON_SET" => Ok(Expression::Function(f)),
23127                            // DECODE(x, search1, result1, ..., default) -> CASE WHEN
23128                            // Behavior per search value type:
23129                            //   NULL literal -> CASE WHEN x IS NULL THEN result
23130                            //   Literal (number, string, bool) -> CASE WHEN x = literal THEN result
23131                            //   Non-literal (column, expr) -> CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
23132                            "DECODE" if f.args.len() >= 3 => {
23133                                // Keep as DECODE for targets that support it natively
23134                                let keep_as_decode = matches!(
23135                                    target,
23136                                    DialectType::Oracle
23137                                        | DialectType::Snowflake
23138                                        | DialectType::Redshift
23139                                        | DialectType::Teradata
23140                                        | DialectType::Spark
23141                                        | DialectType::Databricks
23142                                );
23143                                if keep_as_decode {
23144                                    return Ok(Expression::Function(f));
23145                                }
23146
23147                                let mut args = f.args;
23148                                let this_expr = args.remove(0);
23149                                let mut pairs = Vec::new();
23150                                let mut default = None;
23151                                let mut i = 0;
23152                                while i + 1 < args.len() {
23153                                    pairs.push((args[i].clone(), args[i + 1].clone()));
23154                                    i += 2;
23155                                }
23156                                if i < args.len() {
23157                                    default = Some(args[i].clone());
23158                                }
23159                                // Helper: check if expression is a literal value
23160                                fn is_literal(e: &Expression) -> bool {
23161                                    matches!(
23162                                        e,
23163                                        Expression::Literal(_)
23164                                            | Expression::Boolean(_)
23165                                            | Expression::Neg(_)
23166                                    )
23167                                }
23168                                let whens: Vec<(Expression, Expression)> = pairs
23169                                    .into_iter()
23170                                    .map(|(search, result)| {
23171                                        if matches!(&search, Expression::Null(_)) {
23172                                            // NULL search -> IS NULL
23173                                            let condition = Expression::Is(Box::new(BinaryOp {
23174                                                left: this_expr.clone(),
23175                                                right: Expression::Null(crate::expressions::Null),
23176                                                left_comments: Vec::new(),
23177                                                operator_comments: Vec::new(),
23178                                                trailing_comments: Vec::new(),
23179                                                inferred_type: None,
23180                                            }));
23181                                            (condition, result)
23182                                        } else if is_literal(&search) {
23183                                            // Literal search -> simple equality
23184                                            let eq = Expression::Eq(Box::new(BinaryOp {
23185                                                left: this_expr.clone(),
23186                                                right: search,
23187                                                left_comments: Vec::new(),
23188                                                operator_comments: Vec::new(),
23189                                                trailing_comments: Vec::new(),
23190                                                inferred_type: None,
23191                                            }));
23192                                            (eq, result)
23193                                        } else {
23194                                            // Non-literal (column ref, expression) -> null-safe comparison
23195                                            let needs_paren = matches!(
23196                                                &search,
23197                                                Expression::Eq(_)
23198                                                    | Expression::Neq(_)
23199                                                    | Expression::Gt(_)
23200                                                    | Expression::Gte(_)
23201                                                    | Expression::Lt(_)
23202                                                    | Expression::Lte(_)
23203                                            );
23204                                            let search_for_eq = if needs_paren {
23205                                                Expression::Paren(Box::new(
23206                                                    crate::expressions::Paren {
23207                                                        this: search.clone(),
23208                                                        trailing_comments: Vec::new(),
23209                                                    },
23210                                                ))
23211                                            } else {
23212                                                search.clone()
23213                                            };
23214                                            let eq = Expression::Eq(Box::new(BinaryOp {
23215                                                left: this_expr.clone(),
23216                                                right: search_for_eq,
23217                                                left_comments: Vec::new(),
23218                                                operator_comments: Vec::new(),
23219                                                trailing_comments: Vec::new(),
23220                                                inferred_type: None,
23221                                            }));
23222                                            let search_for_null = if needs_paren {
23223                                                Expression::Paren(Box::new(
23224                                                    crate::expressions::Paren {
23225                                                        this: search.clone(),
23226                                                        trailing_comments: Vec::new(),
23227                                                    },
23228                                                ))
23229                                            } else {
23230                                                search.clone()
23231                                            };
23232                                            let x_is_null = Expression::Is(Box::new(BinaryOp {
23233                                                left: this_expr.clone(),
23234                                                right: Expression::Null(crate::expressions::Null),
23235                                                left_comments: Vec::new(),
23236                                                operator_comments: Vec::new(),
23237                                                trailing_comments: Vec::new(),
23238                                                inferred_type: None,
23239                                            }));
23240                                            let s_is_null = Expression::Is(Box::new(BinaryOp {
23241                                                left: search_for_null,
23242                                                right: Expression::Null(crate::expressions::Null),
23243                                                left_comments: Vec::new(),
23244                                                operator_comments: Vec::new(),
23245                                                trailing_comments: Vec::new(),
23246                                                inferred_type: None,
23247                                            }));
23248                                            let both_null = Expression::And(Box::new(BinaryOp {
23249                                                left: x_is_null,
23250                                                right: s_is_null,
23251                                                left_comments: Vec::new(),
23252                                                operator_comments: Vec::new(),
23253                                                trailing_comments: Vec::new(),
23254                                                inferred_type: None,
23255                                            }));
23256                                            let condition = Expression::Or(Box::new(BinaryOp {
23257                                                left: eq,
23258                                                right: Expression::Paren(Box::new(
23259                                                    crate::expressions::Paren {
23260                                                        this: both_null,
23261                                                        trailing_comments: Vec::new(),
23262                                                    },
23263                                                )),
23264                                                left_comments: Vec::new(),
23265                                                operator_comments: Vec::new(),
23266                                                trailing_comments: Vec::new(),
23267                                                inferred_type: None,
23268                                            }));
23269                                            (condition, result)
23270                                        }
23271                                    })
23272                                    .collect();
23273                                Ok(Expression::Case(Box::new(Case {
23274                                    operand: None,
23275                                    whens,
23276                                    else_: default,
23277                                    comments: Vec::new(),
23278                                    inferred_type: None,
23279                                })))
23280                            }
23281                            // LEVENSHTEIN(a, b, ...) -> dialect-specific
23282                            "LEVENSHTEIN" => {
23283                                match target {
23284                                    DialectType::BigQuery => {
23285                                        let mut new_f = *f;
23286                                        new_f.name = "EDIT_DISTANCE".to_string();
23287                                        Ok(Expression::Function(Box::new(new_f)))
23288                                    }
23289                                    DialectType::Drill => {
23290                                        let mut new_f = *f;
23291                                        new_f.name = "LEVENSHTEIN_DISTANCE".to_string();
23292                                        Ok(Expression::Function(Box::new(new_f)))
23293                                    }
23294                                    DialectType::PostgreSQL if f.args.len() == 6 => {
23295                                        // PostgreSQL: LEVENSHTEIN(src, tgt, ins, del, sub, max_d) -> LEVENSHTEIN_LESS_EQUAL
23296                                        // 2 args: basic, 5 args: with costs, 6 args: with costs + max_distance
23297                                        let mut new_f = *f;
23298                                        new_f.name = "LEVENSHTEIN_LESS_EQUAL".to_string();
23299                                        Ok(Expression::Function(Box::new(new_f)))
23300                                    }
23301                                    _ => Ok(Expression::Function(f)),
23302                                }
23303                            }
23304                            // ARRAY_MAX(x) -> arrayMax(x) for ClickHouse, LIST_MAX(x) for DuckDB
23305                            "ARRAY_MAX" => {
23306                                let name = match target {
23307                                    DialectType::ClickHouse => "arrayMax",
23308                                    DialectType::DuckDB => "LIST_MAX",
23309                                    _ => "ARRAY_MAX",
23310                                };
23311                                let mut new_f = *f;
23312                                new_f.name = name.to_string();
23313                                Ok(Expression::Function(Box::new(new_f)))
23314                            }
23315                            // ARRAY_MIN(x) -> arrayMin(x) for ClickHouse, LIST_MIN(x) for DuckDB
23316                            "ARRAY_MIN" => {
23317                                let name = match target {
23318                                    DialectType::ClickHouse => "arrayMin",
23319                                    DialectType::DuckDB => "LIST_MIN",
23320                                    _ => "ARRAY_MIN",
23321                                };
23322                                let mut new_f = *f;
23323                                new_f.name = name.to_string();
23324                                Ok(Expression::Function(Box::new(new_f)))
23325                            }
23326                            // JAROWINKLER_SIMILARITY(a, b) -> jaroWinklerSimilarity(UPPER(a), UPPER(b)) for ClickHouse
23327                            // -> JARO_WINKLER_SIMILARITY(UPPER(a), UPPER(b)) for DuckDB
23328                            "JAROWINKLER_SIMILARITY" if f.args.len() == 2 => {
23329                                let mut args = f.args;
23330                                let b = args.pop().unwrap();
23331                                let a = args.pop().unwrap();
23332                                match target {
23333                                    DialectType::ClickHouse => {
23334                                        let upper_a = Expression::Upper(Box::new(
23335                                            crate::expressions::UnaryFunc::new(a),
23336                                        ));
23337                                        let upper_b = Expression::Upper(Box::new(
23338                                            crate::expressions::UnaryFunc::new(b),
23339                                        ));
23340                                        Ok(Expression::Function(Box::new(Function::new(
23341                                            "jaroWinklerSimilarity".to_string(),
23342                                            vec![upper_a, upper_b],
23343                                        ))))
23344                                    }
23345                                    DialectType::DuckDB => {
23346                                        let upper_a = Expression::Upper(Box::new(
23347                                            crate::expressions::UnaryFunc::new(a),
23348                                        ));
23349                                        let upper_b = Expression::Upper(Box::new(
23350                                            crate::expressions::UnaryFunc::new(b),
23351                                        ));
23352                                        let score = Expression::Function(Box::new(Function::new(
23353                                            "JARO_WINKLER_SIMILARITY".to_string(),
23354                                            vec![upper_a, upper_b],
23355                                        )));
23356                                        let scaled = Expression::Mul(Box::new(BinaryOp {
23357                                            left: score,
23358                                            right: Expression::number(100),
23359                                            left_comments: Vec::new(),
23360                                            operator_comments: Vec::new(),
23361                                            trailing_comments: Vec::new(),
23362                                            inferred_type: None,
23363                                        }));
23364                                        Ok(Expression::Cast(Box::new(Cast {
23365                                            this: scaled,
23366                                            to: DataType::Int {
23367                                                length: None,
23368                                                integer_spelling: false,
23369                                            },
23370                                            trailing_comments: Vec::new(),
23371                                            double_colon_syntax: false,
23372                                            format: None,
23373                                            default: None,
23374                                            inferred_type: None,
23375                                        })))
23376                                    }
23377                                    _ => Ok(Expression::Function(Box::new(Function::new(
23378                                        "JAROWINKLER_SIMILARITY".to_string(),
23379                                        vec![a, b],
23380                                    )))),
23381                                }
23382                            }
23383                            // CURRENT_SCHEMAS(x) -> CURRENT_SCHEMAS() for Snowflake (drop arg)
23384                            "CURRENT_SCHEMAS" => match target {
23385                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
23386                                    Function::new("CURRENT_SCHEMAS".to_string(), vec![]),
23387                                ))),
23388                                _ => Ok(Expression::Function(f)),
23389                            },
23390                            // TRUNC/TRUNCATE (numeric) -> dialect-specific
23391                            "TRUNC" | "TRUNCATE" if f.args.len() <= 2 => {
23392                                match target {
23393                                    DialectType::TSQL | DialectType::Fabric => {
23394                                        // ROUND(x, decimals, 1) - the 1 flag means truncation
23395                                        let mut args = f.args;
23396                                        let this = if args.is_empty() {
23397                                            return Ok(Expression::Function(Box::new(
23398                                                Function::new("TRUNC".to_string(), args),
23399                                            )));
23400                                        } else {
23401                                            args.remove(0)
23402                                        };
23403                                        let decimals = if args.is_empty() {
23404                                            Expression::Literal(Box::new(Literal::Number(
23405                                                "0".to_string(),
23406                                            )))
23407                                        } else {
23408                                            args.remove(0)
23409                                        };
23410                                        Ok(Expression::Function(Box::new(Function::new(
23411                                            "ROUND".to_string(),
23412                                            vec![
23413                                                this,
23414                                                decimals,
23415                                                Expression::Literal(Box::new(Literal::Number(
23416                                                    "1".to_string(),
23417                                                ))),
23418                                            ],
23419                                        ))))
23420                                    }
23421                                    DialectType::Presto
23422                                    | DialectType::Trino
23423                                    | DialectType::Athena => {
23424                                        // TRUNCATE(x, decimals)
23425                                        let mut new_f = *f;
23426                                        new_f.name = "TRUNCATE".to_string();
23427                                        Ok(Expression::Function(Box::new(new_f)))
23428                                    }
23429                                    DialectType::MySQL
23430                                    | DialectType::SingleStore
23431                                    | DialectType::TiDB => {
23432                                        // TRUNCATE(x, decimals)
23433                                        let mut new_f = *f;
23434                                        new_f.name = "TRUNCATE".to_string();
23435                                        Ok(Expression::Function(Box::new(new_f)))
23436                                    }
23437                                    DialectType::DuckDB => {
23438                                        // DuckDB supports TRUNC(x, decimals) — preserve both args
23439                                        let mut args = f.args;
23440                                        // Snowflake fractions_supported: wrap non-INT decimals in CAST(... AS INT)
23441                                        if args.len() == 2
23442                                            && matches!(source, DialectType::Snowflake)
23443                                        {
23444                                            let decimals = args.remove(1);
23445                                            let is_int = matches!(&decimals, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)))
23446                                                || matches!(&decimals, Expression::Cast(c) if matches!(c.to, DataType::Int { .. } | DataType::SmallInt { .. } | DataType::BigInt { .. } | DataType::TinyInt { .. }));
23447                                            let wrapped = if !is_int {
23448                                                Expression::Cast(Box::new(
23449                                                    crate::expressions::Cast {
23450                                                        this: decimals,
23451                                                        to: DataType::Int {
23452                                                            length: None,
23453                                                            integer_spelling: false,
23454                                                        },
23455                                                        double_colon_syntax: false,
23456                                                        trailing_comments: Vec::new(),
23457                                                        format: None,
23458                                                        default: None,
23459                                                        inferred_type: None,
23460                                                    },
23461                                                ))
23462                                            } else {
23463                                                decimals
23464                                            };
23465                                            args.push(wrapped);
23466                                        }
23467                                        Ok(Expression::Function(Box::new(Function::new(
23468                                            "TRUNC".to_string(),
23469                                            args,
23470                                        ))))
23471                                    }
23472                                    DialectType::ClickHouse => {
23473                                        // trunc(x, decimals) - lowercase
23474                                        let mut new_f = *f;
23475                                        new_f.name = "trunc".to_string();
23476                                        Ok(Expression::Function(Box::new(new_f)))
23477                                    }
23478                                    DialectType::Spark | DialectType::Databricks => {
23479                                        // Spark: TRUNC is date-only; numeric TRUNC → CAST(x AS BIGINT)
23480                                        let this = f.args.into_iter().next().unwrap_or(
23481                                            Expression::Literal(Box::new(Literal::Number(
23482                                                "0".to_string(),
23483                                            ))),
23484                                        );
23485                                        Ok(Expression::Cast(Box::new(crate::expressions::Cast {
23486                                            this,
23487                                            to: crate::expressions::DataType::BigInt {
23488                                                length: None,
23489                                            },
23490                                            double_colon_syntax: false,
23491                                            trailing_comments: Vec::new(),
23492                                            format: None,
23493                                            default: None,
23494                                            inferred_type: None,
23495                                        })))
23496                                    }
23497                                    _ => {
23498                                        // TRUNC(x, decimals) for PostgreSQL, Oracle, Snowflake, etc.
23499                                        let mut new_f = *f;
23500                                        new_f.name = "TRUNC".to_string();
23501                                        Ok(Expression::Function(Box::new(new_f)))
23502                                    }
23503                                }
23504                            }
23505                            // CURRENT_VERSION() -> VERSION() for most dialects
23506                            "CURRENT_VERSION" => match target {
23507                                DialectType::Snowflake
23508                                | DialectType::Databricks
23509                                | DialectType::StarRocks => Ok(Expression::Function(f)),
23510                                DialectType::SQLite => {
23511                                    let mut new_f = *f;
23512                                    new_f.name = "SQLITE_VERSION".to_string();
23513                                    Ok(Expression::Function(Box::new(new_f)))
23514                                }
23515                                _ => {
23516                                    let mut new_f = *f;
23517                                    new_f.name = "VERSION".to_string();
23518                                    Ok(Expression::Function(Box::new(new_f)))
23519                                }
23520                            },
23521                            // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
23522                            "ARRAY_REVERSE" => match target {
23523                                DialectType::ClickHouse => {
23524                                    let mut new_f = *f;
23525                                    new_f.name = "arrayReverse".to_string();
23526                                    Ok(Expression::Function(Box::new(new_f)))
23527                                }
23528                                _ => Ok(Expression::Function(f)),
23529                            },
23530                            // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
23531                            "GENERATE_DATE_ARRAY" => {
23532                                let mut args = f.args;
23533                                if matches!(target, DialectType::BigQuery) {
23534                                    // BigQuery keeps GENERATE_DATE_ARRAY; add default interval if not present
23535                                    if args.len() == 2 {
23536                                        let default_interval = Expression::Interval(Box::new(
23537                                            crate::expressions::Interval {
23538                                                this: Some(Expression::Literal(Box::new(
23539                                                    Literal::String("1".to_string()),
23540                                                ))),
23541                                                unit: Some(
23542                                                    crate::expressions::IntervalUnitSpec::Simple {
23543                                                        unit: crate::expressions::IntervalUnit::Day,
23544                                                        use_plural: false,
23545                                                    },
23546                                                ),
23547                                            },
23548                                        ));
23549                                        args.push(default_interval);
23550                                    }
23551                                    Ok(Expression::Function(Box::new(Function::new(
23552                                        "GENERATE_DATE_ARRAY".to_string(),
23553                                        args,
23554                                    ))))
23555                                } else if matches!(target, DialectType::DuckDB) {
23556                                    // DuckDB: CAST(GENERATE_SERIES(start, end, step) AS DATE[])
23557                                    let start = args.get(0).cloned();
23558                                    let end = args.get(1).cloned();
23559                                    let step = args.get(2).cloned().or_else(|| {
23560                                        Some(Expression::Interval(Box::new(
23561                                            crate::expressions::Interval {
23562                                                this: Some(Expression::Literal(Box::new(
23563                                                    Literal::String("1".to_string()),
23564                                                ))),
23565                                                unit: Some(
23566                                                    crate::expressions::IntervalUnitSpec::Simple {
23567                                                        unit: crate::expressions::IntervalUnit::Day,
23568                                                        use_plural: false,
23569                                                    },
23570                                                ),
23571                                            },
23572                                        )))
23573                                    });
23574                                    let gen_series = Expression::GenerateSeries(Box::new(
23575                                        crate::expressions::GenerateSeries {
23576                                            start: start.map(Box::new),
23577                                            end: end.map(Box::new),
23578                                            step: step.map(Box::new),
23579                                            is_end_exclusive: None,
23580                                        },
23581                                    ));
23582                                    Ok(Expression::Cast(Box::new(Cast {
23583                                        this: gen_series,
23584                                        to: DataType::Array {
23585                                            element_type: Box::new(DataType::Date),
23586                                            dimension: None,
23587                                        },
23588                                        trailing_comments: vec![],
23589                                        double_colon_syntax: false,
23590                                        format: None,
23591                                        default: None,
23592                                        inferred_type: None,
23593                                    })))
23594                                } else if matches!(
23595                                    target,
23596                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
23597                                ) {
23598                                    // Presto/Trino: SEQUENCE(start, end, interval) with interval normalization
23599                                    let start = args.get(0).cloned();
23600                                    let end = args.get(1).cloned();
23601                                    let step = args.get(2).cloned().or_else(|| {
23602                                        Some(Expression::Interval(Box::new(
23603                                            crate::expressions::Interval {
23604                                                this: Some(Expression::Literal(Box::new(
23605                                                    Literal::String("1".to_string()),
23606                                                ))),
23607                                                unit: Some(
23608                                                    crate::expressions::IntervalUnitSpec::Simple {
23609                                                        unit: crate::expressions::IntervalUnit::Day,
23610                                                        use_plural: false,
23611                                                    },
23612                                                ),
23613                                            },
23614                                        )))
23615                                    });
23616                                    let gen_series = Expression::GenerateSeries(Box::new(
23617                                        crate::expressions::GenerateSeries {
23618                                            start: start.map(Box::new),
23619                                            end: end.map(Box::new),
23620                                            step: step.map(Box::new),
23621                                            is_end_exclusive: None,
23622                                        },
23623                                    ));
23624                                    Ok(gen_series)
23625                                } else if matches!(
23626                                    target,
23627                                    DialectType::Spark | DialectType::Databricks
23628                                ) {
23629                                    // Spark/Databricks: SEQUENCE(start, end, step) - keep step as-is
23630                                    let start = args.get(0).cloned();
23631                                    let end = args.get(1).cloned();
23632                                    let step = args.get(2).cloned().or_else(|| {
23633                                        Some(Expression::Interval(Box::new(
23634                                            crate::expressions::Interval {
23635                                                this: Some(Expression::Literal(Box::new(
23636                                                    Literal::String("1".to_string()),
23637                                                ))),
23638                                                unit: Some(
23639                                                    crate::expressions::IntervalUnitSpec::Simple {
23640                                                        unit: crate::expressions::IntervalUnit::Day,
23641                                                        use_plural: false,
23642                                                    },
23643                                                ),
23644                                            },
23645                                        )))
23646                                    });
23647                                    let gen_series = Expression::GenerateSeries(Box::new(
23648                                        crate::expressions::GenerateSeries {
23649                                            start: start.map(Box::new),
23650                                            end: end.map(Box::new),
23651                                            step: step.map(Box::new),
23652                                            is_end_exclusive: None,
23653                                        },
23654                                    ));
23655                                    Ok(gen_series)
23656                                } else if matches!(target, DialectType::Snowflake) {
23657                                    // Snowflake: keep as GENERATE_DATE_ARRAY for later transform
23658                                    if args.len() == 2 {
23659                                        let default_interval = Expression::Interval(Box::new(
23660                                            crate::expressions::Interval {
23661                                                this: Some(Expression::Literal(Box::new(
23662                                                    Literal::String("1".to_string()),
23663                                                ))),
23664                                                unit: Some(
23665                                                    crate::expressions::IntervalUnitSpec::Simple {
23666                                                        unit: crate::expressions::IntervalUnit::Day,
23667                                                        use_plural: false,
23668                                                    },
23669                                                ),
23670                                            },
23671                                        ));
23672                                        args.push(default_interval);
23673                                    }
23674                                    Ok(Expression::Function(Box::new(Function::new(
23675                                        "GENERATE_DATE_ARRAY".to_string(),
23676                                        args,
23677                                    ))))
23678                                } else if matches!(
23679                                    target,
23680                                    DialectType::MySQL
23681                                        | DialectType::TSQL
23682                                        | DialectType::Fabric
23683                                        | DialectType::Redshift
23684                                ) {
23685                                    // MySQL/TSQL/Redshift: keep as GENERATE_DATE_ARRAY for the preprocess
23686                                    // step (unnest_generate_date_array_using_recursive_cte) to convert to CTE
23687                                    Ok(Expression::Function(Box::new(Function::new(
23688                                        "GENERATE_DATE_ARRAY".to_string(),
23689                                        args,
23690                                    ))))
23691                                } else {
23692                                    // PostgreSQL/others: convert to GenerateSeries
23693                                    let start = args.get(0).cloned();
23694                                    let end = args.get(1).cloned();
23695                                    let step = args.get(2).cloned().or_else(|| {
23696                                        Some(Expression::Interval(Box::new(
23697                                            crate::expressions::Interval {
23698                                                this: Some(Expression::Literal(Box::new(
23699                                                    Literal::String("1".to_string()),
23700                                                ))),
23701                                                unit: Some(
23702                                                    crate::expressions::IntervalUnitSpec::Simple {
23703                                                        unit: crate::expressions::IntervalUnit::Day,
23704                                                        use_plural: false,
23705                                                    },
23706                                                ),
23707                                            },
23708                                        )))
23709                                    });
23710                                    Ok(Expression::GenerateSeries(Box::new(
23711                                        crate::expressions::GenerateSeries {
23712                                            start: start.map(Box::new),
23713                                            end: end.map(Box::new),
23714                                            step: step.map(Box::new),
23715                                            is_end_exclusive: None,
23716                                        },
23717                                    )))
23718                                }
23719                            }
23720                            // ARRAYS_OVERLAP(arr1, arr2) from Snowflake -> DuckDB:
23721                            // (arr1 && arr2) OR (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
23722                            "ARRAYS_OVERLAP"
23723                                if f.args.len() == 2
23724                                    && matches!(source, DialectType::Snowflake)
23725                                    && matches!(target, DialectType::DuckDB) =>
23726                            {
23727                                let mut args = f.args;
23728                                let arr1 = args.remove(0);
23729                                let arr2 = args.remove(0);
23730
23731                                // (arr1 && arr2)
23732                                let overlap = Expression::Paren(Box::new(Paren {
23733                                    this: Expression::ArrayOverlaps(Box::new(BinaryOp {
23734                                        left: arr1.clone(),
23735                                        right: arr2.clone(),
23736                                        left_comments: vec![],
23737                                        operator_comments: vec![],
23738                                        trailing_comments: vec![],
23739                                        inferred_type: None,
23740                                    })),
23741                                    trailing_comments: vec![],
23742                                }));
23743
23744                                // ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1)
23745                                let arr1_has_null = Expression::Neq(Box::new(BinaryOp {
23746                                    left: Expression::Function(Box::new(Function::new(
23747                                        "ARRAY_LENGTH".to_string(),
23748                                        vec![arr1.clone()],
23749                                    ))),
23750                                    right: Expression::Function(Box::new(Function::new(
23751                                        "LIST_COUNT".to_string(),
23752                                        vec![arr1],
23753                                    ))),
23754                                    left_comments: vec![],
23755                                    operator_comments: vec![],
23756                                    trailing_comments: vec![],
23757                                    inferred_type: None,
23758                                }));
23759
23760                                // ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2)
23761                                let arr2_has_null = Expression::Neq(Box::new(BinaryOp {
23762                                    left: Expression::Function(Box::new(Function::new(
23763                                        "ARRAY_LENGTH".to_string(),
23764                                        vec![arr2.clone()],
23765                                    ))),
23766                                    right: Expression::Function(Box::new(Function::new(
23767                                        "LIST_COUNT".to_string(),
23768                                        vec![arr2],
23769                                    ))),
23770                                    left_comments: vec![],
23771                                    operator_comments: vec![],
23772                                    trailing_comments: vec![],
23773                                    inferred_type: None,
23774                                }));
23775
23776                                // (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
23777                                let null_check = Expression::Paren(Box::new(Paren {
23778                                    this: Expression::And(Box::new(BinaryOp {
23779                                        left: arr1_has_null,
23780                                        right: arr2_has_null,
23781                                        left_comments: vec![],
23782                                        operator_comments: vec![],
23783                                        trailing_comments: vec![],
23784                                        inferred_type: None,
23785                                    })),
23786                                    trailing_comments: vec![],
23787                                }));
23788
23789                                // (arr1 && arr2) OR (null_check)
23790                                Ok(Expression::Or(Box::new(BinaryOp {
23791                                    left: overlap,
23792                                    right: null_check,
23793                                    left_comments: vec![],
23794                                    operator_comments: vec![],
23795                                    trailing_comments: vec![],
23796                                    inferred_type: None,
23797                                })))
23798                            }
23799                            // ARRAY_INTERSECTION([1, 2], [2, 3]) from Snowflake -> DuckDB:
23800                            // Bag semantics using LIST_TRANSFORM/LIST_FILTER with GENERATE_SERIES
23801                            "ARRAY_INTERSECTION"
23802                                if f.args.len() == 2
23803                                    && matches!(source, DialectType::Snowflake)
23804                                    && matches!(target, DialectType::DuckDB) =>
23805                            {
23806                                let mut args = f.args;
23807                                let arr1 = args.remove(0);
23808                                let arr2 = args.remove(0);
23809
23810                                // Build: arr1 IS NULL
23811                                let arr1_is_null = Expression::IsNull(Box::new(IsNull {
23812                                    this: arr1.clone(),
23813                                    not: false,
23814                                    postfix_form: false,
23815                                }));
23816                                let arr2_is_null = Expression::IsNull(Box::new(IsNull {
23817                                    this: arr2.clone(),
23818                                    not: false,
23819                                    postfix_form: false,
23820                                }));
23821                                let null_check = Expression::Or(Box::new(BinaryOp {
23822                                    left: arr1_is_null,
23823                                    right: arr2_is_null,
23824                                    left_comments: vec![],
23825                                    operator_comments: vec![],
23826                                    trailing_comments: vec![],
23827                                    inferred_type: None,
23828                                }));
23829
23830                                // GENERATE_SERIES(1, LENGTH(arr1))
23831                                let gen_series = Expression::Function(Box::new(Function::new(
23832                                    "GENERATE_SERIES".to_string(),
23833                                    vec![
23834                                        Expression::number(1),
23835                                        Expression::Function(Box::new(Function::new(
23836                                            "LENGTH".to_string(),
23837                                            vec![arr1.clone()],
23838                                        ))),
23839                                    ],
23840                                )));
23841
23842                                // LIST_ZIP(arr1, GENERATE_SERIES(1, LENGTH(arr1)))
23843                                let list_zip = Expression::Function(Box::new(Function::new(
23844                                    "LIST_ZIP".to_string(),
23845                                    vec![arr1.clone(), gen_series],
23846                                )));
23847
23848                                // pair[1] and pair[2]
23849                                let pair_col = Expression::column("pair");
23850                                let pair_1 = Expression::Subscript(Box::new(
23851                                    crate::expressions::Subscript {
23852                                        this: pair_col.clone(),
23853                                        index: Expression::number(1),
23854                                    },
23855                                ));
23856                                let pair_2 = Expression::Subscript(Box::new(
23857                                    crate::expressions::Subscript {
23858                                        this: pair_col.clone(),
23859                                        index: Expression::number(2),
23860                                    },
23861                                ));
23862
23863                                // arr1[1:pair[2]]
23864                                let arr1_slice = Expression::ArraySlice(Box::new(
23865                                    crate::expressions::ArraySlice {
23866                                        this: arr1.clone(),
23867                                        start: Some(Expression::number(1)),
23868                                        end: Some(pair_2),
23869                                    },
23870                                ));
23871
23872                                // e IS NOT DISTINCT FROM pair[1]
23873                                let e_col = Expression::column("e");
23874                                let is_not_distinct = Expression::NullSafeEq(Box::new(BinaryOp {
23875                                    left: e_col.clone(),
23876                                    right: pair_1.clone(),
23877                                    left_comments: vec![],
23878                                    operator_comments: vec![],
23879                                    trailing_comments: vec![],
23880                                    inferred_type: None,
23881                                }));
23882
23883                                // e -> e IS NOT DISTINCT FROM pair[1]
23884                                let inner_lambda1 =
23885                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
23886                                        parameters: vec![crate::expressions::Identifier::new("e")],
23887                                        body: is_not_distinct,
23888                                        colon: false,
23889                                        parameter_types: vec![],
23890                                    }));
23891
23892                                // LIST_FILTER(arr1[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
23893                                let inner_filter1 = Expression::Function(Box::new(Function::new(
23894                                    "LIST_FILTER".to_string(),
23895                                    vec![arr1_slice, inner_lambda1],
23896                                )));
23897
23898                                // LENGTH(LIST_FILTER(arr1[1:pair[2]], ...))
23899                                let len1 = Expression::Function(Box::new(Function::new(
23900                                    "LENGTH".to_string(),
23901                                    vec![inner_filter1],
23902                                )));
23903
23904                                // e -> e IS NOT DISTINCT FROM pair[1]
23905                                let inner_lambda2 =
23906                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
23907                                        parameters: vec![crate::expressions::Identifier::new("e")],
23908                                        body: Expression::NullSafeEq(Box::new(BinaryOp {
23909                                            left: e_col,
23910                                            right: pair_1.clone(),
23911                                            left_comments: vec![],
23912                                            operator_comments: vec![],
23913                                            trailing_comments: vec![],
23914                                            inferred_type: None,
23915                                        })),
23916                                        colon: false,
23917                                        parameter_types: vec![],
23918                                    }));
23919
23920                                // LIST_FILTER(arr2, e -> e IS NOT DISTINCT FROM pair[1])
23921                                let inner_filter2 = Expression::Function(Box::new(Function::new(
23922                                    "LIST_FILTER".to_string(),
23923                                    vec![arr2.clone(), inner_lambda2],
23924                                )));
23925
23926                                // LENGTH(LIST_FILTER(arr2, ...))
23927                                let len2 = Expression::Function(Box::new(Function::new(
23928                                    "LENGTH".to_string(),
23929                                    vec![inner_filter2],
23930                                )));
23931
23932                                // LENGTH(...) <= LENGTH(...)
23933                                let cond = Expression::Paren(Box::new(Paren {
23934                                    this: Expression::Lte(Box::new(BinaryOp {
23935                                        left: len1,
23936                                        right: len2,
23937                                        left_comments: vec![],
23938                                        operator_comments: vec![],
23939                                        trailing_comments: vec![],
23940                                        inferred_type: None,
23941                                    })),
23942                                    trailing_comments: vec![],
23943                                }));
23944
23945                                // pair -> (condition)
23946                                let filter_lambda =
23947                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
23948                                        parameters: vec![crate::expressions::Identifier::new(
23949                                            "pair",
23950                                        )],
23951                                        body: cond,
23952                                        colon: false,
23953                                        parameter_types: vec![],
23954                                    }));
23955
23956                                // LIST_FILTER(LIST_ZIP(...), pair -> ...)
23957                                let outer_filter = Expression::Function(Box::new(Function::new(
23958                                    "LIST_FILTER".to_string(),
23959                                    vec![list_zip, filter_lambda],
23960                                )));
23961
23962                                // pair -> pair[1]
23963                                let transform_lambda =
23964                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
23965                                        parameters: vec![crate::expressions::Identifier::new(
23966                                            "pair",
23967                                        )],
23968                                        body: pair_1,
23969                                        colon: false,
23970                                        parameter_types: vec![],
23971                                    }));
23972
23973                                // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
23974                                let list_transform = Expression::Function(Box::new(Function::new(
23975                                    "LIST_TRANSFORM".to_string(),
23976                                    vec![outer_filter, transform_lambda],
23977                                )));
23978
23979                                // CASE WHEN arr1 IS NULL OR arr2 IS NULL THEN NULL
23980                                // ELSE LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
23981                                // END
23982                                Ok(Expression::Case(Box::new(Case {
23983                                    operand: None,
23984                                    whens: vec![(null_check, Expression::Null(Null))],
23985                                    else_: Some(list_transform),
23986                                    comments: vec![],
23987                                    inferred_type: None,
23988                                })))
23989                            }
23990                            // ARRAY_CONSTRUCT(args) -> Expression::Array for all targets
23991                            "ARRAY_CONSTRUCT" => {
23992                                if matches!(target, DialectType::Snowflake) {
23993                                    Ok(Expression::Function(f))
23994                                } else {
23995                                    Ok(Expression::Array(Box::new(crate::expressions::Array {
23996                                        expressions: f.args,
23997                                    })))
23998                                }
23999                            }
24000                            // ARRAY(args) function -> Expression::Array for DuckDB/Snowflake/Presto/Trino/Athena
24001                            "ARRAY"
24002                                if !f.args.iter().any(|a| {
24003                                    matches!(a, Expression::Select(_) | Expression::Subquery(_))
24004                                }) =>
24005                            {
24006                                match target {
24007                                    DialectType::DuckDB
24008                                    | DialectType::Snowflake
24009                                    | DialectType::Presto
24010                                    | DialectType::Trino
24011                                    | DialectType::Athena => {
24012                                        Ok(Expression::Array(Box::new(crate::expressions::Array {
24013                                            expressions: f.args,
24014                                        })))
24015                                    }
24016                                    _ => Ok(Expression::Function(f)),
24017                                }
24018                            }
24019                            _ => Ok(Expression::Function(f)),
24020                        }
24021                    } else if let Expression::AggregateFunction(mut af) = e {
24022                        let name = af.name.to_ascii_uppercase();
24023                        match name.as_str() {
24024                            "ARBITRARY" if af.args.len() == 1 => {
24025                                let arg = af.args.into_iter().next().unwrap();
24026                                Ok(convert_arbitrary(arg, target))
24027                            }
24028                            "JSON_ARRAYAGG" => {
24029                                match target {
24030                                    DialectType::PostgreSQL => {
24031                                        af.name = "JSON_AGG".to_string();
24032                                        // Add NULLS FIRST to ORDER BY items for PostgreSQL
24033                                        for ordered in af.order_by.iter_mut() {
24034                                            if ordered.nulls_first.is_none() {
24035                                                ordered.nulls_first = Some(true);
24036                                            }
24037                                        }
24038                                        Ok(Expression::AggregateFunction(af))
24039                                    }
24040                                    _ => Ok(Expression::AggregateFunction(af)),
24041                                }
24042                            }
24043                            _ => Ok(Expression::AggregateFunction(af)),
24044                        }
24045                    } else if let Expression::JSONArrayAgg(ja) = e {
24046                        // JSONArrayAgg -> JSON_AGG for PostgreSQL, JSON_ARRAYAGG for others
24047                        match target {
24048                            DialectType::PostgreSQL => {
24049                                let mut order_by = Vec::new();
24050                                if let Some(order_expr) = ja.order {
24051                                    if let Expression::OrderBy(ob) = *order_expr {
24052                                        for mut ordered in ob.expressions {
24053                                            if ordered.nulls_first.is_none() {
24054                                                ordered.nulls_first = Some(true);
24055                                            }
24056                                            order_by.push(ordered);
24057                                        }
24058                                    }
24059                                }
24060                                Ok(Expression::AggregateFunction(Box::new(
24061                                    crate::expressions::AggregateFunction {
24062                                        name: "JSON_AGG".to_string(),
24063                                        args: vec![*ja.this],
24064                                        distinct: false,
24065                                        filter: None,
24066                                        order_by,
24067                                        limit: None,
24068                                        ignore_nulls: None,
24069                                        inferred_type: None,
24070                                    },
24071                                )))
24072                            }
24073                            _ => Ok(Expression::JSONArrayAgg(ja)),
24074                        }
24075                    } else if let Expression::JSONArray(ja) = e {
24076                        match target {
24077                            DialectType::Snowflake
24078                                if ja.null_handling.is_none()
24079                                    && ja.return_type.is_none()
24080                                    && ja.strict.is_none() =>
24081                            {
24082                                let array_construct = Expression::ArrayFunc(Box::new(
24083                                    crate::expressions::ArrayConstructor {
24084                                        expressions: ja.expressions,
24085                                        bracket_notation: false,
24086                                        use_list_keyword: false,
24087                                    },
24088                                ));
24089                                Ok(Expression::Function(Box::new(Function::new(
24090                                    "TO_VARIANT".to_string(),
24091                                    vec![array_construct],
24092                                ))))
24093                            }
24094                            _ => Ok(Expression::JSONArray(ja)),
24095                        }
24096                    } else if let Expression::JsonArray(f) = e {
24097                        match target {
24098                            DialectType::Snowflake => {
24099                                let array_construct = Expression::ArrayFunc(Box::new(
24100                                    crate::expressions::ArrayConstructor {
24101                                        expressions: f.expressions,
24102                                        bracket_notation: false,
24103                                        use_list_keyword: false,
24104                                    },
24105                                ));
24106                                Ok(Expression::Function(Box::new(Function::new(
24107                                    "TO_VARIANT".to_string(),
24108                                    vec![array_construct],
24109                                ))))
24110                            }
24111                            _ => Ok(Expression::JsonArray(f)),
24112                        }
24113                    } else if let Expression::CombinedParameterizedAgg(cpa) = e {
24114                        let function_name = match cpa.this.as_ref() {
24115                            Expression::Identifier(ident) => Some(ident.name.as_str()),
24116                            _ => None,
24117                        };
24118                        match function_name {
24119                            Some(name)
24120                                if name.eq_ignore_ascii_case("groupConcat")
24121                                    && cpa.expressions.len() == 1 =>
24122                            {
24123                                match target {
24124                                    DialectType::MySQL | DialectType::SingleStore => {
24125                                        let this = cpa.expressions[0].clone();
24126                                        let separator = cpa.params.first().cloned();
24127                                        Ok(Expression::GroupConcat(Box::new(
24128                                            crate::expressions::GroupConcatFunc {
24129                                                this,
24130                                                separator,
24131                                                order_by: None,
24132                                                distinct: false,
24133                                                filter: None,
24134                                                limit: None,
24135                                                inferred_type: None,
24136                                            },
24137                                        )))
24138                                    }
24139                                    DialectType::DuckDB => Ok(Expression::ListAgg(Box::new({
24140                                        let this = cpa.expressions[0].clone();
24141                                        let separator = cpa.params.first().cloned();
24142                                        crate::expressions::ListAggFunc {
24143                                            this,
24144                                            separator,
24145                                            on_overflow: None,
24146                                            order_by: None,
24147                                            distinct: false,
24148                                            filter: None,
24149                                            inferred_type: None,
24150                                        }
24151                                    }))),
24152                                    _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
24153                                }
24154                            }
24155                            _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
24156                        }
24157                    } else if let Expression::ToNumber(tn) = e {
24158                        // TO_NUMBER(x) with no format/precision/scale -> CAST(x AS DOUBLE)
24159                        let arg = *tn.this;
24160                        Ok(Expression::Cast(Box::new(crate::expressions::Cast {
24161                            this: arg,
24162                            to: crate::expressions::DataType::Double {
24163                                precision: None,
24164                                scale: None,
24165                            },
24166                            double_colon_syntax: false,
24167                            trailing_comments: Vec::new(),
24168                            format: None,
24169                            default: None,
24170                            inferred_type: None,
24171                        })))
24172                    } else {
24173                        Ok(e)
24174                    }
24175                }
24176
24177                Action::RegexpLikeToDuckDB => {
24178                    if let Expression::RegexpLike(f) = e {
24179                        let mut args = vec![f.this, f.pattern];
24180                        if let Some(flags) = f.flags {
24181                            args.push(flags);
24182                        }
24183                        Ok(Expression::Function(Box::new(Function::new(
24184                            "REGEXP_MATCHES".to_string(),
24185                            args,
24186                        ))))
24187                    } else {
24188                        Ok(e)
24189                    }
24190                }
24191                Action::EpochConvert => {
24192                    if let Expression::Epoch(f) = e {
24193                        let arg = f.this;
24194                        let name = match target {
24195                            DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
24196                                "UNIX_TIMESTAMP"
24197                            }
24198                            DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
24199                            DialectType::BigQuery => "TIME_TO_UNIX",
24200                            _ => "EPOCH",
24201                        };
24202                        Ok(Expression::Function(Box::new(Function::new(
24203                            name.to_string(),
24204                            vec![arg],
24205                        ))))
24206                    } else {
24207                        Ok(e)
24208                    }
24209                }
24210                Action::EpochMsConvert => {
24211                    use crate::expressions::{BinaryOp, Cast};
24212                    if let Expression::EpochMs(f) = e {
24213                        let arg = f.this;
24214                        match target {
24215                            DialectType::Spark | DialectType::Databricks => {
24216                                Ok(Expression::Function(Box::new(Function::new(
24217                                    "TIMESTAMP_MILLIS".to_string(),
24218                                    vec![arg],
24219                                ))))
24220                            }
24221                            DialectType::BigQuery => Ok(Expression::Function(Box::new(
24222                                Function::new("TIMESTAMP_MILLIS".to_string(), vec![arg]),
24223                            ))),
24224                            DialectType::Presto | DialectType::Trino => {
24225                                // FROM_UNIXTIME(CAST(x AS DOUBLE) / POW(10, 3))
24226                                let cast_arg = Expression::Cast(Box::new(Cast {
24227                                    this: arg,
24228                                    to: DataType::Double {
24229                                        precision: None,
24230                                        scale: None,
24231                                    },
24232                                    trailing_comments: Vec::new(),
24233                                    double_colon_syntax: false,
24234                                    format: None,
24235                                    default: None,
24236                                    inferred_type: None,
24237                                }));
24238                                let div = Expression::Div(Box::new(BinaryOp::new(
24239                                    cast_arg,
24240                                    Expression::Function(Box::new(Function::new(
24241                                        "POW".to_string(),
24242                                        vec![Expression::number(10), Expression::number(3)],
24243                                    ))),
24244                                )));
24245                                Ok(Expression::Function(Box::new(Function::new(
24246                                    "FROM_UNIXTIME".to_string(),
24247                                    vec![div],
24248                                ))))
24249                            }
24250                            DialectType::MySQL => {
24251                                // FROM_UNIXTIME(x / POWER(10, 3))
24252                                let div = Expression::Div(Box::new(BinaryOp::new(
24253                                    arg,
24254                                    Expression::Function(Box::new(Function::new(
24255                                        "POWER".to_string(),
24256                                        vec![Expression::number(10), Expression::number(3)],
24257                                    ))),
24258                                )));
24259                                Ok(Expression::Function(Box::new(Function::new(
24260                                    "FROM_UNIXTIME".to_string(),
24261                                    vec![div],
24262                                ))))
24263                            }
24264                            DialectType::PostgreSQL | DialectType::Redshift => {
24265                                // TO_TIMESTAMP(CAST(x AS DOUBLE PRECISION) / POWER(10, 3))
24266                                let cast_arg = Expression::Cast(Box::new(Cast {
24267                                    this: arg,
24268                                    to: DataType::Custom {
24269                                        name: "DOUBLE PRECISION".to_string(),
24270                                    },
24271                                    trailing_comments: Vec::new(),
24272                                    double_colon_syntax: false,
24273                                    format: None,
24274                                    default: None,
24275                                    inferred_type: None,
24276                                }));
24277                                let div = Expression::Div(Box::new(BinaryOp::new(
24278                                    cast_arg,
24279                                    Expression::Function(Box::new(Function::new(
24280                                        "POWER".to_string(),
24281                                        vec![Expression::number(10), Expression::number(3)],
24282                                    ))),
24283                                )));
24284                                Ok(Expression::Function(Box::new(Function::new(
24285                                    "TO_TIMESTAMP".to_string(),
24286                                    vec![div],
24287                                ))))
24288                            }
24289                            DialectType::ClickHouse => {
24290                                // fromUnixTimestamp64Milli(CAST(x AS Nullable(Int64)))
24291                                let cast_arg = Expression::Cast(Box::new(Cast {
24292                                    this: arg,
24293                                    to: DataType::Nullable {
24294                                        inner: Box::new(DataType::BigInt { length: None }),
24295                                    },
24296                                    trailing_comments: Vec::new(),
24297                                    double_colon_syntax: false,
24298                                    format: None,
24299                                    default: None,
24300                                    inferred_type: None,
24301                                }));
24302                                Ok(Expression::Function(Box::new(Function::new(
24303                                    "fromUnixTimestamp64Milli".to_string(),
24304                                    vec![cast_arg],
24305                                ))))
24306                            }
24307                            _ => Ok(Expression::Function(Box::new(Function::new(
24308                                "EPOCH_MS".to_string(),
24309                                vec![arg],
24310                            )))),
24311                        }
24312                    } else {
24313                        Ok(e)
24314                    }
24315                }
24316                Action::TSQLTypeNormalize => {
24317                    if let Expression::DataType(dt) = e {
24318                        let new_dt = match &dt {
24319                            DataType::Custom { name } if name.eq_ignore_ascii_case("MONEY") => {
24320                                DataType::Decimal {
24321                                    precision: Some(15),
24322                                    scale: Some(4),
24323                                }
24324                            }
24325                            DataType::Custom { name }
24326                                if name.eq_ignore_ascii_case("SMALLMONEY") =>
24327                            {
24328                                DataType::Decimal {
24329                                    precision: Some(6),
24330                                    scale: Some(4),
24331                                }
24332                            }
24333                            DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME2") => {
24334                                DataType::Timestamp {
24335                                    timezone: false,
24336                                    precision: None,
24337                                }
24338                            }
24339                            DataType::Custom { name } if name.eq_ignore_ascii_case("REAL") => {
24340                                DataType::Float {
24341                                    precision: None,
24342                                    scale: None,
24343                                    real_spelling: false,
24344                                }
24345                            }
24346                            DataType::Float {
24347                                real_spelling: true,
24348                                ..
24349                            } => DataType::Float {
24350                                precision: None,
24351                                scale: None,
24352                                real_spelling: false,
24353                            },
24354                            DataType::Custom { name } if name.eq_ignore_ascii_case("IMAGE") => {
24355                                DataType::Custom {
24356                                    name: "BLOB".to_string(),
24357                                }
24358                            }
24359                            DataType::Custom { name } if name.eq_ignore_ascii_case("BIT") => {
24360                                DataType::Boolean
24361                            }
24362                            DataType::Custom { name }
24363                                if name.eq_ignore_ascii_case("ROWVERSION") =>
24364                            {
24365                                DataType::Custom {
24366                                    name: "BINARY".to_string(),
24367                                }
24368                            }
24369                            DataType::Custom { name }
24370                                if name.eq_ignore_ascii_case("UNIQUEIDENTIFIER") =>
24371                            {
24372                                match target {
24373                                    DialectType::Spark
24374                                    | DialectType::Databricks
24375                                    | DialectType::Hive => DataType::Custom {
24376                                        name: "STRING".to_string(),
24377                                    },
24378                                    _ => DataType::VarChar {
24379                                        length: Some(36),
24380                                        parenthesized_length: true,
24381                                    },
24382                                }
24383                            }
24384                            DataType::Custom { name }
24385                                if name.eq_ignore_ascii_case("DATETIMEOFFSET") =>
24386                            {
24387                                match target {
24388                                    DialectType::Spark
24389                                    | DialectType::Databricks
24390                                    | DialectType::Hive => DataType::Timestamp {
24391                                        timezone: false,
24392                                        precision: None,
24393                                    },
24394                                    _ => DataType::Timestamp {
24395                                        timezone: true,
24396                                        precision: None,
24397                                    },
24398                                }
24399                            }
24400                            DataType::Custom { ref name }
24401                                if name.len() >= 10
24402                                    && name[..10].eq_ignore_ascii_case("DATETIME2(") =>
24403                            {
24404                                // DATETIME2(n) -> TIMESTAMP
24405                                DataType::Timestamp {
24406                                    timezone: false,
24407                                    precision: None,
24408                                }
24409                            }
24410                            DataType::Custom { ref name }
24411                                if name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME(") =>
24412                            {
24413                                // TIME(n) -> TIMESTAMP for Spark, keep as TIME for others
24414                                match target {
24415                                    DialectType::Spark
24416                                    | DialectType::Databricks
24417                                    | DialectType::Hive => DataType::Timestamp {
24418                                        timezone: false,
24419                                        precision: None,
24420                                    },
24421                                    _ => return Ok(Expression::DataType(dt)),
24422                                }
24423                            }
24424                            DataType::Custom { ref name }
24425                                if name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC") =>
24426                            {
24427                                // Parse NUMERIC(p,s) back to Decimal(p,s)
24428                                let upper = name.to_ascii_uppercase();
24429                                if let Some(inner) = upper
24430                                    .strip_prefix("NUMERIC(")
24431                                    .and_then(|s| s.strip_suffix(')'))
24432                                {
24433                                    let parts: Vec<&str> = inner.split(',').collect();
24434                                    let precision =
24435                                        parts.first().and_then(|s| s.trim().parse::<u32>().ok());
24436                                    let scale =
24437                                        parts.get(1).and_then(|s| s.trim().parse::<u32>().ok());
24438                                    DataType::Decimal { precision, scale }
24439                                } else if upper == "NUMERIC" {
24440                                    DataType::Decimal {
24441                                        precision: None,
24442                                        scale: None,
24443                                    }
24444                                } else {
24445                                    return Ok(Expression::DataType(dt));
24446                                }
24447                            }
24448                            DataType::Float {
24449                                precision: Some(p), ..
24450                            } => {
24451                                // For Hive/Spark: FLOAT(1-32) -> FLOAT, FLOAT(33+) -> DOUBLE (IEEE 754 boundary)
24452                                // For other targets: FLOAT(1-24) -> FLOAT, FLOAT(25+) -> DOUBLE (TSQL boundary)
24453                                let boundary = match target {
24454                                    DialectType::Hive
24455                                    | DialectType::Spark
24456                                    | DialectType::Databricks => 32,
24457                                    _ => 24,
24458                                };
24459                                if *p <= boundary {
24460                                    DataType::Float {
24461                                        precision: None,
24462                                        scale: None,
24463                                        real_spelling: false,
24464                                    }
24465                                } else {
24466                                    DataType::Double {
24467                                        precision: None,
24468                                        scale: None,
24469                                    }
24470                                }
24471                            }
24472                            DataType::TinyInt { .. } => match target {
24473                                DialectType::DuckDB => DataType::Custom {
24474                                    name: "UTINYINT".to_string(),
24475                                },
24476                                DialectType::Hive
24477                                | DialectType::Spark
24478                                | DialectType::Databricks => DataType::SmallInt { length: None },
24479                                _ => return Ok(Expression::DataType(dt)),
24480                            },
24481                            // INTEGER -> INT for Spark/Databricks
24482                            DataType::Int {
24483                                length,
24484                                integer_spelling: true,
24485                            } => DataType::Int {
24486                                length: *length,
24487                                integer_spelling: false,
24488                            },
24489                            _ => return Ok(Expression::DataType(dt)),
24490                        };
24491                        Ok(Expression::DataType(new_dt))
24492                    } else {
24493                        Ok(e)
24494                    }
24495                }
24496                Action::MySQLSafeDivide => {
24497                    use crate::expressions::{BinaryOp, Cast};
24498                    if let Expression::Div(op) = e {
24499                        let left = op.left;
24500                        let right = op.right;
24501                        // For SQLite: CAST left as REAL but NO NULLIF wrapping
24502                        if matches!(target, DialectType::SQLite) {
24503                            let new_left = Expression::Cast(Box::new(Cast {
24504                                this: left,
24505                                to: DataType::Float {
24506                                    precision: None,
24507                                    scale: None,
24508                                    real_spelling: true,
24509                                },
24510                                trailing_comments: Vec::new(),
24511                                double_colon_syntax: false,
24512                                format: None,
24513                                default: None,
24514                                inferred_type: None,
24515                            }));
24516                            return Ok(Expression::Div(Box::new(BinaryOp::new(new_left, right))));
24517                        }
24518                        // Wrap right in NULLIF(right, 0)
24519                        let nullif_right = Expression::Function(Box::new(Function::new(
24520                            "NULLIF".to_string(),
24521                            vec![right, Expression::number(0)],
24522                        )));
24523                        // For some dialects, also CAST the left side
24524                        let new_left = match target {
24525                            DialectType::PostgreSQL
24526                            | DialectType::Redshift
24527                            | DialectType::Teradata
24528                            | DialectType::Materialize
24529                            | DialectType::RisingWave => Expression::Cast(Box::new(Cast {
24530                                this: left,
24531                                to: DataType::Custom {
24532                                    name: "DOUBLE PRECISION".to_string(),
24533                                },
24534                                trailing_comments: Vec::new(),
24535                                double_colon_syntax: false,
24536                                format: None,
24537                                default: None,
24538                                inferred_type: None,
24539                            })),
24540                            DialectType::Drill
24541                            | DialectType::Trino
24542                            | DialectType::Presto
24543                            | DialectType::Athena => Expression::Cast(Box::new(Cast {
24544                                this: left,
24545                                to: DataType::Double {
24546                                    precision: None,
24547                                    scale: None,
24548                                },
24549                                trailing_comments: Vec::new(),
24550                                double_colon_syntax: false,
24551                                format: None,
24552                                default: None,
24553                                inferred_type: None,
24554                            })),
24555                            DialectType::TSQL => Expression::Cast(Box::new(Cast {
24556                                this: left,
24557                                to: DataType::Float {
24558                                    precision: None,
24559                                    scale: None,
24560                                    real_spelling: false,
24561                                },
24562                                trailing_comments: Vec::new(),
24563                                double_colon_syntax: false,
24564                                format: None,
24565                                default: None,
24566                                inferred_type: None,
24567                            })),
24568                            _ => left,
24569                        };
24570                        Ok(Expression::Div(Box::new(BinaryOp::new(
24571                            new_left,
24572                            nullif_right,
24573                        ))))
24574                    } else {
24575                        Ok(e)
24576                    }
24577                }
24578                Action::AlterTableRenameStripSchema => {
24579                    if let Expression::AlterTable(mut at) = e {
24580                        if let Some(crate::expressions::AlterTableAction::RenameTable(
24581                            ref mut new_tbl,
24582                        )) = at.actions.first_mut()
24583                        {
24584                            new_tbl.schema = None;
24585                            new_tbl.catalog = None;
24586                        }
24587                        Ok(Expression::AlterTable(at))
24588                    } else {
24589                        Ok(e)
24590                    }
24591                }
24592                Action::NullsOrdering => {
24593                    // Fill in the source dialect's implied null ordering default.
24594                    // This makes implicit null ordering explicit so the target generator
24595                    // can correctly strip or keep it.
24596                    //
24597                    // Dialect null ordering categories:
24598                    // nulls_are_large (Oracle, PostgreSQL, Redshift, Snowflake):
24599                    //   ASC -> NULLS LAST, DESC -> NULLS FIRST
24600                    // nulls_are_small (Spark, Hive, BigQuery, MySQL, Databricks, ClickHouse, etc.):
24601                    //   ASC -> NULLS FIRST, DESC -> NULLS LAST
24602                    // nulls_are_last (DuckDB, Presto, Trino, Dremio, Athena):
24603                    //   NULLS LAST always (both ASC and DESC)
24604                    if let Expression::Ordered(mut o) = e {
24605                        let is_asc = !o.desc;
24606
24607                        let is_source_nulls_large = matches!(
24608                            source,
24609                            DialectType::Oracle
24610                                | DialectType::PostgreSQL
24611                                | DialectType::Redshift
24612                                | DialectType::Snowflake
24613                        );
24614                        let is_source_nulls_last = matches!(
24615                            source,
24616                            DialectType::DuckDB
24617                                | DialectType::Presto
24618                                | DialectType::Trino
24619                                | DialectType::Dremio
24620                                | DialectType::Athena
24621                                | DialectType::ClickHouse
24622                                | DialectType::Drill
24623                                | DialectType::Exasol
24624                                | DialectType::DataFusion
24625                        );
24626
24627                        // Determine target category to check if default matches
24628                        let is_target_nulls_large = matches!(
24629                            target,
24630                            DialectType::Oracle
24631                                | DialectType::PostgreSQL
24632                                | DialectType::Redshift
24633                                | DialectType::Snowflake
24634                        );
24635                        let is_target_nulls_last = matches!(
24636                            target,
24637                            DialectType::DuckDB
24638                                | DialectType::Presto
24639                                | DialectType::Trino
24640                                | DialectType::Dremio
24641                                | DialectType::Athena
24642                                | DialectType::ClickHouse
24643                                | DialectType::Drill
24644                                | DialectType::Exasol
24645                                | DialectType::DataFusion
24646                        );
24647
24648                        // Compute the implied nulls_first for source
24649                        let source_nulls_first = if is_source_nulls_large {
24650                            !is_asc // ASC -> NULLS LAST (false), DESC -> NULLS FIRST (true)
24651                        } else if is_source_nulls_last {
24652                            false // NULLS LAST always
24653                        } else {
24654                            is_asc // nulls_are_small: ASC -> NULLS FIRST (true), DESC -> NULLS LAST (false)
24655                        };
24656
24657                        // Compute the target's default
24658                        let target_nulls_first = if is_target_nulls_large {
24659                            !is_asc
24660                        } else if is_target_nulls_last {
24661                            false
24662                        } else {
24663                            is_asc
24664                        };
24665
24666                        // Only add explicit nulls ordering if source and target defaults differ
24667                        if source_nulls_first != target_nulls_first {
24668                            o.nulls_first = Some(source_nulls_first);
24669                        }
24670                        // If they match, leave nulls_first as None so the generator won't output it
24671
24672                        Ok(Expression::Ordered(o))
24673                    } else {
24674                        Ok(e)
24675                    }
24676                }
24677                Action::StringAggConvert => {
24678                    match e {
24679                        Expression::WithinGroup(wg) => {
24680                            // STRING_AGG(x, sep) WITHIN GROUP (ORDER BY z) -> target-specific
24681                            // Extract args and distinct flag from either Function, AggregateFunction, or StringAgg
24682                            let (x_opt, sep_opt, distinct) = match wg.this {
24683                                Expression::AggregateFunction(ref af)
24684                                    if af.name.eq_ignore_ascii_case("STRING_AGG")
24685                                        && af.args.len() >= 2 =>
24686                                {
24687                                    (
24688                                        Some(af.args[0].clone()),
24689                                        Some(af.args[1].clone()),
24690                                        af.distinct,
24691                                    )
24692                                }
24693                                Expression::Function(ref f)
24694                                    if f.name.eq_ignore_ascii_case("STRING_AGG")
24695                                        && f.args.len() >= 2 =>
24696                                {
24697                                    (Some(f.args[0].clone()), Some(f.args[1].clone()), false)
24698                                }
24699                                Expression::StringAgg(ref sa) => {
24700                                    (Some(sa.this.clone()), sa.separator.clone(), sa.distinct)
24701                                }
24702                                _ => (None, None, false),
24703                            };
24704                            if let (Some(x), Some(sep)) = (x_opt, sep_opt) {
24705                                let order_by = wg.order_by;
24706
24707                                match target {
24708                                    DialectType::TSQL | DialectType::Fabric => {
24709                                        // Keep as WithinGroup(StringAgg) for TSQL
24710                                        Ok(Expression::WithinGroup(Box::new(
24711                                            crate::expressions::WithinGroup {
24712                                                this: Expression::StringAgg(Box::new(
24713                                                    crate::expressions::StringAggFunc {
24714                                                        this: x,
24715                                                        separator: Some(sep),
24716                                                        order_by: None, // order_by goes in WithinGroup, not StringAgg
24717                                                        distinct,
24718                                                        filter: None,
24719                                                        limit: None,
24720                                                        inferred_type: None,
24721                                                    },
24722                                                )),
24723                                                order_by,
24724                                            },
24725                                        )))
24726                                    }
24727                                    DialectType::MySQL
24728                                    | DialectType::SingleStore
24729                                    | DialectType::Doris
24730                                    | DialectType::StarRocks => {
24731                                        // GROUP_CONCAT(x ORDER BY z SEPARATOR sep)
24732                                        Ok(Expression::GroupConcat(Box::new(
24733                                            crate::expressions::GroupConcatFunc {
24734                                                this: x,
24735                                                separator: Some(sep),
24736                                                order_by: Some(order_by),
24737                                                distinct,
24738                                                filter: None,
24739                                                limit: None,
24740                                                inferred_type: None,
24741                                            },
24742                                        )))
24743                                    }
24744                                    DialectType::SQLite => {
24745                                        // GROUP_CONCAT(x, sep) - no ORDER BY support
24746                                        Ok(Expression::GroupConcat(Box::new(
24747                                            crate::expressions::GroupConcatFunc {
24748                                                this: x,
24749                                                separator: Some(sep),
24750                                                order_by: None,
24751                                                distinct,
24752                                                filter: None,
24753                                                limit: None,
24754                                                inferred_type: None,
24755                                            },
24756                                        )))
24757                                    }
24758                                    DialectType::PostgreSQL | DialectType::Redshift => {
24759                                        // STRING_AGG(x, sep ORDER BY z)
24760                                        Ok(Expression::StringAgg(Box::new(
24761                                            crate::expressions::StringAggFunc {
24762                                                this: x,
24763                                                separator: Some(sep),
24764                                                order_by: Some(order_by),
24765                                                distinct,
24766                                                filter: None,
24767                                                limit: None,
24768                                                inferred_type: None,
24769                                            },
24770                                        )))
24771                                    }
24772                                    _ => {
24773                                        // Default: keep as STRING_AGG(x, sep) with ORDER BY inside
24774                                        Ok(Expression::StringAgg(Box::new(
24775                                            crate::expressions::StringAggFunc {
24776                                                this: x,
24777                                                separator: Some(sep),
24778                                                order_by: Some(order_by),
24779                                                distinct,
24780                                                filter: None,
24781                                                limit: None,
24782                                                inferred_type: None,
24783                                            },
24784                                        )))
24785                                    }
24786                                }
24787                            } else {
24788                                Ok(Expression::WithinGroup(wg))
24789                            }
24790                        }
24791                        Expression::StringAgg(sa) => {
24792                            match target {
24793                                DialectType::MySQL
24794                                | DialectType::SingleStore
24795                                | DialectType::Doris
24796                                | DialectType::StarRocks => {
24797                                    // STRING_AGG(x, sep) -> GROUP_CONCAT(x SEPARATOR sep)
24798                                    Ok(Expression::GroupConcat(Box::new(
24799                                        crate::expressions::GroupConcatFunc {
24800                                            this: sa.this,
24801                                            separator: sa.separator,
24802                                            order_by: sa.order_by,
24803                                            distinct: sa.distinct,
24804                                            filter: sa.filter,
24805                                            limit: None,
24806                                            inferred_type: None,
24807                                        },
24808                                    )))
24809                                }
24810                                DialectType::SQLite => {
24811                                    // STRING_AGG(x, sep) -> GROUP_CONCAT(x, sep)
24812                                    Ok(Expression::GroupConcat(Box::new(
24813                                        crate::expressions::GroupConcatFunc {
24814                                            this: sa.this,
24815                                            separator: sa.separator,
24816                                            order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
24817                                            distinct: sa.distinct,
24818                                            filter: sa.filter,
24819                                            limit: None,
24820                                            inferred_type: None,
24821                                        },
24822                                    )))
24823                                }
24824                                DialectType::Spark | DialectType::Databricks => {
24825                                    // STRING_AGG(x, sep) -> LISTAGG(x, sep)
24826                                    Ok(Expression::ListAgg(Box::new(
24827                                        crate::expressions::ListAggFunc {
24828                                            this: sa.this,
24829                                            separator: sa.separator,
24830                                            on_overflow: None,
24831                                            order_by: sa.order_by,
24832                                            distinct: sa.distinct,
24833                                            filter: None,
24834                                            inferred_type: None,
24835                                        },
24836                                    )))
24837                                }
24838                                _ => Ok(Expression::StringAgg(sa)),
24839                            }
24840                        }
24841                        _ => Ok(e),
24842                    }
24843                }
24844                Action::GroupConcatConvert => {
24845                    // Helper to expand CONCAT(a, b, c) -> a || b || c (for PostgreSQL/SQLite)
24846                    // or CONCAT(a, b, c) -> a + b + c (for TSQL)
24847                    fn expand_concat_to_dpipe(expr: Expression) -> Expression {
24848                        if let Expression::Function(ref f) = expr {
24849                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
24850                                let mut result = f.args[0].clone();
24851                                for arg in &f.args[1..] {
24852                                    result = Expression::Concat(Box::new(BinaryOp {
24853                                        left: result,
24854                                        right: arg.clone(),
24855                                        left_comments: vec![],
24856                                        operator_comments: vec![],
24857                                        trailing_comments: vec![],
24858                                        inferred_type: None,
24859                                    }));
24860                                }
24861                                return result;
24862                            }
24863                        }
24864                        expr
24865                    }
24866                    fn expand_concat_to_plus(expr: Expression) -> Expression {
24867                        if let Expression::Function(ref f) = expr {
24868                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
24869                                let mut result = f.args[0].clone();
24870                                for arg in &f.args[1..] {
24871                                    result = Expression::Add(Box::new(BinaryOp {
24872                                        left: result,
24873                                        right: arg.clone(),
24874                                        left_comments: vec![],
24875                                        operator_comments: vec![],
24876                                        trailing_comments: vec![],
24877                                        inferred_type: None,
24878                                    }));
24879                                }
24880                                return result;
24881                            }
24882                        }
24883                        expr
24884                    }
24885                    // Helper to wrap each arg in CAST(arg AS VARCHAR) for Presto/Trino CONCAT
24886                    fn wrap_concat_args_in_varchar_cast(expr: Expression) -> Expression {
24887                        if let Expression::Function(ref f) = expr {
24888                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
24889                                let new_args: Vec<Expression> = f
24890                                    .args
24891                                    .iter()
24892                                    .map(|arg| {
24893                                        Expression::Cast(Box::new(crate::expressions::Cast {
24894                                            this: arg.clone(),
24895                                            to: crate::expressions::DataType::VarChar {
24896                                                length: None,
24897                                                parenthesized_length: false,
24898                                            },
24899                                            trailing_comments: Vec::new(),
24900                                            double_colon_syntax: false,
24901                                            format: None,
24902                                            default: None,
24903                                            inferred_type: None,
24904                                        }))
24905                                    })
24906                                    .collect();
24907                                return Expression::Function(Box::new(
24908                                    crate::expressions::Function::new(
24909                                        "CONCAT".to_string(),
24910                                        new_args,
24911                                    ),
24912                                ));
24913                            }
24914                        }
24915                        expr
24916                    }
24917                    if let Expression::GroupConcat(gc) = e {
24918                        match target {
24919                            DialectType::Presto => {
24920                                // GROUP_CONCAT(x [, sep]) -> ARRAY_JOIN(ARRAY_AGG(x), sep)
24921                                let sep = gc.separator.unwrap_or(Expression::string(","));
24922                                // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
24923                                let this = wrap_concat_args_in_varchar_cast(gc.this);
24924                                let array_agg =
24925                                    Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
24926                                        this,
24927                                        distinct: gc.distinct,
24928                                        filter: gc.filter,
24929                                        order_by: gc.order_by.unwrap_or_default(),
24930                                        name: None,
24931                                        ignore_nulls: None,
24932                                        having_max: None,
24933                                        limit: None,
24934                                        inferred_type: None,
24935                                    }));
24936                                Ok(Expression::ArrayJoin(Box::new(
24937                                    crate::expressions::ArrayJoinFunc {
24938                                        this: array_agg,
24939                                        separator: sep,
24940                                        null_replacement: None,
24941                                    },
24942                                )))
24943                            }
24944                            DialectType::Trino => {
24945                                // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
24946                                let sep = gc.separator.unwrap_or(Expression::string(","));
24947                                // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
24948                                let this = wrap_concat_args_in_varchar_cast(gc.this);
24949                                Ok(Expression::ListAgg(Box::new(
24950                                    crate::expressions::ListAggFunc {
24951                                        this,
24952                                        separator: Some(sep),
24953                                        on_overflow: None,
24954                                        order_by: gc.order_by,
24955                                        distinct: gc.distinct,
24956                                        filter: gc.filter,
24957                                        inferred_type: None,
24958                                    },
24959                                )))
24960                            }
24961                            DialectType::PostgreSQL
24962                            | DialectType::Redshift
24963                            | DialectType::Snowflake
24964                            | DialectType::DuckDB
24965                            | DialectType::Hive
24966                            | DialectType::ClickHouse => {
24967                                // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep)
24968                                let sep = gc.separator.unwrap_or(Expression::string(","));
24969                                // Expand CONCAT(a,b,c) -> a || b || c for || dialects
24970                                let this = expand_concat_to_dpipe(gc.this);
24971                                // For PostgreSQL, add NULLS LAST for DESC / NULLS FIRST for ASC
24972                                let order_by = if target == DialectType::PostgreSQL {
24973                                    gc.order_by.map(|ords| {
24974                                        ords.into_iter()
24975                                            .map(|mut o| {
24976                                                if o.nulls_first.is_none() {
24977                                                    if o.desc {
24978                                                        o.nulls_first = Some(false);
24979                                                    // NULLS LAST
24980                                                    } else {
24981                                                        o.nulls_first = Some(true);
24982                                                        // NULLS FIRST
24983                                                    }
24984                                                }
24985                                                o
24986                                            })
24987                                            .collect()
24988                                    })
24989                                } else {
24990                                    gc.order_by
24991                                };
24992                                Ok(Expression::StringAgg(Box::new(
24993                                    crate::expressions::StringAggFunc {
24994                                        this,
24995                                        separator: Some(sep),
24996                                        order_by,
24997                                        distinct: gc.distinct,
24998                                        filter: gc.filter,
24999                                        limit: None,
25000                                        inferred_type: None,
25001                                    },
25002                                )))
25003                            }
25004                            DialectType::TSQL => {
25005                                // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep) WITHIN GROUP (ORDER BY ...)
25006                                // TSQL doesn't support DISTINCT in STRING_AGG
25007                                let sep = gc.separator.unwrap_or(Expression::string(","));
25008                                // Expand CONCAT(a,b,c) -> a + b + c for TSQL
25009                                let this = expand_concat_to_plus(gc.this);
25010                                Ok(Expression::StringAgg(Box::new(
25011                                    crate::expressions::StringAggFunc {
25012                                        this,
25013                                        separator: Some(sep),
25014                                        order_by: gc.order_by,
25015                                        distinct: false, // TSQL doesn't support DISTINCT in STRING_AGG
25016                                        filter: gc.filter,
25017                                        limit: None,
25018                                        inferred_type: None,
25019                                    },
25020                                )))
25021                            }
25022                            DialectType::SQLite => {
25023                                // GROUP_CONCAT stays as GROUP_CONCAT but ORDER BY is removed
25024                                // SQLite GROUP_CONCAT doesn't support ORDER BY
25025                                // Expand CONCAT(a,b,c) -> a || b || c
25026                                let this = expand_concat_to_dpipe(gc.this);
25027                                Ok(Expression::GroupConcat(Box::new(
25028                                    crate::expressions::GroupConcatFunc {
25029                                        this,
25030                                        separator: gc.separator,
25031                                        order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
25032                                        distinct: gc.distinct,
25033                                        filter: gc.filter,
25034                                        limit: None,
25035                                        inferred_type: None,
25036                                    },
25037                                )))
25038                            }
25039                            DialectType::Spark | DialectType::Databricks => {
25040                                // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
25041                                let sep = gc.separator.unwrap_or(Expression::string(","));
25042                                Ok(Expression::ListAgg(Box::new(
25043                                    crate::expressions::ListAggFunc {
25044                                        this: gc.this,
25045                                        separator: Some(sep),
25046                                        on_overflow: None,
25047                                        order_by: gc.order_by,
25048                                        distinct: gc.distinct,
25049                                        filter: None,
25050                                        inferred_type: None,
25051                                    },
25052                                )))
25053                            }
25054                            DialectType::MySQL
25055                            | DialectType::SingleStore
25056                            | DialectType::StarRocks => {
25057                                // MySQL GROUP_CONCAT should have explicit SEPARATOR (default ',')
25058                                if gc.separator.is_none() {
25059                                    let mut gc = gc;
25060                                    gc.separator = Some(Expression::string(","));
25061                                    Ok(Expression::GroupConcat(gc))
25062                                } else {
25063                                    Ok(Expression::GroupConcat(gc))
25064                                }
25065                            }
25066                            _ => Ok(Expression::GroupConcat(gc)),
25067                        }
25068                    } else {
25069                        Ok(e)
25070                    }
25071                }
25072                Action::TempTableHash => {
25073                    match e {
25074                        Expression::CreateTable(mut ct) => {
25075                            // TSQL #table -> TEMPORARY TABLE with # stripped from name
25076                            let name = &ct.name.name.name;
25077                            if name.starts_with('#') {
25078                                ct.name.name.name = name.trim_start_matches('#').to_string();
25079                            }
25080                            // Set temporary flag
25081                            ct.temporary = true;
25082                            Ok(Expression::CreateTable(ct))
25083                        }
25084                        Expression::Table(mut tr) => {
25085                            // Strip # from table references
25086                            let name = &tr.name.name;
25087                            if name.starts_with('#') {
25088                                tr.name.name = name.trim_start_matches('#').to_string();
25089                            }
25090                            Ok(Expression::Table(tr))
25091                        }
25092                        Expression::DropTable(mut dt) => {
25093                            // Strip # from DROP TABLE names
25094                            for table_ref in &mut dt.names {
25095                                if table_ref.name.name.starts_with('#') {
25096                                    table_ref.name.name =
25097                                        table_ref.name.name.trim_start_matches('#').to_string();
25098                                }
25099                            }
25100                            Ok(Expression::DropTable(dt))
25101                        }
25102                        _ => Ok(e),
25103                    }
25104                }
25105                Action::NvlClearOriginal => {
25106                    if let Expression::Nvl(mut f) = e {
25107                        f.original_name = None;
25108                        Ok(Expression::Nvl(f))
25109                    } else {
25110                        Ok(e)
25111                    }
25112                }
25113                Action::HiveCastToTryCast => {
25114                    // Convert Hive/Spark CAST to TRY_CAST for targets that support it
25115                    if let Expression::Cast(mut c) = e {
25116                        // For Spark/Hive -> DuckDB: TIMESTAMP -> TIMESTAMPTZ
25117                        // (Spark's TIMESTAMP is always timezone-aware)
25118                        if matches!(target, DialectType::DuckDB)
25119                            && matches!(source, DialectType::Spark | DialectType::Databricks)
25120                            && matches!(
25121                                c.to,
25122                                DataType::Timestamp {
25123                                    timezone: false,
25124                                    ..
25125                                }
25126                            )
25127                        {
25128                            c.to = DataType::Custom {
25129                                name: "TIMESTAMPTZ".to_string(),
25130                            };
25131                        }
25132                        // For Spark source -> Databricks: VARCHAR/CHAR -> STRING
25133                        // Spark parses VARCHAR(n)/CHAR(n) as TEXT, normalize to STRING
25134                        if matches!(target, DialectType::Databricks | DialectType::Spark)
25135                            && matches!(
25136                                source,
25137                                DialectType::Spark | DialectType::Databricks | DialectType::Hive
25138                            )
25139                            && Self::has_varchar_char_type(&c.to)
25140                        {
25141                            c.to = Self::normalize_varchar_to_string(c.to);
25142                        }
25143                        Ok(Expression::TryCast(c))
25144                    } else {
25145                        Ok(e)
25146                    }
25147                }
25148                Action::XorExpand => {
25149                    // Expand XOR to (a AND NOT b) OR (NOT a AND b) for dialects without XOR keyword
25150                    // Snowflake: use BOOLXOR(a, b) instead
25151                    if let Expression::Xor(xor) = e {
25152                        // Collect all XOR operands
25153                        let mut operands = Vec::new();
25154                        if let Some(this) = xor.this {
25155                            operands.push(*this);
25156                        }
25157                        if let Some(expr) = xor.expression {
25158                            operands.push(*expr);
25159                        }
25160                        operands.extend(xor.expressions);
25161
25162                        // Snowflake: use BOOLXOR(a, b)
25163                        if matches!(target, DialectType::Snowflake) && operands.len() == 2 {
25164                            let a = operands.remove(0);
25165                            let b = operands.remove(0);
25166                            return Ok(Expression::Function(Box::new(Function::new(
25167                                "BOOLXOR".to_string(),
25168                                vec![a, b],
25169                            ))));
25170                        }
25171
25172                        // Helper to build (a AND NOT b) OR (NOT a AND b)
25173                        let make_xor = |a: Expression, b: Expression| -> Expression {
25174                            let not_b = Expression::Not(Box::new(
25175                                crate::expressions::UnaryOp::new(b.clone()),
25176                            ));
25177                            let not_a = Expression::Not(Box::new(
25178                                crate::expressions::UnaryOp::new(a.clone()),
25179                            ));
25180                            let left_and = Expression::And(Box::new(BinaryOp {
25181                                left: a,
25182                                right: Expression::Paren(Box::new(Paren {
25183                                    this: not_b,
25184                                    trailing_comments: Vec::new(),
25185                                })),
25186                                left_comments: Vec::new(),
25187                                operator_comments: Vec::new(),
25188                                trailing_comments: Vec::new(),
25189                                inferred_type: None,
25190                            }));
25191                            let right_and = Expression::And(Box::new(BinaryOp {
25192                                left: Expression::Paren(Box::new(Paren {
25193                                    this: not_a,
25194                                    trailing_comments: Vec::new(),
25195                                })),
25196                                right: b,
25197                                left_comments: Vec::new(),
25198                                operator_comments: Vec::new(),
25199                                trailing_comments: Vec::new(),
25200                                inferred_type: None,
25201                            }));
25202                            Expression::Or(Box::new(BinaryOp {
25203                                left: Expression::Paren(Box::new(Paren {
25204                                    this: left_and,
25205                                    trailing_comments: Vec::new(),
25206                                })),
25207                                right: Expression::Paren(Box::new(Paren {
25208                                    this: right_and,
25209                                    trailing_comments: Vec::new(),
25210                                })),
25211                                left_comments: Vec::new(),
25212                                operator_comments: Vec::new(),
25213                                trailing_comments: Vec::new(),
25214                                inferred_type: None,
25215                            }))
25216                        };
25217
25218                        if operands.len() >= 2 {
25219                            let mut result = make_xor(operands.remove(0), operands.remove(0));
25220                            for operand in operands {
25221                                result = make_xor(result, operand);
25222                            }
25223                            Ok(result)
25224                        } else if operands.len() == 1 {
25225                            Ok(operands.remove(0))
25226                        } else {
25227                            // No operands - return FALSE (shouldn't happen)
25228                            Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
25229                                value: false,
25230                            }))
25231                        }
25232                    } else {
25233                        Ok(e)
25234                    }
25235                }
25236                Action::DatePartUnquote => {
25237                    // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
25238                    // Convert the quoted string first arg to a bare Column/Identifier
25239                    if let Expression::Function(mut f) = e {
25240                        if let Some(Expression::Literal(lit)) = f.args.first() {
25241                            if let crate::expressions::Literal::String(s) = lit.as_ref() {
25242                                let bare_name = s.to_ascii_lowercase();
25243                                f.args[0] =
25244                                    Expression::Column(Box::new(crate::expressions::Column {
25245                                        name: Identifier::new(bare_name),
25246                                        table: None,
25247                                        join_mark: false,
25248                                        trailing_comments: Vec::new(),
25249                                        span: None,
25250                                        inferred_type: None,
25251                                    }));
25252                            }
25253                        }
25254                        Ok(Expression::Function(f))
25255                    } else {
25256                        Ok(e)
25257                    }
25258                }
25259                Action::ArrayLengthConvert => {
25260                    // Extract the argument from the expression
25261                    let arg = match e {
25262                        Expression::Cardinality(ref f) => f.this.clone(),
25263                        Expression::ArrayLength(ref f) => f.this.clone(),
25264                        Expression::ArraySize(ref f) => f.this.clone(),
25265                        _ => return Ok(e),
25266                    };
25267                    match target {
25268                        DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
25269                            Ok(Expression::Function(Box::new(Function::new(
25270                                "SIZE".to_string(),
25271                                vec![arg],
25272                            ))))
25273                        }
25274                        DialectType::Presto | DialectType::Trino | DialectType::Athena => {
25275                            Ok(Expression::Cardinality(Box::new(
25276                                crate::expressions::UnaryFunc::new(arg),
25277                            )))
25278                        }
25279                        DialectType::BigQuery => Ok(Expression::ArrayLength(Box::new(
25280                            crate::expressions::UnaryFunc::new(arg),
25281                        ))),
25282                        DialectType::DuckDB => Ok(Expression::ArrayLength(Box::new(
25283                            crate::expressions::UnaryFunc::new(arg),
25284                        ))),
25285                        DialectType::PostgreSQL | DialectType::Redshift => {
25286                            // PostgreSQL ARRAY_LENGTH requires dimension arg
25287                            Ok(Expression::Function(Box::new(Function::new(
25288                                "ARRAY_LENGTH".to_string(),
25289                                vec![arg, Expression::number(1)],
25290                            ))))
25291                        }
25292                        DialectType::Snowflake => Ok(Expression::ArraySize(Box::new(
25293                            crate::expressions::UnaryFunc::new(arg),
25294                        ))),
25295                        _ => Ok(e), // Keep original
25296                    }
25297                }
25298
25299                Action::JsonExtractToArrow => {
25300                    // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB (set arrow_syntax = true)
25301                    if let Expression::JsonExtract(mut f) = e {
25302                        f.arrow_syntax = true;
25303                        // Transform path: convert bracket notation to dot notation
25304                        // SQLite strips wildcards, DuckDB preserves them
25305                        if let Expression::Literal(ref lit) = f.path {
25306                            if let Literal::String(ref s) = lit.as_ref() {
25307                                let mut transformed = s.clone();
25308                                if matches!(target, DialectType::SQLite) {
25309                                    transformed = Self::strip_json_wildcards(&transformed);
25310                                }
25311                                transformed = Self::bracket_to_dot_notation(&transformed);
25312                                if transformed != *s {
25313                                    f.path = Expression::string(&transformed);
25314                                }
25315                            }
25316                        }
25317                        Ok(Expression::JsonExtract(f))
25318                    } else {
25319                        Ok(e)
25320                    }
25321                }
25322
25323                Action::JsonExtractToGetJsonObject => {
25324                    if let Expression::JsonExtract(f) = e {
25325                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
25326                            // JSON_EXTRACT(x, '$.key') -> JSON_EXTRACT_PATH(x, 'key') for PostgreSQL
25327                            // Use proper decomposition that handles brackets
25328                            let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path {
25329                                if let Literal::String(ref s) = lit.as_ref() {
25330                                    let parts = Self::decompose_json_path(s);
25331                                    parts.into_iter().map(|k| Expression::string(&k)).collect()
25332                                } else {
25333                                    vec![]
25334                                }
25335                            } else {
25336                                vec![f.path]
25337                            };
25338                            let func_name = if matches!(target, DialectType::Redshift) {
25339                                "JSON_EXTRACT_PATH_TEXT"
25340                            } else {
25341                                "JSON_EXTRACT_PATH"
25342                            };
25343                            let mut args = vec![f.this];
25344                            args.extend(keys);
25345                            Ok(Expression::Function(Box::new(Function::new(
25346                                func_name.to_string(),
25347                                args,
25348                            ))))
25349                        } else {
25350                            // GET_JSON_OBJECT(x, '$.path') for Hive/Spark
25351                            // Convert bracket double quotes to single quotes
25352                            let path = if let Expression::Literal(ref lit) = f.path {
25353                                if let Literal::String(ref s) = lit.as_ref() {
25354                                    let normalized = Self::bracket_to_single_quotes(s);
25355                                    if normalized != *s {
25356                                        Expression::string(&normalized)
25357                                    } else {
25358                                        f.path.clone()
25359                                    }
25360                                } else {
25361                                    f.path.clone()
25362                                }
25363                            } else {
25364                                f.path.clone()
25365                            };
25366                            Ok(Expression::Function(Box::new(Function::new(
25367                                "GET_JSON_OBJECT".to_string(),
25368                                vec![f.this, path],
25369                            ))))
25370                        }
25371                    } else {
25372                        Ok(e)
25373                    }
25374                }
25375
25376                Action::JsonExtractScalarToGetJsonObject => {
25377                    // JSON_EXTRACT_SCALAR(x, '$.path') -> GET_JSON_OBJECT(x, '$.path') for Hive/Spark
25378                    if let Expression::JsonExtractScalar(f) = e {
25379                        Ok(Expression::Function(Box::new(Function::new(
25380                            "GET_JSON_OBJECT".to_string(),
25381                            vec![f.this, f.path],
25382                        ))))
25383                    } else {
25384                        Ok(e)
25385                    }
25386                }
25387
25388                Action::JsonExtractToTsql => {
25389                    // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> ISNULL(JSON_QUERY(x, path), JSON_VALUE(x, path)) for TSQL
25390                    let (this, path) = match e {
25391                        Expression::JsonExtract(f) => (f.this, f.path),
25392                        Expression::JsonExtractScalar(f) => (f.this, f.path),
25393                        _ => return Ok(e),
25394                    };
25395                    // Transform path: strip wildcards, convert bracket notation to dot notation
25396                    let transformed_path = if let Expression::Literal(ref lit) = path {
25397                        if let Literal::String(ref s) = lit.as_ref() {
25398                            let stripped = Self::strip_json_wildcards(s);
25399                            let dotted = Self::bracket_to_dot_notation(&stripped);
25400                            Expression::string(&dotted)
25401                        } else {
25402                            path.clone()
25403                        }
25404                    } else {
25405                        path
25406                    };
25407                    let json_query = Expression::Function(Box::new(Function::new(
25408                        "JSON_QUERY".to_string(),
25409                        vec![this.clone(), transformed_path.clone()],
25410                    )));
25411                    let json_value = Expression::Function(Box::new(Function::new(
25412                        "JSON_VALUE".to_string(),
25413                        vec![this, transformed_path],
25414                    )));
25415                    Ok(Expression::Function(Box::new(Function::new(
25416                        "ISNULL".to_string(),
25417                        vec![json_query, json_value],
25418                    ))))
25419                }
25420
25421                Action::JsonExtractToClickHouse => {
25422                    // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString(x, 'key1', idx, 'key2') for ClickHouse
25423                    let (this, path) = match e {
25424                        Expression::JsonExtract(f) => (f.this, f.path),
25425                        Expression::JsonExtractScalar(f) => (f.this, f.path),
25426                        _ => return Ok(e),
25427                    };
25428                    let args: Vec<Expression> = if let Expression::Literal(lit) = path {
25429                        if let Literal::String(ref s) = lit.as_ref() {
25430                            let parts = Self::decompose_json_path(s);
25431                            let mut result = vec![this];
25432                            for part in parts {
25433                                // ClickHouse uses 1-based integer indices for array access
25434                                if let Ok(idx) = part.parse::<i64>() {
25435                                    result.push(Expression::number(idx + 1));
25436                                } else {
25437                                    result.push(Expression::string(&part));
25438                                }
25439                            }
25440                            result
25441                        } else {
25442                            vec![]
25443                        }
25444                    } else {
25445                        vec![this, path]
25446                    };
25447                    Ok(Expression::Function(Box::new(Function::new(
25448                        "JSONExtractString".to_string(),
25449                        args,
25450                    ))))
25451                }
25452
25453                Action::JsonExtractScalarConvert => {
25454                    // JSON_EXTRACT_SCALAR -> target-specific
25455                    if let Expression::JsonExtractScalar(f) = e {
25456                        match target {
25457                            DialectType::PostgreSQL | DialectType::Redshift => {
25458                                // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'key1', 'key2')
25459                                let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path
25460                                {
25461                                    if let Literal::String(ref s) = lit.as_ref() {
25462                                        let parts = Self::decompose_json_path(s);
25463                                        parts.into_iter().map(|k| Expression::string(&k)).collect()
25464                                    } else {
25465                                        vec![]
25466                                    }
25467                                } else {
25468                                    vec![f.path]
25469                                };
25470                                let mut args = vec![f.this];
25471                                args.extend(keys);
25472                                Ok(Expression::Function(Box::new(Function::new(
25473                                    "JSON_EXTRACT_PATH_TEXT".to_string(),
25474                                    args,
25475                                ))))
25476                            }
25477                            DialectType::Snowflake => {
25478                                // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'stripped_path')
25479                                let stripped_path = if let Expression::Literal(ref lit) = f.path {
25480                                    if let Literal::String(ref s) = lit.as_ref() {
25481                                        let stripped = Self::strip_json_dollar_prefix(s);
25482                                        Expression::string(&stripped)
25483                                    } else {
25484                                        f.path.clone()
25485                                    }
25486                                } else {
25487                                    f.path
25488                                };
25489                                Ok(Expression::Function(Box::new(Function::new(
25490                                    "JSON_EXTRACT_PATH_TEXT".to_string(),
25491                                    vec![f.this, stripped_path],
25492                                ))))
25493                            }
25494                            DialectType::SQLite | DialectType::DuckDB => {
25495                                // JSON_EXTRACT_SCALAR(x, '$.path') -> x ->> '$.path'
25496                                Ok(Expression::JsonExtractScalar(Box::new(
25497                                    crate::expressions::JsonExtractFunc {
25498                                        this: f.this,
25499                                        path: f.path,
25500                                        returning: f.returning,
25501                                        arrow_syntax: true,
25502                                        hash_arrow_syntax: false,
25503                                        wrapper_option: None,
25504                                        quotes_option: None,
25505                                        on_scalar_string: false,
25506                                        on_error: None,
25507                                    },
25508                                )))
25509                            }
25510                            _ => Ok(Expression::JsonExtractScalar(f)),
25511                        }
25512                    } else {
25513                        Ok(e)
25514                    }
25515                }
25516
25517                Action::JsonPathNormalize => {
25518                    // Normalize JSON path format for BigQuery, MySQL, etc.
25519                    if let Expression::JsonExtract(mut f) = e {
25520                        if let Expression::Literal(ref lit) = f.path {
25521                            if let Literal::String(ref s) = lit.as_ref() {
25522                                let mut normalized = s.clone();
25523                                // Convert bracket notation and handle wildcards per dialect
25524                                match target {
25525                                    DialectType::BigQuery => {
25526                                        // BigQuery strips wildcards and uses single quotes in brackets
25527                                        normalized = Self::strip_json_wildcards(&normalized);
25528                                        normalized = Self::bracket_to_single_quotes(&normalized);
25529                                    }
25530                                    DialectType::MySQL => {
25531                                        // MySQL preserves wildcards, converts brackets to dot notation
25532                                        normalized = Self::bracket_to_dot_notation(&normalized);
25533                                    }
25534                                    _ => {}
25535                                }
25536                                if normalized != *s {
25537                                    f.path = Expression::string(&normalized);
25538                                }
25539                            }
25540                        }
25541                        Ok(Expression::JsonExtract(f))
25542                    } else {
25543                        Ok(e)
25544                    }
25545                }
25546
25547                Action::JsonQueryValueConvert => {
25548                    // JsonQuery/JsonValue -> target-specific
25549                    let (f, is_query) = match e {
25550                        Expression::JsonQuery(f) => (f, true),
25551                        Expression::JsonValue(f) => (f, false),
25552                        _ => return Ok(e),
25553                    };
25554                    match target {
25555                        DialectType::TSQL | DialectType::Fabric => {
25556                            // ISNULL(JSON_QUERY(...), JSON_VALUE(...))
25557                            let json_query = Expression::Function(Box::new(Function::new(
25558                                "JSON_QUERY".to_string(),
25559                                vec![f.this.clone(), f.path.clone()],
25560                            )));
25561                            let json_value = Expression::Function(Box::new(Function::new(
25562                                "JSON_VALUE".to_string(),
25563                                vec![f.this, f.path],
25564                            )));
25565                            Ok(Expression::Function(Box::new(Function::new(
25566                                "ISNULL".to_string(),
25567                                vec![json_query, json_value],
25568                            ))))
25569                        }
25570                        DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
25571                            Ok(Expression::Function(Box::new(Function::new(
25572                                "GET_JSON_OBJECT".to_string(),
25573                                vec![f.this, f.path],
25574                            ))))
25575                        }
25576                        DialectType::PostgreSQL | DialectType::Redshift => {
25577                            Ok(Expression::Function(Box::new(Function::new(
25578                                "JSON_EXTRACT_PATH_TEXT".to_string(),
25579                                vec![f.this, f.path],
25580                            ))))
25581                        }
25582                        DialectType::DuckDB | DialectType::SQLite => {
25583                            // json -> path arrow syntax
25584                            Ok(Expression::JsonExtract(Box::new(
25585                                crate::expressions::JsonExtractFunc {
25586                                    this: f.this,
25587                                    path: f.path,
25588                                    returning: f.returning,
25589                                    arrow_syntax: true,
25590                                    hash_arrow_syntax: false,
25591                                    wrapper_option: f.wrapper_option,
25592                                    quotes_option: f.quotes_option,
25593                                    on_scalar_string: f.on_scalar_string,
25594                                    on_error: f.on_error,
25595                                },
25596                            )))
25597                        }
25598                        DialectType::Snowflake => {
25599                            // GET_PATH(PARSE_JSON(json), 'path')
25600                            // Strip $. prefix from path
25601                            // Only wrap in PARSE_JSON if not already a PARSE_JSON call or ParseJson expression
25602                            let json_expr = match &f.this {
25603                                Expression::Function(ref inner_f)
25604                                    if inner_f.name.eq_ignore_ascii_case("PARSE_JSON") =>
25605                                {
25606                                    f.this
25607                                }
25608                                Expression::ParseJson(_) => {
25609                                    // Already a ParseJson expression, which generates as PARSE_JSON(...)
25610                                    f.this
25611                                }
25612                                _ => Expression::Function(Box::new(Function::new(
25613                                    "PARSE_JSON".to_string(),
25614                                    vec![f.this],
25615                                ))),
25616                            };
25617                            let path_str = match &f.path {
25618                                Expression::Literal(lit)
25619                                    if matches!(lit.as_ref(), Literal::String(_)) =>
25620                                {
25621                                    let Literal::String(s) = lit.as_ref() else {
25622                                        unreachable!()
25623                                    };
25624                                    let stripped = s.strip_prefix("$.").unwrap_or(s);
25625                                    Expression::Literal(Box::new(Literal::String(
25626                                        stripped.to_string(),
25627                                    )))
25628                                }
25629                                other => other.clone(),
25630                            };
25631                            Ok(Expression::Function(Box::new(Function::new(
25632                                "GET_PATH".to_string(),
25633                                vec![json_expr, path_str],
25634                            ))))
25635                        }
25636                        _ => {
25637                            // Default: keep as JSON_QUERY/JSON_VALUE function
25638                            let func_name = if is_query { "JSON_QUERY" } else { "JSON_VALUE" };
25639                            Ok(Expression::Function(Box::new(Function::new(
25640                                func_name.to_string(),
25641                                vec![f.this, f.path],
25642                            ))))
25643                        }
25644                    }
25645                }
25646
25647                Action::JsonLiteralToJsonParse => {
25648                    // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
25649                    // Also DuckDB CAST(x AS JSON) -> JSON_PARSE(x) for Trino/Presto/Athena
25650                    if let Expression::Cast(c) = e {
25651                        let func_name = if matches!(target, DialectType::Snowflake) {
25652                            "PARSE_JSON"
25653                        } else {
25654                            "JSON_PARSE"
25655                        };
25656                        Ok(Expression::Function(Box::new(Function::new(
25657                            func_name.to_string(),
25658                            vec![c.this],
25659                        ))))
25660                    } else {
25661                        Ok(e)
25662                    }
25663                }
25664
25665                Action::DuckDBCastJsonToVariant => {
25666                    if let Expression::Cast(c) = e {
25667                        Ok(Expression::Cast(Box::new(Cast {
25668                            this: c.this,
25669                            to: DataType::Custom {
25670                                name: "VARIANT".to_string(),
25671                            },
25672                            trailing_comments: c.trailing_comments,
25673                            double_colon_syntax: false,
25674                            format: None,
25675                            default: None,
25676                            inferred_type: None,
25677                        })))
25678                    } else {
25679                        Ok(e)
25680                    }
25681                }
25682
25683                Action::DuckDBTryCastJsonToTryJsonParse => {
25684                    // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
25685                    if let Expression::TryCast(c) = e {
25686                        let json_parse = Expression::Function(Box::new(Function::new(
25687                            "JSON_PARSE".to_string(),
25688                            vec![c.this],
25689                        )));
25690                        Ok(Expression::Function(Box::new(Function::new(
25691                            "TRY".to_string(),
25692                            vec![json_parse],
25693                        ))))
25694                    } else {
25695                        Ok(e)
25696                    }
25697                }
25698
25699                Action::DuckDBJsonFuncToJsonParse => {
25700                    // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
25701                    if let Expression::Function(f) = e {
25702                        let args = f.args;
25703                        Ok(Expression::Function(Box::new(Function::new(
25704                            "JSON_PARSE".to_string(),
25705                            args,
25706                        ))))
25707                    } else {
25708                        Ok(e)
25709                    }
25710                }
25711
25712                Action::DuckDBJsonValidToIsJson => {
25713                    // DuckDB json_valid(x) -> x IS JSON (SQL:2016 predicate) for Trino/Presto/Athena
25714                    if let Expression::Function(mut f) = e {
25715                        let arg = f.args.remove(0);
25716                        Ok(Expression::IsJson(Box::new(crate::expressions::IsJson {
25717                            this: arg,
25718                            json_type: None,
25719                            unique_keys: None,
25720                            negated: false,
25721                        })))
25722                    } else {
25723                        Ok(e)
25724                    }
25725                }
25726
25727                Action::AtTimeZoneConvert => {
25728                    // AT TIME ZONE -> target-specific conversion
25729                    if let Expression::AtTimeZone(atz) = e {
25730                        match target {
25731                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
25732                                Ok(Expression::Function(Box::new(Function::new(
25733                                    "AT_TIMEZONE".to_string(),
25734                                    vec![atz.this, atz.zone],
25735                                ))))
25736                            }
25737                            DialectType::Spark | DialectType::Databricks => {
25738                                Ok(Expression::Function(Box::new(Function::new(
25739                                    "FROM_UTC_TIMESTAMP".to_string(),
25740                                    vec![atz.this, atz.zone],
25741                                ))))
25742                            }
25743                            DialectType::Snowflake => {
25744                                // CONVERT_TIMEZONE('zone', expr)
25745                                Ok(Expression::Function(Box::new(Function::new(
25746                                    "CONVERT_TIMEZONE".to_string(),
25747                                    vec![atz.zone, atz.this],
25748                                ))))
25749                            }
25750                            DialectType::BigQuery => {
25751                                // TIMESTAMP(DATETIME(expr, 'zone'))
25752                                let datetime_call = Expression::Function(Box::new(Function::new(
25753                                    "DATETIME".to_string(),
25754                                    vec![atz.this, atz.zone],
25755                                )));
25756                                Ok(Expression::Function(Box::new(Function::new(
25757                                    "TIMESTAMP".to_string(),
25758                                    vec![datetime_call],
25759                                ))))
25760                            }
25761                            _ => Ok(Expression::Function(Box::new(Function::new(
25762                                "AT_TIMEZONE".to_string(),
25763                                vec![atz.this, atz.zone],
25764                            )))),
25765                        }
25766                    } else {
25767                        Ok(e)
25768                    }
25769                }
25770
25771                Action::DayOfWeekConvert => {
25772                    // DAY_OF_WEEK -> ISODOW for DuckDB, ((DAYOFWEEK(x) % 7) + 1) for Spark
25773                    if let Expression::DayOfWeek(f) = e {
25774                        match target {
25775                            DialectType::DuckDB => Ok(Expression::Function(Box::new(
25776                                Function::new("ISODOW".to_string(), vec![f.this]),
25777                            ))),
25778                            DialectType::Spark | DialectType::Databricks => {
25779                                // ((DAYOFWEEK(x) % 7) + 1)
25780                                let dayofweek = Expression::Function(Box::new(Function::new(
25781                                    "DAYOFWEEK".to_string(),
25782                                    vec![f.this],
25783                                )));
25784                                let modulo = Expression::Mod(Box::new(BinaryOp {
25785                                    left: dayofweek,
25786                                    right: Expression::number(7),
25787                                    left_comments: Vec::new(),
25788                                    operator_comments: Vec::new(),
25789                                    trailing_comments: Vec::new(),
25790                                    inferred_type: None,
25791                                }));
25792                                let paren_mod = Expression::Paren(Box::new(Paren {
25793                                    this: modulo,
25794                                    trailing_comments: Vec::new(),
25795                                }));
25796                                let add_one = Expression::Add(Box::new(BinaryOp {
25797                                    left: paren_mod,
25798                                    right: Expression::number(1),
25799                                    left_comments: Vec::new(),
25800                                    operator_comments: Vec::new(),
25801                                    trailing_comments: Vec::new(),
25802                                    inferred_type: None,
25803                                }));
25804                                Ok(Expression::Paren(Box::new(Paren {
25805                                    this: add_one,
25806                                    trailing_comments: Vec::new(),
25807                                })))
25808                            }
25809                            _ => Ok(Expression::DayOfWeek(f)),
25810                        }
25811                    } else {
25812                        Ok(e)
25813                    }
25814                }
25815
25816                Action::MaxByMinByConvert => {
25817                    // MAX_BY -> argMax for ClickHouse, drop 3rd arg for Spark
25818                    // MIN_BY -> argMin for ClickHouse, ARG_MIN for DuckDB, drop 3rd arg for Spark/ClickHouse
25819                    // Handle both Expression::Function and Expression::AggregateFunction
25820                    let (is_max, args) = match &e {
25821                        Expression::Function(f) => {
25822                            (f.name.eq_ignore_ascii_case("MAX_BY"), f.args.clone())
25823                        }
25824                        Expression::AggregateFunction(af) => {
25825                            (af.name.eq_ignore_ascii_case("MAX_BY"), af.args.clone())
25826                        }
25827                        _ => return Ok(e),
25828                    };
25829                    match target {
25830                        DialectType::ClickHouse => {
25831                            let name = if is_max { "argMax" } else { "argMin" };
25832                            let mut args = args;
25833                            args.truncate(2);
25834                            Ok(Expression::Function(Box::new(Function::new(
25835                                name.to_string(),
25836                                args,
25837                            ))))
25838                        }
25839                        DialectType::DuckDB => {
25840                            let name = if is_max { "ARG_MAX" } else { "ARG_MIN" };
25841                            Ok(Expression::Function(Box::new(Function::new(
25842                                name.to_string(),
25843                                args,
25844                            ))))
25845                        }
25846                        DialectType::Spark | DialectType::Databricks => {
25847                            let mut args = args;
25848                            args.truncate(2);
25849                            let name = if is_max { "MAX_BY" } else { "MIN_BY" };
25850                            Ok(Expression::Function(Box::new(Function::new(
25851                                name.to_string(),
25852                                args,
25853                            ))))
25854                        }
25855                        _ => Ok(e),
25856                    }
25857                }
25858
25859                Action::ElementAtConvert => {
25860                    // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
25861                    let (arr, idx) = if let Expression::ElementAt(bf) = e {
25862                        (bf.this, bf.expression)
25863                    } else if let Expression::Function(ref f) = e {
25864                        if f.args.len() >= 2 {
25865                            if let Expression::Function(f) = e {
25866                                let mut args = f.args;
25867                                let arr = args.remove(0);
25868                                let idx = args.remove(0);
25869                                (arr, idx)
25870                            } else {
25871                                unreachable!("outer condition already matched Expression::Function")
25872                            }
25873                        } else {
25874                            return Ok(e);
25875                        }
25876                    } else {
25877                        return Ok(e);
25878                    };
25879                    match target {
25880                        DialectType::PostgreSQL => {
25881                            // Wrap array in parens for PostgreSQL: (ARRAY[1,2,3])[4]
25882                            let arr_expr = Expression::Paren(Box::new(Paren {
25883                                this: arr,
25884                                trailing_comments: vec![],
25885                            }));
25886                            Ok(Expression::Subscript(Box::new(
25887                                crate::expressions::Subscript {
25888                                    this: arr_expr,
25889                                    index: idx,
25890                                },
25891                            )))
25892                        }
25893                        DialectType::BigQuery => {
25894                            // BigQuery: convert ARRAY[...] to bare [...] for subscript
25895                            let arr_expr = match arr {
25896                                Expression::ArrayFunc(af) => Expression::ArrayFunc(Box::new(
25897                                    crate::expressions::ArrayConstructor {
25898                                        expressions: af.expressions,
25899                                        bracket_notation: true,
25900                                        use_list_keyword: false,
25901                                    },
25902                                )),
25903                                other => other,
25904                            };
25905                            let safe_ordinal = Expression::Function(Box::new(Function::new(
25906                                "SAFE_ORDINAL".to_string(),
25907                                vec![idx],
25908                            )));
25909                            Ok(Expression::Subscript(Box::new(
25910                                crate::expressions::Subscript {
25911                                    this: arr_expr,
25912                                    index: safe_ordinal,
25913                                },
25914                            )))
25915                        }
25916                        _ => Ok(Expression::Function(Box::new(Function::new(
25917                            "ELEMENT_AT".to_string(),
25918                            vec![arr, idx],
25919                        )))),
25920                    }
25921                }
25922
25923                Action::CurrentUserParens => {
25924                    // CURRENT_USER -> CURRENT_USER() for Snowflake
25925                    Ok(Expression::Function(Box::new(Function::new(
25926                        "CURRENT_USER".to_string(),
25927                        vec![],
25928                    ))))
25929                }
25930
25931                Action::ArrayAggToCollectList => {
25932                    // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
25933                    // Python sqlglot Hive.arrayagg_sql strips ORDER BY for simple cases
25934                    // but preserves it when DISTINCT/IGNORE NULLS/LIMIT are present
25935                    match e {
25936                        Expression::AggregateFunction(mut af) => {
25937                            let is_simple =
25938                                !af.distinct && af.ignore_nulls.is_none() && af.limit.is_none();
25939                            let args = if af.args.is_empty() {
25940                                vec![]
25941                            } else {
25942                                vec![af.args[0].clone()]
25943                            };
25944                            af.name = "COLLECT_LIST".to_string();
25945                            af.args = args;
25946                            if is_simple {
25947                                af.order_by = Vec::new();
25948                            }
25949                            Ok(Expression::AggregateFunction(af))
25950                        }
25951                        Expression::ArrayAgg(agg) => {
25952                            let is_simple =
25953                                !agg.distinct && agg.ignore_nulls.is_none() && agg.limit.is_none();
25954                            Ok(Expression::AggregateFunction(Box::new(
25955                                crate::expressions::AggregateFunction {
25956                                    name: "COLLECT_LIST".to_string(),
25957                                    args: vec![agg.this.clone()],
25958                                    distinct: agg.distinct,
25959                                    filter: agg.filter.clone(),
25960                                    order_by: if is_simple {
25961                                        Vec::new()
25962                                    } else {
25963                                        agg.order_by.clone()
25964                                    },
25965                                    limit: agg.limit.clone(),
25966                                    ignore_nulls: agg.ignore_nulls,
25967                                    inferred_type: None,
25968                                },
25969                            )))
25970                        }
25971                        _ => Ok(e),
25972                    }
25973                }
25974
25975                Action::ArraySyntaxConvert => {
25976                    match e {
25977                        // ARRAY[1, 2] (ArrayFunc bracket_notation=false) -> set bracket_notation=true
25978                        // so the generator uses dialect-specific output (ARRAY() for Spark, [] for BigQuery)
25979                        Expression::ArrayFunc(arr) if !arr.bracket_notation => Ok(
25980                            Expression::ArrayFunc(Box::new(crate::expressions::ArrayConstructor {
25981                                expressions: arr.expressions,
25982                                bracket_notation: true,
25983                                use_list_keyword: false,
25984                            })),
25985                        ),
25986                        // ARRAY(y) function style -> ArrayFunc for target dialect
25987                        // bracket_notation=true for BigQuery/DuckDB/ClickHouse/StarRocks (output []), false for Presto (output ARRAY[])
25988                        Expression::Function(f) if f.name.eq_ignore_ascii_case("ARRAY") => {
25989                            let bracket = matches!(
25990                                target,
25991                                DialectType::BigQuery
25992                                    | DialectType::DuckDB
25993                                    | DialectType::Snowflake
25994                                    | DialectType::ClickHouse
25995                                    | DialectType::StarRocks
25996                            );
25997                            Ok(Expression::ArrayFunc(Box::new(
25998                                crate::expressions::ArrayConstructor {
25999                                    expressions: f.args,
26000                                    bracket_notation: bracket,
26001                                    use_list_keyword: false,
26002                                },
26003                            )))
26004                        }
26005                        _ => Ok(e),
26006                    }
26007                }
26008
26009                Action::CastToJsonForSpark => {
26010                    // CAST(x AS JSON) -> TO_JSON(x) for Spark
26011                    if let Expression::Cast(c) = e {
26012                        Ok(Expression::Function(Box::new(Function::new(
26013                            "TO_JSON".to_string(),
26014                            vec![c.this],
26015                        ))))
26016                    } else {
26017                        Ok(e)
26018                    }
26019                }
26020
26021                Action::CastJsonToFromJson => {
26022                    // CAST(ParseJson(literal) AS ARRAY/MAP/STRUCT) -> FROM_JSON(literal, type_string) for Spark
26023                    if let Expression::Cast(c) = e {
26024                        // Extract the string literal from ParseJson
26025                        let literal_expr = if let Expression::ParseJson(pj) = c.this {
26026                            pj.this
26027                        } else {
26028                            c.this
26029                        };
26030                        // Convert the target DataType to Spark's type string format
26031                        let type_str = Self::data_type_to_spark_string(&c.to);
26032                        Ok(Expression::Function(Box::new(Function::new(
26033                            "FROM_JSON".to_string(),
26034                            vec![
26035                                literal_expr,
26036                                Expression::Literal(Box::new(Literal::String(type_str))),
26037                            ],
26038                        ))))
26039                    } else {
26040                        Ok(e)
26041                    }
26042                }
26043
26044                Action::ToJsonConvert => {
26045                    // TO_JSON(x) -> target-specific conversion
26046                    if let Expression::ToJson(f) = e {
26047                        let arg = f.this;
26048                        match target {
26049                            DialectType::Presto | DialectType::Trino => {
26050                                // JSON_FORMAT(CAST(x AS JSON))
26051                                let cast_json = Expression::Cast(Box::new(Cast {
26052                                    this: arg,
26053                                    to: DataType::Custom {
26054                                        name: "JSON".to_string(),
26055                                    },
26056                                    trailing_comments: vec![],
26057                                    double_colon_syntax: false,
26058                                    format: None,
26059                                    default: None,
26060                                    inferred_type: None,
26061                                }));
26062                                Ok(Expression::Function(Box::new(Function::new(
26063                                    "JSON_FORMAT".to_string(),
26064                                    vec![cast_json],
26065                                ))))
26066                            }
26067                            DialectType::BigQuery => Ok(Expression::Function(Box::new(
26068                                Function::new("TO_JSON_STRING".to_string(), vec![arg]),
26069                            ))),
26070                            DialectType::DuckDB => {
26071                                // CAST(TO_JSON(x) AS TEXT)
26072                                let to_json =
26073                                    Expression::ToJson(Box::new(crate::expressions::UnaryFunc {
26074                                        this: arg,
26075                                        original_name: None,
26076                                        inferred_type: None,
26077                                    }));
26078                                Ok(Expression::Cast(Box::new(Cast {
26079                                    this: to_json,
26080                                    to: DataType::Text,
26081                                    trailing_comments: vec![],
26082                                    double_colon_syntax: false,
26083                                    format: None,
26084                                    default: None,
26085                                    inferred_type: None,
26086                                })))
26087                            }
26088                            _ => Ok(Expression::ToJson(Box::new(
26089                                crate::expressions::UnaryFunc {
26090                                    this: arg,
26091                                    original_name: None,
26092                                    inferred_type: None,
26093                                },
26094                            ))),
26095                        }
26096                    } else {
26097                        Ok(e)
26098                    }
26099                }
26100
26101                Action::VarianceToClickHouse => {
26102                    if let Expression::Variance(f) = e {
26103                        Ok(Expression::Function(Box::new(Function::new(
26104                            "varSamp".to_string(),
26105                            vec![f.this],
26106                        ))))
26107                    } else {
26108                        Ok(e)
26109                    }
26110                }
26111
26112                Action::StddevToClickHouse => {
26113                    if let Expression::Stddev(f) = e {
26114                        Ok(Expression::Function(Box::new(Function::new(
26115                            "stddevSamp".to_string(),
26116                            vec![f.this],
26117                        ))))
26118                    } else {
26119                        Ok(e)
26120                    }
26121                }
26122
26123                Action::ApproxQuantileConvert => {
26124                    if let Expression::ApproxQuantile(aq) = e {
26125                        let mut args = vec![*aq.this];
26126                        if let Some(q) = aq.quantile {
26127                            args.push(*q);
26128                        }
26129                        Ok(Expression::Function(Box::new(Function::new(
26130                            "APPROX_PERCENTILE".to_string(),
26131                            args,
26132                        ))))
26133                    } else {
26134                        Ok(e)
26135                    }
26136                }
26137
26138                Action::DollarParamConvert => {
26139                    if let Expression::Parameter(p) = e {
26140                        Ok(Expression::Parameter(Box::new(
26141                            crate::expressions::Parameter {
26142                                name: p.name,
26143                                index: p.index,
26144                                style: crate::expressions::ParameterStyle::At,
26145                                quoted: p.quoted,
26146                                string_quoted: p.string_quoted,
26147                                expression: p.expression,
26148                            },
26149                        )))
26150                    } else {
26151                        Ok(e)
26152                    }
26153                }
26154
26155                Action::EscapeStringNormalize => {
26156                    if let Expression::Literal(ref lit) = e {
26157                        if let Literal::EscapeString(s) = lit.as_ref() {
26158                            // Strip prefix (e.g., "e:" or "E:") if present from tokenizer
26159                            let stripped = if s.starts_with("e:") || s.starts_with("E:") {
26160                                s[2..].to_string()
26161                            } else {
26162                                s.clone()
26163                            };
26164                            let normalized = stripped
26165                                .replace('\n', "\\n")
26166                                .replace('\r', "\\r")
26167                                .replace('\t', "\\t");
26168                            match target {
26169                                DialectType::BigQuery => {
26170                                    // BigQuery: e'...' -> CAST(b'...' AS STRING)
26171                                    // Use Raw for the b'...' part to avoid double-escaping
26172                                    let raw_sql = format!("CAST(b'{}' AS STRING)", normalized);
26173                                    Ok(Expression::Raw(crate::expressions::Raw { sql: raw_sql }))
26174                                }
26175                                _ => Ok(Expression::Literal(Box::new(Literal::EscapeString(
26176                                    normalized,
26177                                )))),
26178                            }
26179                        } else {
26180                            Ok(e)
26181                        }
26182                    } else {
26183                        Ok(e)
26184                    }
26185                }
26186
26187                Action::StraightJoinCase => {
26188                    // straight_join: keep lowercase for DuckDB, quote for MySQL
26189                    if let Expression::Column(col) = e {
26190                        if col.name.name == "STRAIGHT_JOIN" {
26191                            let mut new_col = col;
26192                            new_col.name.name = "straight_join".to_string();
26193                            if matches!(target, DialectType::MySQL) {
26194                                // MySQL: needs quoting since it's a reserved keyword
26195                                new_col.name.quoted = true;
26196                            }
26197                            Ok(Expression::Column(new_col))
26198                        } else {
26199                            Ok(Expression::Column(col))
26200                        }
26201                    } else {
26202                        Ok(e)
26203                    }
26204                }
26205
26206                Action::TablesampleReservoir => {
26207                    // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB
26208                    if let Expression::TableSample(mut ts) = e {
26209                        if let Some(ref mut sample) = ts.sample {
26210                            sample.method = crate::expressions::SampleMethod::Reservoir;
26211                            sample.explicit_method = true;
26212                        }
26213                        Ok(Expression::TableSample(ts))
26214                    } else {
26215                        Ok(e)
26216                    }
26217                }
26218
26219                Action::TablesampleSnowflakeStrip => {
26220                    // Strip method and PERCENT for Snowflake target from non-Snowflake source
26221                    match e {
26222                        Expression::TableSample(mut ts) => {
26223                            if let Some(ref mut sample) = ts.sample {
26224                                sample.suppress_method_output = true;
26225                                sample.unit_after_size = false;
26226                                sample.is_percent = false;
26227                            }
26228                            Ok(Expression::TableSample(ts))
26229                        }
26230                        Expression::Table(mut t) => {
26231                            if let Some(ref mut sample) = t.table_sample {
26232                                sample.suppress_method_output = true;
26233                                sample.unit_after_size = false;
26234                                sample.is_percent = false;
26235                            }
26236                            Ok(Expression::Table(t))
26237                        }
26238                        _ => Ok(e),
26239                    }
26240                }
26241
26242                Action::FirstToAnyValue => {
26243                    // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
26244                    if let Expression::First(mut agg) = e {
26245                        agg.ignore_nulls = None;
26246                        agg.name = Some("ANY_VALUE".to_string());
26247                        Ok(Expression::AnyValue(agg))
26248                    } else {
26249                        Ok(e)
26250                    }
26251                }
26252
26253                Action::ArrayIndexConvert => {
26254                    // Subscript index: 1-based to 0-based for BigQuery
26255                    if let Expression::Subscript(mut sub) = e {
26256                        if let Expression::Literal(ref lit) = sub.index {
26257                            if let Literal::Number(ref n) = lit.as_ref() {
26258                                if let Ok(val) = n.parse::<i64>() {
26259                                    sub.index = Expression::Literal(Box::new(Literal::Number(
26260                                        (val - 1).to_string(),
26261                                    )));
26262                                }
26263                            }
26264                        }
26265                        Ok(Expression::Subscript(sub))
26266                    } else {
26267                        Ok(e)
26268                    }
26269                }
26270
26271                Action::AnyValueIgnoreNulls => {
26272                    // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
26273                    if let Expression::AnyValue(mut av) = e {
26274                        if av.ignore_nulls.is_none() {
26275                            av.ignore_nulls = Some(true);
26276                        }
26277                        Ok(Expression::AnyValue(av))
26278                    } else {
26279                        Ok(e)
26280                    }
26281                }
26282
26283                Action::BigQueryNullsOrdering => {
26284                    // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
26285                    if let Expression::WindowFunction(mut wf) = e {
26286                        for o in &mut wf.over.order_by {
26287                            o.nulls_first = None;
26288                        }
26289                        Ok(Expression::WindowFunction(wf))
26290                    } else if let Expression::Ordered(mut o) = e {
26291                        o.nulls_first = None;
26292                        Ok(Expression::Ordered(o))
26293                    } else {
26294                        Ok(e)
26295                    }
26296                }
26297
26298                Action::SnowflakeFloatProtect => {
26299                    // Convert DataType::Float to DataType::Custom("FLOAT") to prevent
26300                    // Snowflake's target transform from converting it to DOUBLE.
26301                    // Non-Snowflake sources should keep their FLOAT spelling.
26302                    if let Expression::DataType(DataType::Float { .. }) = e {
26303                        Ok(Expression::DataType(DataType::Custom {
26304                            name: "FLOAT".to_string(),
26305                        }))
26306                    } else {
26307                        Ok(e)
26308                    }
26309                }
26310
26311                Action::MysqlNullsOrdering => {
26312                    // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
26313                    if let Expression::Ordered(mut o) = e {
26314                        let nulls_last = o.nulls_first == Some(false);
26315                        let desc = o.desc;
26316                        // MySQL default: ASC -> NULLS LAST, DESC -> NULLS FIRST
26317                        // If requested ordering matches default, just strip NULLS clause
26318                        let matches_default = if desc {
26319                            // DESC default is NULLS FIRST, so nulls_first=true matches
26320                            o.nulls_first == Some(true)
26321                        } else {
26322                            // ASC default is NULLS LAST, so nulls_first=false matches
26323                            nulls_last
26324                        };
26325                        if matches_default {
26326                            o.nulls_first = None;
26327                            Ok(Expression::Ordered(o))
26328                        } else {
26329                            // Need CASE WHEN x IS NULL THEN 0/1 ELSE 0/1 END, x
26330                            // For ASC NULLS FIRST: ORDER BY CASE WHEN x IS NULL THEN 0 ELSE 1 END, x ASC
26331                            // For DESC NULLS LAST: ORDER BY CASE WHEN x IS NULL THEN 1 ELSE 0 END, x DESC
26332                            let null_val = if desc { 1 } else { 0 };
26333                            let non_null_val = if desc { 0 } else { 1 };
26334                            let _case_expr = Expression::Case(Box::new(Case {
26335                                operand: None,
26336                                whens: vec![(
26337                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
26338                                        this: o.this.clone(),
26339                                        not: false,
26340                                        postfix_form: false,
26341                                    })),
26342                                    Expression::number(null_val),
26343                                )],
26344                                else_: Some(Expression::number(non_null_val)),
26345                                comments: Vec::new(),
26346                                inferred_type: None,
26347                            }));
26348                            o.nulls_first = None;
26349                            // Return a tuple of [case_expr, ordered_expr]
26350                            // We need to return both as part of the ORDER BY
26351                            // But since transform_recursive processes individual expressions,
26352                            // we can't easily add extra ORDER BY items here.
26353                            // Instead, strip the nulls_first
26354                            o.nulls_first = None;
26355                            Ok(Expression::Ordered(o))
26356                        }
26357                    } else {
26358                        Ok(e)
26359                    }
26360                }
26361
26362                Action::MysqlNullsLastRewrite => {
26363                    // DuckDB -> MySQL: Add CASE WHEN IS NULL THEN 1 ELSE 0 END to ORDER BY
26364                    // to simulate NULLS LAST for ASC ordering
26365                    if let Expression::WindowFunction(mut wf) = e {
26366                        let mut new_order_by = Vec::new();
26367                        for o in wf.over.order_by {
26368                            if !o.desc {
26369                                // ASC: DuckDB has NULLS LAST, MySQL has NULLS FIRST
26370                                // Add CASE WHEN expr IS NULL THEN 1 ELSE 0 END before expr
26371                                let case_expr = Expression::Case(Box::new(Case {
26372                                    operand: None,
26373                                    whens: vec![(
26374                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
26375                                            this: o.this.clone(),
26376                                            not: false,
26377                                            postfix_form: false,
26378                                        })),
26379                                        Expression::Literal(Box::new(Literal::Number(
26380                                            "1".to_string(),
26381                                        ))),
26382                                    )],
26383                                    else_: Some(Expression::Literal(Box::new(Literal::Number(
26384                                        "0".to_string(),
26385                                    )))),
26386                                    comments: Vec::new(),
26387                                    inferred_type: None,
26388                                }));
26389                                new_order_by.push(crate::expressions::Ordered {
26390                                    this: case_expr,
26391                                    desc: false,
26392                                    nulls_first: None,
26393                                    explicit_asc: false,
26394                                    with_fill: None,
26395                                });
26396                                let mut ordered = o;
26397                                ordered.nulls_first = None;
26398                                new_order_by.push(ordered);
26399                            } else {
26400                                // DESC: DuckDB has NULLS LAST, MySQL also has NULLS LAST (NULLs smallest in DESC)
26401                                // No change needed
26402                                let mut ordered = o;
26403                                ordered.nulls_first = None;
26404                                new_order_by.push(ordered);
26405                            }
26406                        }
26407                        wf.over.order_by = new_order_by;
26408                        Ok(Expression::WindowFunction(wf))
26409                    } else {
26410                        Ok(e)
26411                    }
26412                }
26413
26414                Action::RespectNullsConvert => {
26415                    // RESPECT NULLS -> strip for SQLite (FIRST_VALUE(c) OVER (...))
26416                    if let Expression::WindowFunction(mut wf) = e {
26417                        match &mut wf.this {
26418                            Expression::FirstValue(ref mut vf) => {
26419                                if vf.ignore_nulls == Some(false) {
26420                                    vf.ignore_nulls = None;
26421                                    // For SQLite, we'd need to add NULLS LAST to ORDER BY in the OVER clause
26422                                    // but that's handled by the generator's NULLS ordering
26423                                }
26424                            }
26425                            Expression::LastValue(ref mut vf) => {
26426                                if vf.ignore_nulls == Some(false) {
26427                                    vf.ignore_nulls = None;
26428                                }
26429                            }
26430                            _ => {}
26431                        }
26432                        Ok(Expression::WindowFunction(wf))
26433                    } else {
26434                        Ok(e)
26435                    }
26436                }
26437
26438                Action::SnowflakeWindowFrameStrip => {
26439                    // Strip the default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
26440                    // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when targeting Snowflake
26441                    if let Expression::WindowFunction(mut wf) = e {
26442                        wf.over.frame = None;
26443                        Ok(Expression::WindowFunction(wf))
26444                    } else {
26445                        Ok(e)
26446                    }
26447                }
26448
26449                Action::SnowflakeWindowFrameAdd => {
26450                    // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
26451                    // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when transpiling from Snowflake to non-Snowflake
26452                    if let Expression::WindowFunction(mut wf) = e {
26453                        wf.over.frame = Some(crate::expressions::WindowFrame {
26454                            kind: crate::expressions::WindowFrameKind::Rows,
26455                            start: crate::expressions::WindowFrameBound::UnboundedPreceding,
26456                            end: Some(crate::expressions::WindowFrameBound::UnboundedFollowing),
26457                            exclude: None,
26458                            kind_text: None,
26459                            start_side_text: None,
26460                            end_side_text: None,
26461                        });
26462                        Ok(Expression::WindowFunction(wf))
26463                    } else {
26464                        Ok(e)
26465                    }
26466                }
26467
26468                Action::CreateTableStripComment => {
26469                    // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
26470                    if let Expression::CreateTable(mut ct) = e {
26471                        for col in &mut ct.columns {
26472                            col.comment = None;
26473                            col.constraints.retain(|c| {
26474                                !matches!(c, crate::expressions::ColumnConstraint::Comment(_))
26475                            });
26476                            // Also remove Comment from constraint_order
26477                            col.constraint_order.retain(|c| {
26478                                !matches!(c, crate::expressions::ConstraintType::Comment)
26479                            });
26480                        }
26481                        // Strip properties (USING, PARTITIONED BY, etc.)
26482                        ct.properties.clear();
26483                        Ok(Expression::CreateTable(ct))
26484                    } else {
26485                        Ok(e)
26486                    }
26487                }
26488
26489                Action::AlterTableToSpRename => {
26490                    // ALTER TABLE db.t1 RENAME TO db.t2 -> EXEC sp_rename 'db.t1', 't2'
26491                    if let Expression::AlterTable(ref at) = e {
26492                        if let Some(crate::expressions::AlterTableAction::RenameTable(
26493                            ref new_tbl,
26494                        )) = at.actions.first()
26495                        {
26496                            // Build the old table name using TSQL bracket quoting
26497                            let old_name = if let Some(ref schema) = at.name.schema {
26498                                if at.name.name.quoted || schema.quoted {
26499                                    format!("[{}].[{}]", schema.name, at.name.name.name)
26500                                } else {
26501                                    format!("{}.{}", schema.name, at.name.name.name)
26502                                }
26503                            } else {
26504                                if at.name.name.quoted {
26505                                    format!("[{}]", at.name.name.name)
26506                                } else {
26507                                    at.name.name.name.clone()
26508                                }
26509                            };
26510                            let new_name = new_tbl.name.name.clone();
26511                            // EXEC sp_rename 'old_name', 'new_name'
26512                            let sql = format!("EXEC sp_rename '{}', '{}'", old_name, new_name);
26513                            Ok(Expression::Raw(crate::expressions::Raw { sql }))
26514                        } else {
26515                            Ok(e)
26516                        }
26517                    } else {
26518                        Ok(e)
26519                    }
26520                }
26521
26522                Action::SnowflakeIntervalFormat => {
26523                    // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
26524                    if let Expression::Interval(mut iv) = e {
26525                        if let (Some(Expression::Literal(lit)), Some(ref unit_spec)) =
26526                            (&iv.this, &iv.unit)
26527                        {
26528                            if let Literal::String(ref val) = lit.as_ref() {
26529                                let unit_str = match unit_spec {
26530                                    crate::expressions::IntervalUnitSpec::Simple {
26531                                        unit, ..
26532                                    } => match unit {
26533                                        crate::expressions::IntervalUnit::Year => "YEAR",
26534                                        crate::expressions::IntervalUnit::Quarter => "QUARTER",
26535                                        crate::expressions::IntervalUnit::Month => "MONTH",
26536                                        crate::expressions::IntervalUnit::Week => "WEEK",
26537                                        crate::expressions::IntervalUnit::Day => "DAY",
26538                                        crate::expressions::IntervalUnit::Hour => "HOUR",
26539                                        crate::expressions::IntervalUnit::Minute => "MINUTE",
26540                                        crate::expressions::IntervalUnit::Second => "SECOND",
26541                                        crate::expressions::IntervalUnit::Millisecond => {
26542                                            "MILLISECOND"
26543                                        }
26544                                        crate::expressions::IntervalUnit::Microsecond => {
26545                                            "MICROSECOND"
26546                                        }
26547                                        crate::expressions::IntervalUnit::Nanosecond => {
26548                                            "NANOSECOND"
26549                                        }
26550                                    },
26551                                    _ => "",
26552                                };
26553                                if !unit_str.is_empty() {
26554                                    let combined = format!("{} {}", val, unit_str);
26555                                    iv.this = Some(Expression::Literal(Box::new(Literal::String(
26556                                        combined,
26557                                    ))));
26558                                    iv.unit = None;
26559                                }
26560                            }
26561                        }
26562                        Ok(Expression::Interval(iv))
26563                    } else {
26564                        Ok(e)
26565                    }
26566                }
26567
26568                Action::ArrayConcatBracketConvert => {
26569                    // Expression::Array/ArrayFunc -> target-specific
26570                    // For PostgreSQL: Array -> ArrayFunc (bracket_notation: false)
26571                    // For Redshift: Array/ArrayFunc -> Function("ARRAY", args) to produce ARRAY(1, 2) with parens
26572                    match e {
26573                        Expression::Array(arr) => {
26574                            if matches!(target, DialectType::Redshift) {
26575                                Ok(Expression::Function(Box::new(Function::new(
26576                                    "ARRAY".to_string(),
26577                                    arr.expressions,
26578                                ))))
26579                            } else {
26580                                Ok(Expression::ArrayFunc(Box::new(
26581                                    crate::expressions::ArrayConstructor {
26582                                        expressions: arr.expressions,
26583                                        bracket_notation: false,
26584                                        use_list_keyword: false,
26585                                    },
26586                                )))
26587                            }
26588                        }
26589                        Expression::ArrayFunc(arr) => {
26590                            // Only for Redshift: convert bracket-notation ArrayFunc to Function("ARRAY")
26591                            if matches!(target, DialectType::Redshift) {
26592                                Ok(Expression::Function(Box::new(Function::new(
26593                                    "ARRAY".to_string(),
26594                                    arr.expressions,
26595                                ))))
26596                            } else {
26597                                Ok(Expression::ArrayFunc(arr))
26598                            }
26599                        }
26600                        _ => Ok(e),
26601                    }
26602                }
26603
26604                Action::BitAggFloatCast => {
26605                    // BIT_OR/BIT_AND/BIT_XOR with float/decimal cast arg -> wrap with ROUND+INT cast for DuckDB
26606                    // For FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
26607                    // For DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
26608                    let int_type = DataType::Int {
26609                        length: None,
26610                        integer_spelling: false,
26611                    };
26612                    let wrap_agg = |agg_this: Expression, int_dt: DataType| -> Expression {
26613                        if let Expression::Cast(c) = agg_this {
26614                            match &c.to {
26615                                DataType::Float { .. }
26616                                | DataType::Double { .. }
26617                                | DataType::Custom { .. } => {
26618                                    // FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
26619                                    // Change FLOAT to REAL (Float with real_spelling=true) for DuckDB generator
26620                                    let inner_type = match &c.to {
26621                                        DataType::Float {
26622                                            precision, scale, ..
26623                                        } => DataType::Float {
26624                                            precision: *precision,
26625                                            scale: *scale,
26626                                            real_spelling: true,
26627                                        },
26628                                        other => other.clone(),
26629                                    };
26630                                    let inner_cast =
26631                                        Expression::Cast(Box::new(crate::expressions::Cast {
26632                                            this: c.this.clone(),
26633                                            to: inner_type,
26634                                            trailing_comments: Vec::new(),
26635                                            double_colon_syntax: false,
26636                                            format: None,
26637                                            default: None,
26638                                            inferred_type: None,
26639                                        }));
26640                                    let rounded = Expression::Function(Box::new(Function::new(
26641                                        "ROUND".to_string(),
26642                                        vec![inner_cast],
26643                                    )));
26644                                    Expression::Cast(Box::new(crate::expressions::Cast {
26645                                        this: rounded,
26646                                        to: int_dt,
26647                                        trailing_comments: Vec::new(),
26648                                        double_colon_syntax: false,
26649                                        format: None,
26650                                        default: None,
26651                                        inferred_type: None,
26652                                    }))
26653                                }
26654                                DataType::Decimal { .. } => {
26655                                    // DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
26656                                    Expression::Cast(Box::new(crate::expressions::Cast {
26657                                        this: Expression::Cast(c),
26658                                        to: int_dt,
26659                                        trailing_comments: Vec::new(),
26660                                        double_colon_syntax: false,
26661                                        format: None,
26662                                        default: None,
26663                                        inferred_type: None,
26664                                    }))
26665                                }
26666                                _ => Expression::Cast(c),
26667                            }
26668                        } else {
26669                            agg_this
26670                        }
26671                    };
26672                    match e {
26673                        Expression::BitwiseOrAgg(mut f) => {
26674                            f.this = wrap_agg(f.this, int_type);
26675                            Ok(Expression::BitwiseOrAgg(f))
26676                        }
26677                        Expression::BitwiseAndAgg(mut f) => {
26678                            let int_type = DataType::Int {
26679                                length: None,
26680                                integer_spelling: false,
26681                            };
26682                            f.this = wrap_agg(f.this, int_type);
26683                            Ok(Expression::BitwiseAndAgg(f))
26684                        }
26685                        Expression::BitwiseXorAgg(mut f) => {
26686                            let int_type = DataType::Int {
26687                                length: None,
26688                                integer_spelling: false,
26689                            };
26690                            f.this = wrap_agg(f.this, int_type);
26691                            Ok(Expression::BitwiseXorAgg(f))
26692                        }
26693                        _ => Ok(e),
26694                    }
26695                }
26696
26697                Action::BitAggSnowflakeRename => {
26698                    // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG, BIT_XOR -> BITXORAGG for Snowflake
26699                    match e {
26700                        Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(
26701                            Function::new("BITORAGG".to_string(), vec![f.this]),
26702                        ))),
26703                        Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(
26704                            Function::new("BITANDAGG".to_string(), vec![f.this]),
26705                        ))),
26706                        Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(
26707                            Function::new("BITXORAGG".to_string(), vec![f.this]),
26708                        ))),
26709                        _ => Ok(e),
26710                    }
26711                }
26712
26713                Action::StrftimeCastTimestamp => {
26714                    // CAST(x AS TIMESTAMP) -> CAST(x AS TIMESTAMP_NTZ) for Spark
26715                    if let Expression::Cast(mut c) = e {
26716                        if matches!(
26717                            c.to,
26718                            DataType::Timestamp {
26719                                timezone: false,
26720                                ..
26721                            }
26722                        ) {
26723                            c.to = DataType::Custom {
26724                                name: "TIMESTAMP_NTZ".to_string(),
26725                            };
26726                        }
26727                        Ok(Expression::Cast(c))
26728                    } else {
26729                        Ok(e)
26730                    }
26731                }
26732
26733                Action::DecimalDefaultPrecision => {
26734                    // DECIMAL without precision -> DECIMAL(18, 3) for Snowflake
26735                    if let Expression::Cast(mut c) = e {
26736                        if matches!(
26737                            c.to,
26738                            DataType::Decimal {
26739                                precision: None,
26740                                ..
26741                            }
26742                        ) {
26743                            c.to = DataType::Decimal {
26744                                precision: Some(18),
26745                                scale: Some(3),
26746                            };
26747                        }
26748                        Ok(Expression::Cast(c))
26749                    } else {
26750                        Ok(e)
26751                    }
26752                }
26753
26754                Action::FilterToIff => {
26755                    // FILTER(WHERE cond) -> rewrite aggregate: AGG(IFF(cond, val, NULL))
26756                    if let Expression::Filter(f) = e {
26757                        let condition = *f.expression;
26758                        let agg = *f.this;
26759                        // Strip WHERE from condition
26760                        let cond = match condition {
26761                            Expression::Where(w) => w.this,
26762                            other => other,
26763                        };
26764                        // Extract the aggregate function and its argument
26765                        // We want AVG(IFF(condition, x, NULL))
26766                        match agg {
26767                            Expression::Function(mut func) => {
26768                                if !func.args.is_empty() {
26769                                    let orig_arg = func.args[0].clone();
26770                                    let iff_call = Expression::Function(Box::new(Function::new(
26771                                        "IFF".to_string(),
26772                                        vec![cond, orig_arg, Expression::Null(Null)],
26773                                    )));
26774                                    func.args[0] = iff_call;
26775                                    Ok(Expression::Function(func))
26776                                } else {
26777                                    Ok(Expression::Filter(Box::new(crate::expressions::Filter {
26778                                        this: Box::new(Expression::Function(func)),
26779                                        expression: Box::new(cond),
26780                                    })))
26781                                }
26782                            }
26783                            Expression::Avg(mut avg) => {
26784                                let iff_call = Expression::Function(Box::new(Function::new(
26785                                    "IFF".to_string(),
26786                                    vec![cond, avg.this.clone(), Expression::Null(Null)],
26787                                )));
26788                                avg.this = iff_call;
26789                                Ok(Expression::Avg(avg))
26790                            }
26791                            Expression::Sum(mut s) => {
26792                                let iff_call = Expression::Function(Box::new(Function::new(
26793                                    "IFF".to_string(),
26794                                    vec![cond, s.this.clone(), Expression::Null(Null)],
26795                                )));
26796                                s.this = iff_call;
26797                                Ok(Expression::Sum(s))
26798                            }
26799                            Expression::Count(mut c) => {
26800                                if let Some(ref this_expr) = c.this {
26801                                    let iff_call = Expression::Function(Box::new(Function::new(
26802                                        "IFF".to_string(),
26803                                        vec![cond, this_expr.clone(), Expression::Null(Null)],
26804                                    )));
26805                                    c.this = Some(iff_call);
26806                                }
26807                                Ok(Expression::Count(c))
26808                            }
26809                            other => {
26810                                // Fallback: keep as Filter
26811                                Ok(Expression::Filter(Box::new(crate::expressions::Filter {
26812                                    this: Box::new(other),
26813                                    expression: Box::new(cond),
26814                                })))
26815                            }
26816                        }
26817                    } else {
26818                        Ok(e)
26819                    }
26820                }
26821
26822                Action::AggFilterToIff => {
26823                    // AggFunc.filter -> IFF wrapping: AVG(x) FILTER(WHERE cond) -> AVG(IFF(cond, x, NULL))
26824                    // Helper macro to handle the common AggFunc case
26825                    macro_rules! handle_agg_filter_to_iff {
26826                        ($variant:ident, $agg:expr) => {{
26827                            let mut agg = $agg;
26828                            if let Some(filter_cond) = agg.filter.take() {
26829                                let iff_call = Expression::Function(Box::new(Function::new(
26830                                    "IFF".to_string(),
26831                                    vec![filter_cond, agg.this.clone(), Expression::Null(Null)],
26832                                )));
26833                                agg.this = iff_call;
26834                            }
26835                            Ok(Expression::$variant(agg))
26836                        }};
26837                    }
26838
26839                    match e {
26840                        Expression::Avg(agg) => handle_agg_filter_to_iff!(Avg, agg),
26841                        Expression::Sum(agg) => handle_agg_filter_to_iff!(Sum, agg),
26842                        Expression::Min(agg) => handle_agg_filter_to_iff!(Min, agg),
26843                        Expression::Max(agg) => handle_agg_filter_to_iff!(Max, agg),
26844                        Expression::ArrayAgg(agg) => handle_agg_filter_to_iff!(ArrayAgg, agg),
26845                        Expression::CountIf(agg) => handle_agg_filter_to_iff!(CountIf, agg),
26846                        Expression::Stddev(agg) => handle_agg_filter_to_iff!(Stddev, agg),
26847                        Expression::StddevPop(agg) => handle_agg_filter_to_iff!(StddevPop, agg),
26848                        Expression::StddevSamp(agg) => handle_agg_filter_to_iff!(StddevSamp, agg),
26849                        Expression::Variance(agg) => handle_agg_filter_to_iff!(Variance, agg),
26850                        Expression::VarPop(agg) => handle_agg_filter_to_iff!(VarPop, agg),
26851                        Expression::VarSamp(agg) => handle_agg_filter_to_iff!(VarSamp, agg),
26852                        Expression::Median(agg) => handle_agg_filter_to_iff!(Median, agg),
26853                        Expression::Mode(agg) => handle_agg_filter_to_iff!(Mode, agg),
26854                        Expression::First(agg) => handle_agg_filter_to_iff!(First, agg),
26855                        Expression::Last(agg) => handle_agg_filter_to_iff!(Last, agg),
26856                        Expression::AnyValue(agg) => handle_agg_filter_to_iff!(AnyValue, agg),
26857                        Expression::ApproxDistinct(agg) => {
26858                            handle_agg_filter_to_iff!(ApproxDistinct, agg)
26859                        }
26860                        Expression::Count(mut c) => {
26861                            if let Some(filter_cond) = c.filter.take() {
26862                                if let Some(ref this_expr) = c.this {
26863                                    let iff_call = Expression::Function(Box::new(Function::new(
26864                                        "IFF".to_string(),
26865                                        vec![
26866                                            filter_cond,
26867                                            this_expr.clone(),
26868                                            Expression::Null(Null),
26869                                        ],
26870                                    )));
26871                                    c.this = Some(iff_call);
26872                                }
26873                            }
26874                            Ok(Expression::Count(c))
26875                        }
26876                        other => Ok(other),
26877                    }
26878                }
26879
26880                Action::JsonToGetPath => {
26881                    // JSON_EXTRACT(x, '$.key') -> GET_PATH(PARSE_JSON(x), 'key')
26882                    if let Expression::JsonExtract(je) = e {
26883                        // Convert to PARSE_JSON() wrapper:
26884                        // - JSON(x) -> PARSE_JSON(x)
26885                        // - PARSE_JSON(x) -> keep as-is
26886                        // - anything else -> wrap in PARSE_JSON()
26887                        let this = match &je.this {
26888                            Expression::Function(f)
26889                                if f.name.eq_ignore_ascii_case("JSON") && f.args.len() == 1 =>
26890                            {
26891                                Expression::Function(Box::new(Function::new(
26892                                    "PARSE_JSON".to_string(),
26893                                    f.args.clone(),
26894                                )))
26895                            }
26896                            Expression::Function(f)
26897                                if f.name.eq_ignore_ascii_case("PARSE_JSON") =>
26898                            {
26899                                je.this.clone()
26900                            }
26901                            // GET_PATH result is already JSON, don't wrap
26902                            Expression::Function(f) if f.name.eq_ignore_ascii_case("GET_PATH") => {
26903                                je.this.clone()
26904                            }
26905                            other => {
26906                                // Wrap non-JSON expressions in PARSE_JSON()
26907                                Expression::Function(Box::new(Function::new(
26908                                    "PARSE_JSON".to_string(),
26909                                    vec![other.clone()],
26910                                )))
26911                            }
26912                        };
26913                        // Convert path: extract key from JSONPath or strip $. prefix from string
26914                        let path = match &je.path {
26915                            Expression::JSONPath(jp) => {
26916                                // Extract the key from JSONPath: $root.key -> 'key'
26917                                let mut key_parts = Vec::new();
26918                                for expr in &jp.expressions {
26919                                    match expr {
26920                                        Expression::JSONPathRoot(_) => {} // skip root
26921                                        Expression::JSONPathKey(k) => {
26922                                            if let Expression::Literal(lit) = &*k.this {
26923                                                if let Literal::String(s) = lit.as_ref() {
26924                                                    key_parts.push(s.clone());
26925                                                }
26926                                            }
26927                                        }
26928                                        _ => {}
26929                                    }
26930                                }
26931                                if !key_parts.is_empty() {
26932                                    Expression::Literal(Box::new(Literal::String(
26933                                        key_parts.join("."),
26934                                    )))
26935                                } else {
26936                                    je.path.clone()
26937                                }
26938                            }
26939                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with("$.")) =>
26940                            {
26941                                let Literal::String(s) = lit.as_ref() else {
26942                                    unreachable!()
26943                                };
26944                                let stripped = Self::strip_json_wildcards(&s[2..].to_string());
26945                                Expression::Literal(Box::new(Literal::String(stripped)))
26946                            }
26947                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$')) =>
26948                            {
26949                                let Literal::String(s) = lit.as_ref() else {
26950                                    unreachable!()
26951                                };
26952                                let stripped = Self::strip_json_wildcards(&s[1..].to_string());
26953                                Expression::Literal(Box::new(Literal::String(stripped)))
26954                            }
26955                            _ => je.path.clone(),
26956                        };
26957                        Ok(Expression::Function(Box::new(Function::new(
26958                            "GET_PATH".to_string(),
26959                            vec![this, path],
26960                        ))))
26961                    } else {
26962                        Ok(e)
26963                    }
26964                }
26965
26966                Action::StructToRow => {
26967                    // DuckDB struct/dict -> BigQuery STRUCT(value AS key, ...) / Presto ROW
26968                    // Handles both Expression::Struct and Expression::MapFunc(curly_brace_syntax=true)
26969
26970                    // Extract key-value pairs from either Struct or MapFunc
26971                    let kv_pairs: Option<Vec<(String, Expression)>> = match &e {
26972                        Expression::Struct(s) => Some(
26973                            s.fields
26974                                .iter()
26975                                .map(|(opt_name, field_expr)| {
26976                                    if let Some(name) = opt_name {
26977                                        (name.clone(), field_expr.clone())
26978                                    } else if let Expression::NamedArgument(na) = field_expr {
26979                                        (na.name.name.clone(), na.value.clone())
26980                                    } else {
26981                                        (String::new(), field_expr.clone())
26982                                    }
26983                                })
26984                                .collect(),
26985                        ),
26986                        Expression::MapFunc(m) if m.curly_brace_syntax => Some(
26987                            m.keys
26988                                .iter()
26989                                .zip(m.values.iter())
26990                                .map(|(key, value)| {
26991                                    let key_name = match key {
26992                                        Expression::Literal(lit)
26993                                            if matches!(lit.as_ref(), Literal::String(_)) =>
26994                                        {
26995                                            let Literal::String(s) = lit.as_ref() else {
26996                                                unreachable!()
26997                                            };
26998                                            s.clone()
26999                                        }
27000                                        Expression::Identifier(id) => id.name.clone(),
27001                                        _ => String::new(),
27002                                    };
27003                                    (key_name, value.clone())
27004                                })
27005                                .collect(),
27006                        ),
27007                        _ => None,
27008                    };
27009
27010                    if let Some(pairs) = kv_pairs {
27011                        let mut named_args = Vec::new();
27012                        for (key_name, value) in pairs {
27013                            if matches!(target, DialectType::BigQuery) && !key_name.is_empty() {
27014                                named_args.push(Expression::Alias(Box::new(
27015                                    crate::expressions::Alias::new(
27016                                        value,
27017                                        Identifier::new(key_name),
27018                                    ),
27019                                )));
27020                            } else if matches!(target, DialectType::Presto | DialectType::Trino) {
27021                                named_args.push(value);
27022                            } else {
27023                                named_args.push(value);
27024                            }
27025                        }
27026
27027                        if matches!(target, DialectType::BigQuery) {
27028                            Ok(Expression::Function(Box::new(Function::new(
27029                                "STRUCT".to_string(),
27030                                named_args,
27031                            ))))
27032                        } else if matches!(target, DialectType::Presto | DialectType::Trino) {
27033                            // For Presto/Trino, infer types and wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
27034                            let row_func = Expression::Function(Box::new(Function::new(
27035                                "ROW".to_string(),
27036                                named_args,
27037                            )));
27038
27039                            // Try to infer types for each pair
27040                            let kv_pairs_again: Option<Vec<(String, Expression)>> = match &e {
27041                                Expression::Struct(s) => Some(
27042                                    s.fields
27043                                        .iter()
27044                                        .map(|(opt_name, field_expr)| {
27045                                            if let Some(name) = opt_name {
27046                                                (name.clone(), field_expr.clone())
27047                                            } else if let Expression::NamedArgument(na) = field_expr
27048                                            {
27049                                                (na.name.name.clone(), na.value.clone())
27050                                            } else {
27051                                                (String::new(), field_expr.clone())
27052                                            }
27053                                        })
27054                                        .collect(),
27055                                ),
27056                                Expression::MapFunc(m) if m.curly_brace_syntax => Some(
27057                                    m.keys
27058                                        .iter()
27059                                        .zip(m.values.iter())
27060                                        .map(|(key, value)| {
27061                                            let key_name = match key {
27062                                                Expression::Literal(lit)
27063                                                    if matches!(
27064                                                        lit.as_ref(),
27065                                                        Literal::String(_)
27066                                                    ) =>
27067                                                {
27068                                                    let Literal::String(s) = lit.as_ref() else {
27069                                                        unreachable!()
27070                                                    };
27071                                                    s.clone()
27072                                                }
27073                                                Expression::Identifier(id) => id.name.clone(),
27074                                                _ => String::new(),
27075                                            };
27076                                            (key_name, value.clone())
27077                                        })
27078                                        .collect(),
27079                                ),
27080                                _ => None,
27081                            };
27082
27083                            if let Some(pairs) = kv_pairs_again {
27084                                // Infer types for all values
27085                                let mut all_inferred = true;
27086                                let mut fields = Vec::new();
27087                                for (name, value) in &pairs {
27088                                    let inferred_type = match value {
27089                                        Expression::Literal(lit)
27090                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
27091                                        {
27092                                            let Literal::Number(n) = lit.as_ref() else {
27093                                                unreachable!()
27094                                            };
27095                                            if n.contains('.') {
27096                                                Some(DataType::Double {
27097                                                    precision: None,
27098                                                    scale: None,
27099                                                })
27100                                            } else {
27101                                                Some(DataType::Int {
27102                                                    length: None,
27103                                                    integer_spelling: true,
27104                                                })
27105                                            }
27106                                        }
27107                                        Expression::Literal(lit)
27108                                            if matches!(lit.as_ref(), Literal::String(_)) =>
27109                                        {
27110                                            Some(DataType::VarChar {
27111                                                length: None,
27112                                                parenthesized_length: false,
27113                                            })
27114                                        }
27115                                        Expression::Boolean(_) => Some(DataType::Boolean),
27116                                        _ => None,
27117                                    };
27118                                    if let Some(dt) = inferred_type {
27119                                        fields.push(crate::expressions::StructField::new(
27120                                            name.clone(),
27121                                            dt,
27122                                        ));
27123                                    } else {
27124                                        all_inferred = false;
27125                                        break;
27126                                    }
27127                                }
27128
27129                                if all_inferred && !fields.is_empty() {
27130                                    let row_type = DataType::Struct {
27131                                        fields,
27132                                        nested: true,
27133                                    };
27134                                    Ok(Expression::Cast(Box::new(Cast {
27135                                        this: row_func,
27136                                        to: row_type,
27137                                        trailing_comments: Vec::new(),
27138                                        double_colon_syntax: false,
27139                                        format: None,
27140                                        default: None,
27141                                        inferred_type: None,
27142                                    })))
27143                                } else {
27144                                    Ok(row_func)
27145                                }
27146                            } else {
27147                                Ok(row_func)
27148                            }
27149                        } else {
27150                            Ok(Expression::Function(Box::new(Function::new(
27151                                "ROW".to_string(),
27152                                named_args,
27153                            ))))
27154                        }
27155                    } else {
27156                        Ok(e)
27157                    }
27158                }
27159
27160                Action::SparkStructConvert => {
27161                    // Spark STRUCT(val AS name, ...) -> Presto CAST(ROW(...) AS ROW(name TYPE, ...))
27162                    // or DuckDB {'name': val, ...}
27163                    if let Expression::Function(f) = e {
27164                        // Extract name-value pairs from aliased args
27165                        let mut pairs: Vec<(String, Expression)> = Vec::new();
27166                        for arg in &f.args {
27167                            match arg {
27168                                Expression::Alias(a) => {
27169                                    pairs.push((a.alias.name.clone(), a.this.clone()));
27170                                }
27171                                _ => {
27172                                    pairs.push((String::new(), arg.clone()));
27173                                }
27174                            }
27175                        }
27176
27177                        match target {
27178                            DialectType::DuckDB => {
27179                                // Convert to DuckDB struct literal {'name': value, ...}
27180                                let mut keys = Vec::new();
27181                                let mut values = Vec::new();
27182                                for (name, value) in &pairs {
27183                                    keys.push(Expression::Literal(Box::new(Literal::String(
27184                                        name.clone(),
27185                                    ))));
27186                                    values.push(value.clone());
27187                                }
27188                                Ok(Expression::MapFunc(Box::new(
27189                                    crate::expressions::MapConstructor {
27190                                        keys,
27191                                        values,
27192                                        curly_brace_syntax: true,
27193                                        with_map_keyword: false,
27194                                    },
27195                                )))
27196                            }
27197                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
27198                                // Convert to CAST(ROW(val1, val2) AS ROW(name1 TYPE1, name2 TYPE2))
27199                                let row_args: Vec<Expression> =
27200                                    pairs.iter().map(|(_, v)| v.clone()).collect();
27201                                let row_func = Expression::Function(Box::new(Function::new(
27202                                    "ROW".to_string(),
27203                                    row_args,
27204                                )));
27205
27206                                // Infer types
27207                                let mut all_inferred = true;
27208                                let mut fields = Vec::new();
27209                                for (name, value) in &pairs {
27210                                    let inferred_type = match value {
27211                                        Expression::Literal(lit)
27212                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
27213                                        {
27214                                            let Literal::Number(n) = lit.as_ref() else {
27215                                                unreachable!()
27216                                            };
27217                                            if n.contains('.') {
27218                                                Some(DataType::Double {
27219                                                    precision: None,
27220                                                    scale: None,
27221                                                })
27222                                            } else {
27223                                                Some(DataType::Int {
27224                                                    length: None,
27225                                                    integer_spelling: true,
27226                                                })
27227                                            }
27228                                        }
27229                                        Expression::Literal(lit)
27230                                            if matches!(lit.as_ref(), Literal::String(_)) =>
27231                                        {
27232                                            Some(DataType::VarChar {
27233                                                length: None,
27234                                                parenthesized_length: false,
27235                                            })
27236                                        }
27237                                        Expression::Boolean(_) => Some(DataType::Boolean),
27238                                        _ => None,
27239                                    };
27240                                    if let Some(dt) = inferred_type {
27241                                        fields.push(crate::expressions::StructField::new(
27242                                            name.clone(),
27243                                            dt,
27244                                        ));
27245                                    } else {
27246                                        all_inferred = false;
27247                                        break;
27248                                    }
27249                                }
27250
27251                                if all_inferred && !fields.is_empty() {
27252                                    let row_type = DataType::Struct {
27253                                        fields,
27254                                        nested: true,
27255                                    };
27256                                    Ok(Expression::Cast(Box::new(Cast {
27257                                        this: row_func,
27258                                        to: row_type,
27259                                        trailing_comments: Vec::new(),
27260                                        double_colon_syntax: false,
27261                                        format: None,
27262                                        default: None,
27263                                        inferred_type: None,
27264                                    })))
27265                                } else {
27266                                    Ok(row_func)
27267                                }
27268                            }
27269                            _ => Ok(Expression::Function(f)),
27270                        }
27271                    } else {
27272                        Ok(e)
27273                    }
27274                }
27275
27276                Action::ApproxCountDistinctToApproxDistinct => {
27277                    // APPROX_COUNT_DISTINCT(x) -> APPROX_DISTINCT(x)
27278                    if let Expression::ApproxCountDistinct(f) = e {
27279                        Ok(Expression::ApproxDistinct(f))
27280                    } else {
27281                        Ok(e)
27282                    }
27283                }
27284
27285                Action::CollectListToArrayAgg => {
27286                    // COLLECT_LIST(x) -> ARRAY_AGG(x) FILTER(WHERE x IS NOT NULL)
27287                    if let Expression::AggregateFunction(f) = e {
27288                        let filter_expr = if !f.args.is_empty() {
27289                            let arg = f.args[0].clone();
27290                            Some(Expression::IsNull(Box::new(crate::expressions::IsNull {
27291                                this: arg,
27292                                not: true,
27293                                postfix_form: false,
27294                            })))
27295                        } else {
27296                            None
27297                        };
27298                        let agg = crate::expressions::AggFunc {
27299                            this: if f.args.is_empty() {
27300                                Expression::Null(crate::expressions::Null)
27301                            } else {
27302                                f.args[0].clone()
27303                            },
27304                            distinct: f.distinct,
27305                            order_by: f.order_by.clone(),
27306                            filter: filter_expr,
27307                            ignore_nulls: None,
27308                            name: None,
27309                            having_max: None,
27310                            limit: None,
27311                            inferred_type: None,
27312                        };
27313                        Ok(Expression::ArrayAgg(Box::new(agg)))
27314                    } else {
27315                        Ok(e)
27316                    }
27317                }
27318
27319                Action::CollectSetConvert => {
27320                    // COLLECT_SET(x) -> target-specific
27321                    if let Expression::AggregateFunction(f) = e {
27322                        match target {
27323                            DialectType::Presto => Ok(Expression::AggregateFunction(Box::new(
27324                                crate::expressions::AggregateFunction {
27325                                    name: "SET_AGG".to_string(),
27326                                    args: f.args,
27327                                    distinct: false,
27328                                    order_by: f.order_by,
27329                                    filter: f.filter,
27330                                    limit: f.limit,
27331                                    ignore_nulls: f.ignore_nulls,
27332                                    inferred_type: None,
27333                                },
27334                            ))),
27335                            DialectType::Snowflake => Ok(Expression::AggregateFunction(Box::new(
27336                                crate::expressions::AggregateFunction {
27337                                    name: "ARRAY_UNIQUE_AGG".to_string(),
27338                                    args: f.args,
27339                                    distinct: false,
27340                                    order_by: f.order_by,
27341                                    filter: f.filter,
27342                                    limit: f.limit,
27343                                    ignore_nulls: f.ignore_nulls,
27344                                    inferred_type: None,
27345                                },
27346                            ))),
27347                            DialectType::Trino | DialectType::DuckDB => {
27348                                let agg = crate::expressions::AggFunc {
27349                                    this: if f.args.is_empty() {
27350                                        Expression::Null(crate::expressions::Null)
27351                                    } else {
27352                                        f.args[0].clone()
27353                                    },
27354                                    distinct: true,
27355                                    order_by: Vec::new(),
27356                                    filter: None,
27357                                    ignore_nulls: None,
27358                                    name: None,
27359                                    having_max: None,
27360                                    limit: None,
27361                                    inferred_type: None,
27362                                };
27363                                Ok(Expression::ArrayAgg(Box::new(agg)))
27364                            }
27365                            _ => Ok(Expression::AggregateFunction(f)),
27366                        }
27367                    } else {
27368                        Ok(e)
27369                    }
27370                }
27371
27372                Action::PercentileConvert => {
27373                    // PERCENTILE(x, 0.5) -> QUANTILE(x, 0.5) / APPROX_PERCENTILE(x, 0.5)
27374                    if let Expression::AggregateFunction(f) = e {
27375                        let name = match target {
27376                            DialectType::DuckDB => "QUANTILE",
27377                            DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
27378                            _ => "PERCENTILE",
27379                        };
27380                        Ok(Expression::AggregateFunction(Box::new(
27381                            crate::expressions::AggregateFunction {
27382                                name: name.to_string(),
27383                                args: f.args,
27384                                distinct: f.distinct,
27385                                order_by: f.order_by,
27386                                filter: f.filter,
27387                                limit: f.limit,
27388                                ignore_nulls: f.ignore_nulls,
27389                                inferred_type: None,
27390                            },
27391                        )))
27392                    } else {
27393                        Ok(e)
27394                    }
27395                }
27396
27397                Action::CorrIsnanWrap => {
27398                    // CORR(a, b) -> CASE WHEN ISNAN(CORR(a, b)) THEN NULL ELSE CORR(a, b) END
27399                    // The CORR expression could be AggregateFunction, WindowFunction, or Filter-wrapped
27400                    let corr_clone = e.clone();
27401                    let isnan = Expression::Function(Box::new(Function::new(
27402                        "ISNAN".to_string(),
27403                        vec![corr_clone.clone()],
27404                    )));
27405                    let case_expr = Expression::Case(Box::new(Case {
27406                        operand: None,
27407                        whens: vec![(isnan, Expression::Null(crate::expressions::Null))],
27408                        else_: Some(corr_clone),
27409                        comments: Vec::new(),
27410                        inferred_type: None,
27411                    }));
27412                    Ok(case_expr)
27413                }
27414
27415                Action::TruncToDateTrunc => {
27416                    // TRUNC(timestamp, 'MONTH') -> DATE_TRUNC('MONTH', timestamp)
27417                    if let Expression::Function(f) = e {
27418                        if f.args.len() == 2 {
27419                            let timestamp = f.args[0].clone();
27420                            let unit_expr = f.args[1].clone();
27421
27422                            if matches!(target, DialectType::ClickHouse) {
27423                                // For ClickHouse, produce Expression::DateTrunc which the generator
27424                                // outputs as DATE_TRUNC(...) without going through the ClickHouse
27425                                // target transform that would convert it to dateTrunc
27426                                let unit_str = Self::get_unit_str_static(&unit_expr);
27427                                let dt_field = match unit_str.as_str() {
27428                                    "YEAR" => DateTimeField::Year,
27429                                    "MONTH" => DateTimeField::Month,
27430                                    "DAY" => DateTimeField::Day,
27431                                    "HOUR" => DateTimeField::Hour,
27432                                    "MINUTE" => DateTimeField::Minute,
27433                                    "SECOND" => DateTimeField::Second,
27434                                    "WEEK" => DateTimeField::Week,
27435                                    "QUARTER" => DateTimeField::Quarter,
27436                                    _ => DateTimeField::Custom(unit_str),
27437                                };
27438                                Ok(Expression::DateTrunc(Box::new(
27439                                    crate::expressions::DateTruncFunc {
27440                                        this: timestamp,
27441                                        unit: dt_field,
27442                                    },
27443                                )))
27444                            } else {
27445                                let new_args = vec![unit_expr, timestamp];
27446                                Ok(Expression::Function(Box::new(Function::new(
27447                                    "DATE_TRUNC".to_string(),
27448                                    new_args,
27449                                ))))
27450                            }
27451                        } else {
27452                            Ok(Expression::Function(f))
27453                        }
27454                    } else {
27455                        Ok(e)
27456                    }
27457                }
27458
27459                Action::ArrayContainsConvert => {
27460                    if let Expression::ArrayContains(f) = e {
27461                        match target {
27462                            DialectType::Presto | DialectType::Trino => {
27463                                // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val)
27464                                Ok(Expression::Function(Box::new(Function::new(
27465                                    "CONTAINS".to_string(),
27466                                    vec![f.this, f.expression],
27467                                ))))
27468                            }
27469                            DialectType::Snowflake => {
27470                                // ARRAY_CONTAINS(arr, val) -> ARRAY_CONTAINS(CAST(val AS VARIANT), arr)
27471                                let cast_val =
27472                                    Expression::Cast(Box::new(crate::expressions::Cast {
27473                                        this: f.expression,
27474                                        to: crate::expressions::DataType::Custom {
27475                                            name: "VARIANT".to_string(),
27476                                        },
27477                                        trailing_comments: Vec::new(),
27478                                        double_colon_syntax: false,
27479                                        format: None,
27480                                        default: None,
27481                                        inferred_type: None,
27482                                    }));
27483                                Ok(Expression::Function(Box::new(Function::new(
27484                                    "ARRAY_CONTAINS".to_string(),
27485                                    vec![cast_val, f.this],
27486                                ))))
27487                            }
27488                            _ => Ok(Expression::ArrayContains(f)),
27489                        }
27490                    } else {
27491                        Ok(e)
27492                    }
27493                }
27494
27495                Action::ArrayExceptConvert => {
27496                    if let Expression::ArrayExcept(f) = e {
27497                        let source_arr = f.this;
27498                        let exclude_arr = f.expression;
27499                        match target {
27500                            DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
27501                                // Snowflake ARRAY_EXCEPT -> DuckDB bag semantics:
27502                                // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
27503                                // ELSE LIST_TRANSFORM(LIST_FILTER(
27504                                //   LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source))),
27505                                //   pair -> (LENGTH(LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1]))
27506                                //            > LENGTH(LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])))),
27507                                //   pair -> pair[1])
27508                                // END
27509
27510                                // Build null check
27511                                let source_is_null =
27512                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
27513                                        this: source_arr.clone(),
27514                                        not: false,
27515                                        postfix_form: false,
27516                                    }));
27517                                let exclude_is_null =
27518                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
27519                                        this: exclude_arr.clone(),
27520                                        not: false,
27521                                        postfix_form: false,
27522                                    }));
27523                                let null_check =
27524                                    Expression::Or(Box::new(crate::expressions::BinaryOp {
27525                                        left: source_is_null,
27526                                        right: exclude_is_null,
27527                                        left_comments: vec![],
27528                                        operator_comments: vec![],
27529                                        trailing_comments: vec![],
27530                                        inferred_type: None,
27531                                    }));
27532
27533                                // GENERATE_SERIES(1, LENGTH(source))
27534                                let gen_series = Expression::Function(Box::new(Function::new(
27535                                    "GENERATE_SERIES".to_string(),
27536                                    vec![
27537                                        Expression::number(1),
27538                                        Expression::Function(Box::new(Function::new(
27539                                            "LENGTH".to_string(),
27540                                            vec![source_arr.clone()],
27541                                        ))),
27542                                    ],
27543                                )));
27544
27545                                // LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source)))
27546                                let list_zip = Expression::Function(Box::new(Function::new(
27547                                    "LIST_ZIP".to_string(),
27548                                    vec![source_arr.clone(), gen_series],
27549                                )));
27550
27551                                // pair[1] and pair[2]
27552                                let pair_col = Expression::column("pair");
27553                                let pair_1 = Expression::Subscript(Box::new(
27554                                    crate::expressions::Subscript {
27555                                        this: pair_col.clone(),
27556                                        index: Expression::number(1),
27557                                    },
27558                                ));
27559                                let pair_2 = Expression::Subscript(Box::new(
27560                                    crate::expressions::Subscript {
27561                                        this: pair_col.clone(),
27562                                        index: Expression::number(2),
27563                                    },
27564                                ));
27565
27566                                // source[1:pair[2]]
27567                                let source_slice = Expression::ArraySlice(Box::new(
27568                                    crate::expressions::ArraySlice {
27569                                        this: source_arr.clone(),
27570                                        start: Some(Expression::number(1)),
27571                                        end: Some(pair_2),
27572                                    },
27573                                ));
27574
27575                                let e_col = Expression::column("e");
27576
27577                                // e -> e IS NOT DISTINCT FROM pair[1]
27578                                let inner_lambda1 =
27579                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27580                                        parameters: vec![crate::expressions::Identifier::new("e")],
27581                                        body: Expression::NullSafeEq(Box::new(
27582                                            crate::expressions::BinaryOp {
27583                                                left: e_col.clone(),
27584                                                right: pair_1.clone(),
27585                                                left_comments: vec![],
27586                                                operator_comments: vec![],
27587                                                trailing_comments: vec![],
27588                                                inferred_type: None,
27589                                            },
27590                                        )),
27591                                        colon: false,
27592                                        parameter_types: vec![],
27593                                    }));
27594
27595                                // LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
27596                                let inner_filter1 = Expression::Function(Box::new(Function::new(
27597                                    "LIST_FILTER".to_string(),
27598                                    vec![source_slice, inner_lambda1],
27599                                )));
27600
27601                                // LENGTH(LIST_FILTER(source[1:pair[2]], ...))
27602                                let len1 = Expression::Function(Box::new(Function::new(
27603                                    "LENGTH".to_string(),
27604                                    vec![inner_filter1],
27605                                )));
27606
27607                                // e -> e IS NOT DISTINCT FROM pair[1]
27608                                let inner_lambda2 =
27609                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27610                                        parameters: vec![crate::expressions::Identifier::new("e")],
27611                                        body: Expression::NullSafeEq(Box::new(
27612                                            crate::expressions::BinaryOp {
27613                                                left: e_col,
27614                                                right: pair_1.clone(),
27615                                                left_comments: vec![],
27616                                                operator_comments: vec![],
27617                                                trailing_comments: vec![],
27618                                                inferred_type: None,
27619                                            },
27620                                        )),
27621                                        colon: false,
27622                                        parameter_types: vec![],
27623                                    }));
27624
27625                                // LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])
27626                                let inner_filter2 = Expression::Function(Box::new(Function::new(
27627                                    "LIST_FILTER".to_string(),
27628                                    vec![exclude_arr.clone(), inner_lambda2],
27629                                )));
27630
27631                                // LENGTH(LIST_FILTER(exclude, ...))
27632                                let len2 = Expression::Function(Box::new(Function::new(
27633                                    "LENGTH".to_string(),
27634                                    vec![inner_filter2],
27635                                )));
27636
27637                                // (LENGTH(...) > LENGTH(...))
27638                                let cond = Expression::Paren(Box::new(Paren {
27639                                    this: Expression::Gt(Box::new(crate::expressions::BinaryOp {
27640                                        left: len1,
27641                                        right: len2,
27642                                        left_comments: vec![],
27643                                        operator_comments: vec![],
27644                                        trailing_comments: vec![],
27645                                        inferred_type: None,
27646                                    })),
27647                                    trailing_comments: vec![],
27648                                }));
27649
27650                                // pair -> (condition)
27651                                let filter_lambda =
27652                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27653                                        parameters: vec![crate::expressions::Identifier::new(
27654                                            "pair",
27655                                        )],
27656                                        body: cond,
27657                                        colon: false,
27658                                        parameter_types: vec![],
27659                                    }));
27660
27661                                // LIST_FILTER(LIST_ZIP(...), pair -> ...)
27662                                let outer_filter = Expression::Function(Box::new(Function::new(
27663                                    "LIST_FILTER".to_string(),
27664                                    vec![list_zip, filter_lambda],
27665                                )));
27666
27667                                // pair -> pair[1]
27668                                let transform_lambda =
27669                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27670                                        parameters: vec![crate::expressions::Identifier::new(
27671                                            "pair",
27672                                        )],
27673                                        body: pair_1,
27674                                        colon: false,
27675                                        parameter_types: vec![],
27676                                    }));
27677
27678                                // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
27679                                let list_transform = Expression::Function(Box::new(Function::new(
27680                                    "LIST_TRANSFORM".to_string(),
27681                                    vec![outer_filter, transform_lambda],
27682                                )));
27683
27684                                Ok(Expression::Case(Box::new(Case {
27685                                    operand: None,
27686                                    whens: vec![(null_check, Expression::Null(Null))],
27687                                    else_: Some(list_transform),
27688                                    comments: Vec::new(),
27689                                    inferred_type: None,
27690                                })))
27691                            }
27692                            DialectType::DuckDB => {
27693                                // ARRAY_EXCEPT(source, exclude) -> set semantics for DuckDB:
27694                                // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
27695                                // ELSE LIST_FILTER(LIST_DISTINCT(source),
27696                                //   e -> LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)) = 0)
27697                                // END
27698
27699                                // Build: source IS NULL
27700                                let source_is_null =
27701                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
27702                                        this: source_arr.clone(),
27703                                        not: false,
27704                                        postfix_form: false,
27705                                    }));
27706                                // Build: exclude IS NULL
27707                                let exclude_is_null =
27708                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
27709                                        this: exclude_arr.clone(),
27710                                        not: false,
27711                                        postfix_form: false,
27712                                    }));
27713                                // source IS NULL OR exclude IS NULL
27714                                let null_check =
27715                                    Expression::Or(Box::new(crate::expressions::BinaryOp {
27716                                        left: source_is_null,
27717                                        right: exclude_is_null,
27718                                        left_comments: vec![],
27719                                        operator_comments: vec![],
27720                                        trailing_comments: vec![],
27721                                        inferred_type: None,
27722                                    }));
27723
27724                                // LIST_DISTINCT(source)
27725                                let list_distinct = Expression::Function(Box::new(Function::new(
27726                                    "LIST_DISTINCT".to_string(),
27727                                    vec![source_arr.clone()],
27728                                )));
27729
27730                                // x IS NOT DISTINCT FROM e
27731                                let x_col = Expression::column("x");
27732                                let e_col = Expression::column("e");
27733                                let is_not_distinct = Expression::NullSafeEq(Box::new(
27734                                    crate::expressions::BinaryOp {
27735                                        left: x_col,
27736                                        right: e_col.clone(),
27737                                        left_comments: vec![],
27738                                        operator_comments: vec![],
27739                                        trailing_comments: vec![],
27740                                        inferred_type: None,
27741                                    },
27742                                ));
27743
27744                                // x -> x IS NOT DISTINCT FROM e
27745                                let inner_lambda =
27746                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27747                                        parameters: vec![crate::expressions::Identifier::new("x")],
27748                                        body: is_not_distinct,
27749                                        colon: false,
27750                                        parameter_types: vec![],
27751                                    }));
27752
27753                                // LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)
27754                                let inner_list_filter =
27755                                    Expression::Function(Box::new(Function::new(
27756                                        "LIST_FILTER".to_string(),
27757                                        vec![exclude_arr.clone(), inner_lambda],
27758                                    )));
27759
27760                                // LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e))
27761                                let len_inner = Expression::Function(Box::new(Function::new(
27762                                    "LENGTH".to_string(),
27763                                    vec![inner_list_filter],
27764                                )));
27765
27766                                // LENGTH(...) = 0
27767                                let eq_zero =
27768                                    Expression::Eq(Box::new(crate::expressions::BinaryOp {
27769                                        left: len_inner,
27770                                        right: Expression::number(0),
27771                                        left_comments: vec![],
27772                                        operator_comments: vec![],
27773                                        trailing_comments: vec![],
27774                                        inferred_type: None,
27775                                    }));
27776
27777                                // e -> LENGTH(LIST_FILTER(...)) = 0
27778                                let outer_lambda =
27779                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27780                                        parameters: vec![crate::expressions::Identifier::new("e")],
27781                                        body: eq_zero,
27782                                        colon: false,
27783                                        parameter_types: vec![],
27784                                    }));
27785
27786                                // LIST_FILTER(LIST_DISTINCT(source), e -> ...)
27787                                let outer_list_filter =
27788                                    Expression::Function(Box::new(Function::new(
27789                                        "LIST_FILTER".to_string(),
27790                                        vec![list_distinct, outer_lambda],
27791                                    )));
27792
27793                                // CASE WHEN ... IS NULL ... THEN NULL ELSE LIST_FILTER(...) END
27794                                Ok(Expression::Case(Box::new(Case {
27795                                    operand: None,
27796                                    whens: vec![(null_check, Expression::Null(Null))],
27797                                    else_: Some(outer_list_filter),
27798                                    comments: Vec::new(),
27799                                    inferred_type: None,
27800                                })))
27801                            }
27802                            DialectType::Snowflake => {
27803                                // Snowflake: ARRAY_EXCEPT(source, exclude) - keep as-is
27804                                Ok(Expression::ArrayExcept(Box::new(
27805                                    crate::expressions::BinaryFunc {
27806                                        this: source_arr,
27807                                        expression: exclude_arr,
27808                                        original_name: None,
27809                                        inferred_type: None,
27810                                    },
27811                                )))
27812                            }
27813                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
27814                                // Presto/Trino: ARRAY_EXCEPT(source, exclude) - keep function name, array syntax already converted
27815                                Ok(Expression::Function(Box::new(Function::new(
27816                                    "ARRAY_EXCEPT".to_string(),
27817                                    vec![source_arr, exclude_arr],
27818                                ))))
27819                            }
27820                            _ => Ok(Expression::ArrayExcept(Box::new(
27821                                crate::expressions::BinaryFunc {
27822                                    this: source_arr,
27823                                    expression: exclude_arr,
27824                                    original_name: None,
27825                                    inferred_type: None,
27826                                },
27827                            ))),
27828                        }
27829                    } else {
27830                        Ok(e)
27831                    }
27832                }
27833
27834                Action::RegexpLikeExasolAnchor => {
27835                    // RegexpLike -> Exasol: wrap pattern with .*...*
27836                    // Exasol REGEXP_LIKE does full-string match, but RLIKE/REGEXP from other
27837                    // dialects does partial match, so we need to anchor with .* on both sides
27838                    if let Expression::RegexpLike(mut f) = e {
27839                        match &f.pattern {
27840                            Expression::Literal(lit)
27841                                if matches!(lit.as_ref(), Literal::String(_)) =>
27842                            {
27843                                let Literal::String(s) = lit.as_ref() else {
27844                                    unreachable!()
27845                                };
27846                                // String literal: wrap with .*...*
27847                                f.pattern = Expression::Literal(Box::new(Literal::String(
27848                                    format!(".*{}.*", s),
27849                                )));
27850                            }
27851                            _ => {
27852                                // Non-literal: wrap with CONCAT('.*', pattern, '.*')
27853                                f.pattern =
27854                                    Expression::Paren(Box::new(crate::expressions::Paren {
27855                                        this: Expression::Concat(Box::new(
27856                                            crate::expressions::BinaryOp {
27857                                                left: Expression::Concat(Box::new(
27858                                                    crate::expressions::BinaryOp {
27859                                                        left: Expression::Literal(Box::new(
27860                                                            Literal::String(".*".to_string()),
27861                                                        )),
27862                                                        right: f.pattern,
27863                                                        left_comments: vec![],
27864                                                        operator_comments: vec![],
27865                                                        trailing_comments: vec![],
27866                                                        inferred_type: None,
27867                                                    },
27868                                                )),
27869                                                right: Expression::Literal(Box::new(
27870                                                    Literal::String(".*".to_string()),
27871                                                )),
27872                                                left_comments: vec![],
27873                                                operator_comments: vec![],
27874                                                trailing_comments: vec![],
27875                                                inferred_type: None,
27876                                            },
27877                                        )),
27878                                        trailing_comments: vec![],
27879                                    }));
27880                            }
27881                        }
27882                        Ok(Expression::RegexpLike(f))
27883                    } else {
27884                        Ok(e)
27885                    }
27886                }
27887
27888                Action::ArrayPositionSnowflakeSwap => {
27889                    // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
27890                    if let Expression::ArrayPosition(f) = e {
27891                        Ok(Expression::ArrayPosition(Box::new(
27892                            crate::expressions::BinaryFunc {
27893                                this: f.expression,
27894                                expression: f.this,
27895                                original_name: f.original_name,
27896                                inferred_type: f.inferred_type,
27897                            },
27898                        )))
27899                    } else {
27900                        Ok(e)
27901                    }
27902                }
27903
27904                Action::SnowflakeArrayPositionToDuckDB => {
27905                    // Snowflake ARRAY_POSITION(value, array) -> DuckDB ARRAY_POSITION(array, value) - 1
27906                    // Snowflake uses 0-based indexing, DuckDB uses 1-based
27907                    // The parser has this=value, expression=array (Snowflake order)
27908                    if let Expression::ArrayPosition(f) = e {
27909                        // Create ARRAY_POSITION(array, value) in standard order
27910                        let standard_pos =
27911                            Expression::ArrayPosition(Box::new(crate::expressions::BinaryFunc {
27912                                this: f.expression, // array
27913                                expression: f.this, // value
27914                                original_name: f.original_name,
27915                                inferred_type: f.inferred_type,
27916                            }));
27917                        // Subtract 1 for zero-based indexing
27918                        Ok(Expression::Sub(Box::new(BinaryOp {
27919                            left: standard_pos,
27920                            right: Expression::number(1),
27921                            left_comments: vec![],
27922                            operator_comments: vec![],
27923                            trailing_comments: vec![],
27924                            inferred_type: None,
27925                        })))
27926                    } else {
27927                        Ok(e)
27928                    }
27929                }
27930
27931                Action::ArrayDistinctConvert => {
27932                    // ARRAY_DISTINCT(arr) -> DuckDB NULL-aware CASE:
27933                    // CASE WHEN ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
27934                    //   THEN LIST_APPEND(LIST_DISTINCT(LIST_FILTER(arr, _u -> NOT _u IS NULL)), NULL)
27935                    //   ELSE LIST_DISTINCT(arr)
27936                    // END
27937                    if let Expression::ArrayDistinct(f) = e {
27938                        let arr = f.this;
27939
27940                        // ARRAY_LENGTH(arr)
27941                        let array_length = Expression::Function(Box::new(Function::new(
27942                            "ARRAY_LENGTH".to_string(),
27943                            vec![arr.clone()],
27944                        )));
27945                        // LIST_COUNT(arr)
27946                        let list_count = Expression::Function(Box::new(Function::new(
27947                            "LIST_COUNT".to_string(),
27948                            vec![arr.clone()],
27949                        )));
27950                        // ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
27951                        let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
27952                            left: array_length,
27953                            right: list_count,
27954                            left_comments: vec![],
27955                            operator_comments: vec![],
27956                            trailing_comments: vec![],
27957                            inferred_type: None,
27958                        }));
27959
27960                        // _u column
27961                        let u_col = Expression::column("_u");
27962                        // NOT _u IS NULL
27963                        let u_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
27964                            this: u_col.clone(),
27965                            not: false,
27966                            postfix_form: false,
27967                        }));
27968                        let not_u_is_null =
27969                            Expression::Not(Box::new(crate::expressions::UnaryOp {
27970                                this: u_is_null,
27971                                inferred_type: None,
27972                            }));
27973                        // _u -> NOT _u IS NULL
27974                        let filter_lambda =
27975                            Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
27976                                parameters: vec![crate::expressions::Identifier::new("_u")],
27977                                body: not_u_is_null,
27978                                colon: false,
27979                                parameter_types: vec![],
27980                            }));
27981                        // LIST_FILTER(arr, _u -> NOT _u IS NULL)
27982                        let list_filter = Expression::Function(Box::new(Function::new(
27983                            "LIST_FILTER".to_string(),
27984                            vec![arr.clone(), filter_lambda],
27985                        )));
27986                        // LIST_DISTINCT(LIST_FILTER(arr, ...))
27987                        let list_distinct_filtered = Expression::Function(Box::new(Function::new(
27988                            "LIST_DISTINCT".to_string(),
27989                            vec![list_filter],
27990                        )));
27991                        // LIST_APPEND(LIST_DISTINCT(LIST_FILTER(...)), NULL)
27992                        let list_append = Expression::Function(Box::new(Function::new(
27993                            "LIST_APPEND".to_string(),
27994                            vec![list_distinct_filtered, Expression::Null(Null)],
27995                        )));
27996
27997                        // LIST_DISTINCT(arr)
27998                        let list_distinct = Expression::Function(Box::new(Function::new(
27999                            "LIST_DISTINCT".to_string(),
28000                            vec![arr],
28001                        )));
28002
28003                        // CASE WHEN neq THEN list_append ELSE list_distinct END
28004                        Ok(Expression::Case(Box::new(Case {
28005                            operand: None,
28006                            whens: vec![(neq, list_append)],
28007                            else_: Some(list_distinct),
28008                            comments: Vec::new(),
28009                            inferred_type: None,
28010                        })))
28011                    } else {
28012                        Ok(e)
28013                    }
28014                }
28015
28016                Action::ArrayDistinctClickHouse => {
28017                    // ARRAY_DISTINCT(arr) -> arrayDistinct(arr) for ClickHouse
28018                    if let Expression::ArrayDistinct(f) = e {
28019                        Ok(Expression::Function(Box::new(Function::new(
28020                            "arrayDistinct".to_string(),
28021                            vec![f.this],
28022                        ))))
28023                    } else {
28024                        Ok(e)
28025                    }
28026                }
28027
28028                Action::ArrayContainsDuckDBConvert => {
28029                    // Snowflake ARRAY_CONTAINS(value, array) -> DuckDB NULL-aware:
28030                    // CASE WHEN value IS NULL
28031                    //   THEN NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
28032                    //   ELSE ARRAY_CONTAINS(array, value)
28033                    // END
28034                    // Note: In Rust AST from Snowflake parse, this=value (first arg), expression=array (second arg)
28035                    if let Expression::ArrayContains(f) = e {
28036                        let value = f.this;
28037                        let array = f.expression;
28038
28039                        // value IS NULL
28040                        let value_is_null =
28041                            Expression::IsNull(Box::new(crate::expressions::IsNull {
28042                                this: value.clone(),
28043                                not: false,
28044                                postfix_form: false,
28045                            }));
28046
28047                        // ARRAY_LENGTH(array)
28048                        let array_length = Expression::Function(Box::new(Function::new(
28049                            "ARRAY_LENGTH".to_string(),
28050                            vec![array.clone()],
28051                        )));
28052                        // LIST_COUNT(array)
28053                        let list_count = Expression::Function(Box::new(Function::new(
28054                            "LIST_COUNT".to_string(),
28055                            vec![array.clone()],
28056                        )));
28057                        // ARRAY_LENGTH(array) <> LIST_COUNT(array)
28058                        let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
28059                            left: array_length,
28060                            right: list_count,
28061                            left_comments: vec![],
28062                            operator_comments: vec![],
28063                            trailing_comments: vec![],
28064                            inferred_type: None,
28065                        }));
28066                        // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
28067                        let nullif = Expression::Nullif(Box::new(crate::expressions::Nullif {
28068                            this: Box::new(neq),
28069                            expression: Box::new(Expression::Boolean(
28070                                crate::expressions::BooleanLiteral { value: false },
28071                            )),
28072                        }));
28073
28074                        // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
28075                        let array_contains = Expression::Function(Box::new(Function::new(
28076                            "ARRAY_CONTAINS".to_string(),
28077                            vec![array, value],
28078                        )));
28079
28080                        // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
28081                        Ok(Expression::Case(Box::new(Case {
28082                            operand: None,
28083                            whens: vec![(value_is_null, nullif)],
28084                            else_: Some(array_contains),
28085                            comments: Vec::new(),
28086                            inferred_type: None,
28087                        })))
28088                    } else {
28089                        Ok(e)
28090                    }
28091                }
28092
28093                Action::StrPositionExpand => {
28094                    // StrPosition with position arg -> complex STRPOS expansion for Presto/DuckDB
28095                    // For Presto: IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
28096                    // For DuckDB: CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
28097                    if let Expression::StrPosition(sp) = e {
28098                        let crate::expressions::StrPosition {
28099                            this,
28100                            substr,
28101                            position,
28102                            occurrence,
28103                        } = *sp;
28104                        let string = *this;
28105                        let substr_expr = match substr {
28106                            Some(s) => *s,
28107                            None => Expression::Null(Null),
28108                        };
28109                        let pos = match position {
28110                            Some(p) => *p,
28111                            None => Expression::number(1),
28112                        };
28113
28114                        // SUBSTRING(string, pos)
28115                        let substring_call = Expression::Function(Box::new(Function::new(
28116                            "SUBSTRING".to_string(),
28117                            vec![string.clone(), pos.clone()],
28118                        )));
28119                        // STRPOS(SUBSTRING(string, pos), substr)
28120                        let strpos_call = Expression::Function(Box::new(Function::new(
28121                            "STRPOS".to_string(),
28122                            vec![substring_call, substr_expr.clone()],
28123                        )));
28124                        // STRPOS(...) + pos - 1
28125                        let pos_adjusted =
28126                            Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
28127                                Expression::Add(Box::new(crate::expressions::BinaryOp::new(
28128                                    strpos_call.clone(),
28129                                    pos.clone(),
28130                                ))),
28131                                Expression::number(1),
28132                            )));
28133                        // STRPOS(...) = 0
28134                        let is_zero = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
28135                            strpos_call.clone(),
28136                            Expression::number(0),
28137                        )));
28138
28139                        match target {
28140                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28141                                // IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
28142                                Ok(Expression::Function(Box::new(Function::new(
28143                                    "IF".to_string(),
28144                                    vec![is_zero, Expression::number(0), pos_adjusted],
28145                                ))))
28146                            }
28147                            DialectType::DuckDB => {
28148                                // CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
28149                                Ok(Expression::Case(Box::new(Case {
28150                                    operand: None,
28151                                    whens: vec![(is_zero, Expression::number(0))],
28152                                    else_: Some(pos_adjusted),
28153                                    comments: Vec::new(),
28154                                    inferred_type: None,
28155                                })))
28156                            }
28157                            _ => {
28158                                // Reconstruct StrPosition
28159                                Ok(Expression::StrPosition(Box::new(
28160                                    crate::expressions::StrPosition {
28161                                        this: Box::new(string),
28162                                        substr: Some(Box::new(substr_expr)),
28163                                        position: Some(Box::new(pos)),
28164                                        occurrence,
28165                                    },
28166                                )))
28167                            }
28168                        }
28169                    } else {
28170                        Ok(e)
28171                    }
28172                }
28173
28174                Action::MonthsBetweenConvert => {
28175                    if let Expression::MonthsBetween(mb) = e {
28176                        let crate::expressions::BinaryFunc {
28177                            this: end_date,
28178                            expression: start_date,
28179                            ..
28180                        } = *mb;
28181                        match target {
28182                            DialectType::DuckDB => {
28183                                let cast_end = Self::ensure_cast_date(end_date);
28184                                let cast_start = Self::ensure_cast_date(start_date);
28185                                let dd = Expression::Function(Box::new(Function::new(
28186                                    "DATE_DIFF".to_string(),
28187                                    vec![
28188                                        Expression::string("MONTH"),
28189                                        cast_start.clone(),
28190                                        cast_end.clone(),
28191                                    ],
28192                                )));
28193                                let day_end = Expression::Function(Box::new(Function::new(
28194                                    "DAY".to_string(),
28195                                    vec![cast_end.clone()],
28196                                )));
28197                                let day_start = Expression::Function(Box::new(Function::new(
28198                                    "DAY".to_string(),
28199                                    vec![cast_start.clone()],
28200                                )));
28201                                let last_day_end = Expression::Function(Box::new(Function::new(
28202                                    "LAST_DAY".to_string(),
28203                                    vec![cast_end.clone()],
28204                                )));
28205                                let last_day_start = Expression::Function(Box::new(Function::new(
28206                                    "LAST_DAY".to_string(),
28207                                    vec![cast_start.clone()],
28208                                )));
28209                                let day_last_end = Expression::Function(Box::new(Function::new(
28210                                    "DAY".to_string(),
28211                                    vec![last_day_end],
28212                                )));
28213                                let day_last_start = Expression::Function(Box::new(Function::new(
28214                                    "DAY".to_string(),
28215                                    vec![last_day_start],
28216                                )));
28217                                let cond1 = Expression::Eq(Box::new(BinaryOp::new(
28218                                    day_end.clone(),
28219                                    day_last_end,
28220                                )));
28221                                let cond2 = Expression::Eq(Box::new(BinaryOp::new(
28222                                    day_start.clone(),
28223                                    day_last_start,
28224                                )));
28225                                let both_cond =
28226                                    Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
28227                                let day_diff =
28228                                    Expression::Sub(Box::new(BinaryOp::new(day_end, day_start)));
28229                                let day_diff_paren =
28230                                    Expression::Paren(Box::new(crate::expressions::Paren {
28231                                        this: day_diff,
28232                                        trailing_comments: Vec::new(),
28233                                    }));
28234                                let frac = Expression::Div(Box::new(BinaryOp::new(
28235                                    day_diff_paren,
28236                                    Expression::Literal(Box::new(Literal::Number(
28237                                        "31.0".to_string(),
28238                                    ))),
28239                                )));
28240                                let case_expr = Expression::Case(Box::new(Case {
28241                                    operand: None,
28242                                    whens: vec![(both_cond, Expression::number(0))],
28243                                    else_: Some(frac),
28244                                    comments: Vec::new(),
28245                                    inferred_type: None,
28246                                }));
28247                                Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
28248                            }
28249                            DialectType::Snowflake | DialectType::Redshift => {
28250                                let unit = Expression::Identifier(Identifier::new("MONTH"));
28251                                Ok(Expression::Function(Box::new(Function::new(
28252                                    "DATEDIFF".to_string(),
28253                                    vec![unit, start_date, end_date],
28254                                ))))
28255                            }
28256                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28257                                Ok(Expression::Function(Box::new(Function::new(
28258                                    "DATE_DIFF".to_string(),
28259                                    vec![Expression::string("MONTH"), start_date, end_date],
28260                                ))))
28261                            }
28262                            _ => Ok(Expression::MonthsBetween(Box::new(
28263                                crate::expressions::BinaryFunc {
28264                                    this: end_date,
28265                                    expression: start_date,
28266                                    original_name: None,
28267                                    inferred_type: None,
28268                                },
28269                            ))),
28270                        }
28271                    } else {
28272                        Ok(e)
28273                    }
28274                }
28275
28276                Action::AddMonthsConvert => {
28277                    if let Expression::AddMonths(am) = e {
28278                        let date = am.this;
28279                        let val = am.expression;
28280                        match target {
28281                            DialectType::TSQL | DialectType::Fabric => {
28282                                let cast_date = Self::ensure_cast_datetime2(date);
28283                                Ok(Expression::Function(Box::new(Function::new(
28284                                    "DATEADD".to_string(),
28285                                    vec![
28286                                        Expression::Identifier(Identifier::new("MONTH")),
28287                                        val,
28288                                        cast_date,
28289                                    ],
28290                                ))))
28291                            }
28292                            DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
28293                                // DuckDB ADD_MONTHS from Snowflake: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
28294                                // Optionally wrapped in CAST(... AS type) if the input had a specific type
28295
28296                                // Determine the cast type from the date expression
28297                                let (cast_date, return_type) = match &date {
28298                                    Expression::Literal(lit)
28299                                        if matches!(lit.as_ref(), Literal::String(_)) =>
28300                                    {
28301                                        // String literal: CAST(str AS TIMESTAMP), no outer CAST
28302                                        (
28303                                            Expression::Cast(Box::new(Cast {
28304                                                this: date.clone(),
28305                                                to: DataType::Timestamp {
28306                                                    precision: None,
28307                                                    timezone: false,
28308                                                },
28309                                                trailing_comments: Vec::new(),
28310                                                double_colon_syntax: false,
28311                                                format: None,
28312                                                default: None,
28313                                                inferred_type: None,
28314                                            })),
28315                                            None,
28316                                        )
28317                                    }
28318                                    Expression::Cast(c) => {
28319                                        // Already cast (e.g., '2023-01-31'::DATE) - keep the cast, wrap result in CAST(... AS type)
28320                                        (date.clone(), Some(c.to.clone()))
28321                                    }
28322                                    _ => {
28323                                        // Expression or NULL::TYPE - keep as-is, check for cast type
28324                                        if let Expression::Cast(c) = &date {
28325                                            (date.clone(), Some(c.to.clone()))
28326                                        } else {
28327                                            (date.clone(), None)
28328                                        }
28329                                    }
28330                                };
28331
28332                                // Build the interval expression
28333                                // For non-integer values (float, decimal, cast), use TO_MONTHS(CAST(ROUND(val) AS INT))
28334                                // For integer values, use INTERVAL val MONTH
28335                                let is_non_integer_val = match &val {
28336                                    Expression::Literal(lit)
28337                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
28338                                    {
28339                                        let Literal::Number(n) = lit.as_ref() else {
28340                                            unreachable!()
28341                                        };
28342                                        n.contains('.')
28343                                    }
28344                                    Expression::Cast(_) => true, // e.g., 3.2::DECIMAL(10,2)
28345                                    Expression::Neg(n) => {
28346                                        if let Expression::Literal(lit) = &n.this {
28347                                            if let Literal::Number(s) = lit.as_ref() {
28348                                                s.contains('.')
28349                                            } else {
28350                                                false
28351                                            }
28352                                        } else {
28353                                            false
28354                                        }
28355                                    }
28356                                    _ => false,
28357                                };
28358
28359                                let add_interval = if is_non_integer_val {
28360                                    // TO_MONTHS(CAST(ROUND(val) AS INT))
28361                                    let round_val = Expression::Function(Box::new(Function::new(
28362                                        "ROUND".to_string(),
28363                                        vec![val.clone()],
28364                                    )));
28365                                    let cast_int = Expression::Cast(Box::new(Cast {
28366                                        this: round_val,
28367                                        to: DataType::Int {
28368                                            length: None,
28369                                            integer_spelling: false,
28370                                        },
28371                                        trailing_comments: Vec::new(),
28372                                        double_colon_syntax: false,
28373                                        format: None,
28374                                        default: None,
28375                                        inferred_type: None,
28376                                    }));
28377                                    Expression::Function(Box::new(Function::new(
28378                                        "TO_MONTHS".to_string(),
28379                                        vec![cast_int],
28380                                    )))
28381                                } else {
28382                                    // INTERVAL val MONTH
28383                                    // For negative numbers, wrap in parens
28384                                    let interval_val = match &val {
28385                                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n.starts_with('-')) =>
28386                                        {
28387                                            let Literal::Number(_) = lit.as_ref() else {
28388                                                unreachable!()
28389                                            };
28390                                            Expression::Paren(Box::new(Paren {
28391                                                this: val.clone(),
28392                                                trailing_comments: Vec::new(),
28393                                            }))
28394                                        }
28395                                        Expression::Neg(_) => Expression::Paren(Box::new(Paren {
28396                                            this: val.clone(),
28397                                            trailing_comments: Vec::new(),
28398                                        })),
28399                                        Expression::Null(_) => Expression::Paren(Box::new(Paren {
28400                                            this: val.clone(),
28401                                            trailing_comments: Vec::new(),
28402                                        })),
28403                                        _ => val.clone(),
28404                                    };
28405                                    Expression::Interval(Box::new(crate::expressions::Interval {
28406                                        this: Some(interval_val),
28407                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
28408                                            unit: crate::expressions::IntervalUnit::Month,
28409                                            use_plural: false,
28410                                        }),
28411                                    }))
28412                                };
28413
28414                                // Build: date + interval
28415                                let date_plus_interval = Expression::Add(Box::new(BinaryOp::new(
28416                                    cast_date.clone(),
28417                                    add_interval.clone(),
28418                                )));
28419
28420                                // Build LAST_DAY(date)
28421                                let last_day_date = Expression::Function(Box::new(Function::new(
28422                                    "LAST_DAY".to_string(),
28423                                    vec![cast_date.clone()],
28424                                )));
28425
28426                                // Build LAST_DAY(date + interval)
28427                                let last_day_date_plus =
28428                                    Expression::Function(Box::new(Function::new(
28429                                        "LAST_DAY".to_string(),
28430                                        vec![date_plus_interval.clone()],
28431                                    )));
28432
28433                                // Build: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
28434                                let case_expr = Expression::Case(Box::new(Case {
28435                                    operand: None,
28436                                    whens: vec![(
28437                                        Expression::Eq(Box::new(BinaryOp::new(
28438                                            last_day_date,
28439                                            cast_date.clone(),
28440                                        ))),
28441                                        last_day_date_plus,
28442                                    )],
28443                                    else_: Some(date_plus_interval),
28444                                    comments: Vec::new(),
28445                                    inferred_type: None,
28446                                }));
28447
28448                                // Wrap in CAST(... AS type) if needed
28449                                if let Some(dt) = return_type {
28450                                    Ok(Expression::Cast(Box::new(Cast {
28451                                        this: case_expr,
28452                                        to: dt,
28453                                        trailing_comments: Vec::new(),
28454                                        double_colon_syntax: false,
28455                                        format: None,
28456                                        default: None,
28457                                        inferred_type: None,
28458                                    })))
28459                                } else {
28460                                    Ok(case_expr)
28461                                }
28462                            }
28463                            DialectType::DuckDB => {
28464                                // Non-Snowflake source: simple date + INTERVAL
28465                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
28466                                {
28467                                    Expression::Cast(Box::new(Cast {
28468                                        this: date,
28469                                        to: DataType::Timestamp {
28470                                            precision: None,
28471                                            timezone: false,
28472                                        },
28473                                        trailing_comments: Vec::new(),
28474                                        double_colon_syntax: false,
28475                                        format: None,
28476                                        default: None,
28477                                        inferred_type: None,
28478                                    }))
28479                                } else {
28480                                    date
28481                                };
28482                                let interval =
28483                                    Expression::Interval(Box::new(crate::expressions::Interval {
28484                                        this: Some(val),
28485                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
28486                                            unit: crate::expressions::IntervalUnit::Month,
28487                                            use_plural: false,
28488                                        }),
28489                                    }));
28490                                Ok(Expression::Add(Box::new(BinaryOp::new(
28491                                    cast_date, interval,
28492                                ))))
28493                            }
28494                            DialectType::Snowflake => {
28495                                // Keep ADD_MONTHS when source is also Snowflake
28496                                if matches!(source, DialectType::Snowflake) {
28497                                    Ok(Expression::Function(Box::new(Function::new(
28498                                        "ADD_MONTHS".to_string(),
28499                                        vec![date, val],
28500                                    ))))
28501                                } else {
28502                                    Ok(Expression::Function(Box::new(Function::new(
28503                                        "DATEADD".to_string(),
28504                                        vec![
28505                                            Expression::Identifier(Identifier::new("MONTH")),
28506                                            val,
28507                                            date,
28508                                        ],
28509                                    ))))
28510                                }
28511                            }
28512                            DialectType::Redshift => {
28513                                Ok(Expression::Function(Box::new(Function::new(
28514                                    "DATEADD".to_string(),
28515                                    vec![
28516                                        Expression::Identifier(Identifier::new("MONTH")),
28517                                        val,
28518                                        date,
28519                                    ],
28520                                ))))
28521                            }
28522                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28523                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
28524                                {
28525                                    Expression::Cast(Box::new(Cast {
28526                                        this: date,
28527                                        to: DataType::Timestamp {
28528                                            precision: None,
28529                                            timezone: false,
28530                                        },
28531                                        trailing_comments: Vec::new(),
28532                                        double_colon_syntax: false,
28533                                        format: None,
28534                                        default: None,
28535                                        inferred_type: None,
28536                                    }))
28537                                } else {
28538                                    date
28539                                };
28540                                Ok(Expression::Function(Box::new(Function::new(
28541                                    "DATE_ADD".to_string(),
28542                                    vec![Expression::string("MONTH"), val, cast_date],
28543                                ))))
28544                            }
28545                            DialectType::BigQuery => {
28546                                let interval =
28547                                    Expression::Interval(Box::new(crate::expressions::Interval {
28548                                        this: Some(val),
28549                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
28550                                            unit: crate::expressions::IntervalUnit::Month,
28551                                            use_plural: false,
28552                                        }),
28553                                    }));
28554                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
28555                                {
28556                                    Expression::Cast(Box::new(Cast {
28557                                        this: date,
28558                                        to: DataType::Custom {
28559                                            name: "DATETIME".to_string(),
28560                                        },
28561                                        trailing_comments: Vec::new(),
28562                                        double_colon_syntax: false,
28563                                        format: None,
28564                                        default: None,
28565                                        inferred_type: None,
28566                                    }))
28567                                } else {
28568                                    date
28569                                };
28570                                Ok(Expression::Function(Box::new(Function::new(
28571                                    "DATE_ADD".to_string(),
28572                                    vec![cast_date, interval],
28573                                ))))
28574                            }
28575                            DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
28576                                Ok(Expression::Function(Box::new(Function::new(
28577                                    "ADD_MONTHS".to_string(),
28578                                    vec![date, val],
28579                                ))))
28580                            }
28581                            _ => {
28582                                // Default: keep as AddMonths expression
28583                                Ok(Expression::AddMonths(Box::new(
28584                                    crate::expressions::BinaryFunc {
28585                                        this: date,
28586                                        expression: val,
28587                                        original_name: None,
28588                                        inferred_type: None,
28589                                    },
28590                                )))
28591                            }
28592                        }
28593                    } else {
28594                        Ok(e)
28595                    }
28596                }
28597
28598                Action::PercentileContConvert => {
28599                    // PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY col) ->
28600                    // Presto/Trino: APPROX_PERCENTILE(col, p)
28601                    // Spark/Databricks: PERCENTILE_APPROX(col, p)
28602                    if let Expression::WithinGroup(wg) = e {
28603                        // Extract percentile value and order by column
28604                        let (percentile, _is_disc) = match &wg.this {
28605                            Expression::Function(f) => {
28606                                let is_disc = f.name.eq_ignore_ascii_case("PERCENTILE_DISC");
28607                                let pct = f.args.first().cloned().unwrap_or(Expression::Literal(
28608                                    Box::new(Literal::Number("0.5".to_string())),
28609                                ));
28610                                (pct, is_disc)
28611                            }
28612                            Expression::AggregateFunction(af) => {
28613                                let is_disc = af.name.eq_ignore_ascii_case("PERCENTILE_DISC");
28614                                let pct = af.args.first().cloned().unwrap_or(Expression::Literal(
28615                                    Box::new(Literal::Number("0.5".to_string())),
28616                                ));
28617                                (pct, is_disc)
28618                            }
28619                            Expression::PercentileCont(pc) => (pc.percentile.clone(), false),
28620                            _ => return Ok(Expression::WithinGroup(wg)),
28621                        };
28622                        let col = wg.order_by.first().map(|o| o.this.clone()).unwrap_or(
28623                            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
28624                        );
28625
28626                        let func_name = match target {
28627                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28628                                "APPROX_PERCENTILE"
28629                            }
28630                            _ => "PERCENTILE_APPROX", // Spark, Databricks
28631                        };
28632                        Ok(Expression::Function(Box::new(Function::new(
28633                            func_name.to_string(),
28634                            vec![col, percentile],
28635                        ))))
28636                    } else {
28637                        Ok(e)
28638                    }
28639                }
28640
28641                Action::CurrentUserSparkParens => {
28642                    // CURRENT_USER -> CURRENT_USER() for Spark
28643                    if let Expression::CurrentUser(_) = e {
28644                        Ok(Expression::Function(Box::new(Function::new(
28645                            "CURRENT_USER".to_string(),
28646                            vec![],
28647                        ))))
28648                    } else {
28649                        Ok(e)
28650                    }
28651                }
28652
28653                Action::SparkDateFuncCast => {
28654                    // MONTH/YEAR/DAY('string') from Spark -> wrap arg in CAST to DATE
28655                    let cast_arg = |arg: Expression| -> Expression {
28656                        match target {
28657                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28658                                Self::double_cast_timestamp_date(arg)
28659                            }
28660                            _ => {
28661                                // DuckDB, PostgreSQL, etc: CAST(arg AS DATE)
28662                                Self::ensure_cast_date(arg)
28663                            }
28664                        }
28665                    };
28666                    match e {
28667                        Expression::Month(f) => Ok(Expression::Month(Box::new(
28668                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
28669                        ))),
28670                        Expression::Year(f) => Ok(Expression::Year(Box::new(
28671                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
28672                        ))),
28673                        Expression::Day(f) => Ok(Expression::Day(Box::new(
28674                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
28675                        ))),
28676                        other => Ok(other),
28677                    }
28678                }
28679
28680                Action::MapFromArraysConvert => {
28681                    // Expression::MapFromArrays -> target-specific
28682                    if let Expression::MapFromArrays(mfa) = e {
28683                        let keys = mfa.this;
28684                        let values = mfa.expression;
28685                        match target {
28686                            DialectType::Snowflake => Ok(Expression::Function(Box::new(
28687                                Function::new("OBJECT_CONSTRUCT".to_string(), vec![keys, values]),
28688                            ))),
28689                            _ => {
28690                                // Hive, Presto, DuckDB, etc.: MAP(keys, values)
28691                                Ok(Expression::Function(Box::new(Function::new(
28692                                    "MAP".to_string(),
28693                                    vec![keys, values],
28694                                ))))
28695                            }
28696                        }
28697                    } else {
28698                        Ok(e)
28699                    }
28700                }
28701
28702                Action::AnyToExists => {
28703                    if let Expression::Any(q) = e {
28704                        if let Some(op) = q.op.clone() {
28705                            let lambda_param = crate::expressions::Identifier::new("x");
28706                            let rhs = Expression::Identifier(lambda_param.clone());
28707                            let body = match op {
28708                                crate::expressions::QuantifiedOp::Eq => {
28709                                    Expression::Eq(Box::new(BinaryOp::new(q.this, rhs)))
28710                                }
28711                                crate::expressions::QuantifiedOp::Neq => {
28712                                    Expression::Neq(Box::new(BinaryOp::new(q.this, rhs)))
28713                                }
28714                                crate::expressions::QuantifiedOp::Lt => {
28715                                    Expression::Lt(Box::new(BinaryOp::new(q.this, rhs)))
28716                                }
28717                                crate::expressions::QuantifiedOp::Lte => {
28718                                    Expression::Lte(Box::new(BinaryOp::new(q.this, rhs)))
28719                                }
28720                                crate::expressions::QuantifiedOp::Gt => {
28721                                    Expression::Gt(Box::new(BinaryOp::new(q.this, rhs)))
28722                                }
28723                                crate::expressions::QuantifiedOp::Gte => {
28724                                    Expression::Gte(Box::new(BinaryOp::new(q.this, rhs)))
28725                                }
28726                            };
28727                            let lambda =
28728                                Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
28729                                    parameters: vec![lambda_param],
28730                                    body,
28731                                    colon: false,
28732                                    parameter_types: Vec::new(),
28733                                }));
28734                            Ok(Expression::Function(Box::new(Function::new(
28735                                "EXISTS".to_string(),
28736                                vec![q.subquery, lambda],
28737                            ))))
28738                        } else {
28739                            Ok(Expression::Any(q))
28740                        }
28741                    } else {
28742                        Ok(e)
28743                    }
28744                }
28745
28746                Action::GenerateSeriesConvert => {
28747                    // GENERATE_SERIES(start, end[, step]) -> SEQUENCE for Spark/Databricks/Hive, wrapped in UNNEST/EXPLODE
28748                    // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
28749                    // For PG/Redshift target: keep as GENERATE_SERIES but normalize interval string step
28750                    if let Expression::Function(f) = e {
28751                        if f.name.eq_ignore_ascii_case("GENERATE_SERIES") && f.args.len() >= 2 {
28752                            let start = f.args[0].clone();
28753                            let end = f.args[1].clone();
28754                            let step = f.args.get(2).cloned();
28755
28756                            // Normalize step: convert string interval like '1day' or '  2   days  ' to INTERVAL expression
28757                            let step = step.map(|s| Self::normalize_interval_string(s, target));
28758
28759                            // Helper: wrap CURRENT_TIMESTAMP in CAST(... AS TIMESTAMP) for Presto/Trino/Spark
28760                            let maybe_cast_timestamp = |arg: Expression| -> Expression {
28761                                if matches!(
28762                                    target,
28763                                    DialectType::Presto
28764                                        | DialectType::Trino
28765                                        | DialectType::Athena
28766                                        | DialectType::Spark
28767                                        | DialectType::Databricks
28768                                        | DialectType::Hive
28769                                ) {
28770                                    match &arg {
28771                                        Expression::CurrentTimestamp(_) => {
28772                                            Expression::Cast(Box::new(Cast {
28773                                                this: arg,
28774                                                to: DataType::Timestamp {
28775                                                    precision: None,
28776                                                    timezone: false,
28777                                                },
28778                                                trailing_comments: Vec::new(),
28779                                                double_colon_syntax: false,
28780                                                format: None,
28781                                                default: None,
28782                                                inferred_type: None,
28783                                            }))
28784                                        }
28785                                        _ => arg,
28786                                    }
28787                                } else {
28788                                    arg
28789                                }
28790                            };
28791
28792                            let start = maybe_cast_timestamp(start);
28793                            let end = maybe_cast_timestamp(end);
28794
28795                            // For PostgreSQL/Redshift target, keep as GENERATE_SERIES
28796                            if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
28797                                let mut gs_args = vec![start, end];
28798                                if let Some(step) = step {
28799                                    gs_args.push(step);
28800                                }
28801                                return Ok(Expression::Function(Box::new(Function::new(
28802                                    "GENERATE_SERIES".to_string(),
28803                                    gs_args,
28804                                ))));
28805                            }
28806
28807                            // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
28808                            if matches!(target, DialectType::DuckDB) {
28809                                let mut gs_args = vec![start, end];
28810                                if let Some(step) = step {
28811                                    gs_args.push(step);
28812                                }
28813                                let gs = Expression::Function(Box::new(Function::new(
28814                                    "GENERATE_SERIES".to_string(),
28815                                    gs_args,
28816                                )));
28817                                return Ok(Expression::Function(Box::new(Function::new(
28818                                    "UNNEST".to_string(),
28819                                    vec![gs],
28820                                ))));
28821                            }
28822
28823                            let mut seq_args = vec![start, end];
28824                            if let Some(step) = step {
28825                                seq_args.push(step);
28826                            }
28827
28828                            let seq = Expression::Function(Box::new(Function::new(
28829                                "SEQUENCE".to_string(),
28830                                seq_args,
28831                            )));
28832
28833                            match target {
28834                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28835                                    // Wrap in UNNEST
28836                                    Ok(Expression::Function(Box::new(Function::new(
28837                                        "UNNEST".to_string(),
28838                                        vec![seq],
28839                                    ))))
28840                                }
28841                                DialectType::Spark
28842                                | DialectType::Databricks
28843                                | DialectType::Hive => {
28844                                    // Wrap in EXPLODE
28845                                    Ok(Expression::Function(Box::new(Function::new(
28846                                        "EXPLODE".to_string(),
28847                                        vec![seq],
28848                                    ))))
28849                                }
28850                                _ => {
28851                                    // Just SEQUENCE for others
28852                                    Ok(seq)
28853                                }
28854                            }
28855                        } else {
28856                            Ok(Expression::Function(f))
28857                        }
28858                    } else {
28859                        Ok(e)
28860                    }
28861                }
28862
28863                Action::ConcatCoalesceWrap => {
28864                    // CONCAT(a, b) function -> CONCAT(COALESCE(CAST(a AS VARCHAR), ''), ...) for Presto
28865                    // CONCAT(a, b) function -> CONCAT(COALESCE(a, ''), ...) for ClickHouse
28866                    if let Expression::Function(f) = e {
28867                        if f.name.eq_ignore_ascii_case("CONCAT") {
28868                            let new_args: Vec<Expression> = f
28869                                .args
28870                                .into_iter()
28871                                .map(|arg| {
28872                                    let cast_arg = if matches!(
28873                                        target,
28874                                        DialectType::Presto
28875                                            | DialectType::Trino
28876                                            | DialectType::Athena
28877                                    ) {
28878                                        Expression::Cast(Box::new(Cast {
28879                                            this: arg,
28880                                            to: DataType::VarChar {
28881                                                length: None,
28882                                                parenthesized_length: false,
28883                                            },
28884                                            trailing_comments: Vec::new(),
28885                                            double_colon_syntax: false,
28886                                            format: None,
28887                                            default: None,
28888                                            inferred_type: None,
28889                                        }))
28890                                    } else {
28891                                        arg
28892                                    };
28893                                    Expression::Function(Box::new(Function::new(
28894                                        "COALESCE".to_string(),
28895                                        vec![cast_arg, Expression::string("")],
28896                                    )))
28897                                })
28898                                .collect();
28899                            Ok(Expression::Function(Box::new(Function::new(
28900                                "CONCAT".to_string(),
28901                                new_args,
28902                            ))))
28903                        } else {
28904                            Ok(Expression::Function(f))
28905                        }
28906                    } else {
28907                        Ok(e)
28908                    }
28909                }
28910
28911                Action::PipeConcatToConcat => {
28912                    // a || b (Concat operator) -> CONCAT(CAST(a AS VARCHAR), CAST(b AS VARCHAR)) for Presto/Trino
28913                    if let Expression::Concat(op) = e {
28914                        let cast_left = Expression::Cast(Box::new(Cast {
28915                            this: op.left,
28916                            to: DataType::VarChar {
28917                                length: None,
28918                                parenthesized_length: false,
28919                            },
28920                            trailing_comments: Vec::new(),
28921                            double_colon_syntax: false,
28922                            format: None,
28923                            default: None,
28924                            inferred_type: None,
28925                        }));
28926                        let cast_right = Expression::Cast(Box::new(Cast {
28927                            this: op.right,
28928                            to: DataType::VarChar {
28929                                length: None,
28930                                parenthesized_length: false,
28931                            },
28932                            trailing_comments: Vec::new(),
28933                            double_colon_syntax: false,
28934                            format: None,
28935                            default: None,
28936                            inferred_type: None,
28937                        }));
28938                        Ok(Expression::Function(Box::new(Function::new(
28939                            "CONCAT".to_string(),
28940                            vec![cast_left, cast_right],
28941                        ))))
28942                    } else {
28943                        Ok(e)
28944                    }
28945                }
28946
28947                Action::DivFuncConvert => {
28948                    // DIV(a, b) -> target-specific integer division
28949                    if let Expression::Function(f) = e {
28950                        if f.name.eq_ignore_ascii_case("DIV") && f.args.len() == 2 {
28951                            let a = f.args[0].clone();
28952                            let b = f.args[1].clone();
28953                            match target {
28954                                DialectType::DuckDB => {
28955                                    // DIV(a, b) -> CAST(a // b AS DECIMAL)
28956                                    let int_div = Expression::IntDiv(Box::new(
28957                                        crate::expressions::BinaryFunc {
28958                                            this: a,
28959                                            expression: b,
28960                                            original_name: None,
28961                                            inferred_type: None,
28962                                        },
28963                                    ));
28964                                    Ok(Expression::Cast(Box::new(Cast {
28965                                        this: int_div,
28966                                        to: DataType::Decimal {
28967                                            precision: None,
28968                                            scale: None,
28969                                        },
28970                                        trailing_comments: Vec::new(),
28971                                        double_colon_syntax: false,
28972                                        format: None,
28973                                        default: None,
28974                                        inferred_type: None,
28975                                    })))
28976                                }
28977                                DialectType::BigQuery => {
28978                                    // DIV(a, b) -> CAST(DIV(a, b) AS NUMERIC)
28979                                    let div_func = Expression::Function(Box::new(Function::new(
28980                                        "DIV".to_string(),
28981                                        vec![a, b],
28982                                    )));
28983                                    Ok(Expression::Cast(Box::new(Cast {
28984                                        this: div_func,
28985                                        to: DataType::Custom {
28986                                            name: "NUMERIC".to_string(),
28987                                        },
28988                                        trailing_comments: Vec::new(),
28989                                        double_colon_syntax: false,
28990                                        format: None,
28991                                        default: None,
28992                                        inferred_type: None,
28993                                    })))
28994                                }
28995                                DialectType::SQLite => {
28996                                    // DIV(a, b) -> CAST(CAST(CAST(a AS REAL) / b AS INTEGER) AS REAL)
28997                                    let cast_a = Expression::Cast(Box::new(Cast {
28998                                        this: a,
28999                                        to: DataType::Custom {
29000                                            name: "REAL".to_string(),
29001                                        },
29002                                        trailing_comments: Vec::new(),
29003                                        double_colon_syntax: false,
29004                                        format: None,
29005                                        default: None,
29006                                        inferred_type: None,
29007                                    }));
29008                                    let div = Expression::Div(Box::new(BinaryOp::new(cast_a, b)));
29009                                    let cast_int = Expression::Cast(Box::new(Cast {
29010                                        this: div,
29011                                        to: DataType::Int {
29012                                            length: None,
29013                                            integer_spelling: true,
29014                                        },
29015                                        trailing_comments: Vec::new(),
29016                                        double_colon_syntax: false,
29017                                        format: None,
29018                                        default: None,
29019                                        inferred_type: None,
29020                                    }));
29021                                    Ok(Expression::Cast(Box::new(Cast {
29022                                        this: cast_int,
29023                                        to: DataType::Custom {
29024                                            name: "REAL".to_string(),
29025                                        },
29026                                        trailing_comments: Vec::new(),
29027                                        double_colon_syntax: false,
29028                                        format: None,
29029                                        default: None,
29030                                        inferred_type: None,
29031                                    })))
29032                                }
29033                                _ => Ok(Expression::Function(f)),
29034                            }
29035                        } else {
29036                            Ok(Expression::Function(f))
29037                        }
29038                    } else {
29039                        Ok(e)
29040                    }
29041                }
29042
29043                Action::JsonObjectAggConvert => {
29044                    // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
29045                    match e {
29046                        Expression::Function(f) => Ok(Expression::Function(Box::new(
29047                            Function::new("JSON_GROUP_OBJECT".to_string(), f.args),
29048                        ))),
29049                        Expression::AggregateFunction(af) => {
29050                            // AggregateFunction stores all args in the `args` vec
29051                            Ok(Expression::Function(Box::new(Function::new(
29052                                "JSON_GROUP_OBJECT".to_string(),
29053                                af.args,
29054                            ))))
29055                        }
29056                        other => Ok(other),
29057                    }
29058                }
29059
29060                Action::JsonbExistsConvert => {
29061                    // JSONB_EXISTS('json', 'key') -> JSON_EXISTS('json', '$.key') for DuckDB
29062                    if let Expression::Function(f) = e {
29063                        if f.args.len() == 2 {
29064                            let json_expr = f.args[0].clone();
29065                            let key = match &f.args[1] {
29066                                Expression::Literal(lit)
29067                                    if matches!(
29068                                        lit.as_ref(),
29069                                        crate::expressions::Literal::String(_)
29070                                    ) =>
29071                                {
29072                                    let crate::expressions::Literal::String(s) = lit.as_ref()
29073                                    else {
29074                                        unreachable!()
29075                                    };
29076                                    format!("$.{}", s)
29077                                }
29078                                _ => return Ok(Expression::Function(f)),
29079                            };
29080                            Ok(Expression::Function(Box::new(Function::new(
29081                                "JSON_EXISTS".to_string(),
29082                                vec![json_expr, Expression::string(&key)],
29083                            ))))
29084                        } else {
29085                            Ok(Expression::Function(f))
29086                        }
29087                    } else {
29088                        Ok(e)
29089                    }
29090                }
29091
29092                Action::DateBinConvert => {
29093                    // DATE_BIN('interval', ts, origin) -> TIME_BUCKET('interval', ts, origin) for DuckDB
29094                    if let Expression::Function(f) = e {
29095                        Ok(Expression::Function(Box::new(Function::new(
29096                            "TIME_BUCKET".to_string(),
29097                            f.args,
29098                        ))))
29099                    } else {
29100                        Ok(e)
29101                    }
29102                }
29103
29104                Action::MysqlCastCharToText => {
29105                    // MySQL CAST(x AS CHAR) was originally TEXT -> convert to target text type
29106                    if let Expression::Cast(mut c) = e {
29107                        c.to = DataType::Text;
29108                        Ok(Expression::Cast(c))
29109                    } else {
29110                        Ok(e)
29111                    }
29112                }
29113
29114                Action::SparkCastVarcharToString => {
29115                    // Spark parses VARCHAR(n)/CHAR(n) as TEXT -> normalize to STRING
29116                    match e {
29117                        Expression::Cast(mut c) => {
29118                            c.to = Self::normalize_varchar_to_string(c.to);
29119                            Ok(Expression::Cast(c))
29120                        }
29121                        Expression::TryCast(mut c) => {
29122                            c.to = Self::normalize_varchar_to_string(c.to);
29123                            Ok(Expression::TryCast(c))
29124                        }
29125                        _ => Ok(e),
29126                    }
29127                }
29128
29129                Action::MinMaxToLeastGreatest => {
29130                    // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
29131                    if let Expression::Function(f) = e {
29132                        let new_name = if f.name.eq_ignore_ascii_case("MIN") {
29133                            "LEAST"
29134                        } else if f.name.eq_ignore_ascii_case("MAX") {
29135                            "GREATEST"
29136                        } else {
29137                            return Ok(Expression::Function(f));
29138                        };
29139                        Ok(Expression::Function(Box::new(Function::new(
29140                            new_name.to_string(),
29141                            f.args,
29142                        ))))
29143                    } else {
29144                        Ok(e)
29145                    }
29146                }
29147
29148                Action::ClickHouseUniqToApproxCountDistinct => {
29149                    // ClickHouse uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
29150                    if let Expression::Function(f) = e {
29151                        Ok(Expression::Function(Box::new(Function::new(
29152                            "APPROX_COUNT_DISTINCT".to_string(),
29153                            f.args,
29154                        ))))
29155                    } else {
29156                        Ok(e)
29157                    }
29158                }
29159
29160                Action::ClickHouseAnyToAnyValue => {
29161                    // ClickHouse any(x) -> ANY_VALUE(x) for non-ClickHouse targets
29162                    if let Expression::Function(f) = e {
29163                        Ok(Expression::Function(Box::new(Function::new(
29164                            "ANY_VALUE".to_string(),
29165                            f.args,
29166                        ))))
29167                    } else {
29168                        Ok(e)
29169                    }
29170                }
29171
29172                Action::OracleVarchar2ToVarchar => {
29173                    // Oracle VARCHAR2(N CHAR/BYTE) / NVARCHAR2(N) -> VarChar(N) for non-Oracle targets
29174                    if let Expression::DataType(DataType::Custom { ref name }) = e {
29175                        // Extract length from VARCHAR2(N ...) or NVARCHAR2(N ...)
29176                        let starts_varchar2 =
29177                            name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2(");
29178                        let starts_nvarchar2 =
29179                            name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2(");
29180                        let inner = if starts_varchar2 || starts_nvarchar2 {
29181                            let start = if starts_nvarchar2 { 10 } else { 9 }; // skip "NVARCHAR2(" or "VARCHAR2("
29182                            let end = name.len() - 1; // skip trailing ")"
29183                            Some(&name[start..end])
29184                        } else {
29185                            Option::None
29186                        };
29187                        if let Some(inner_str) = inner {
29188                            // Parse the number part, ignoring BYTE/CHAR qualifier
29189                            let num_str = inner_str.split_whitespace().next().unwrap_or("");
29190                            if let Ok(n) = num_str.parse::<u32>() {
29191                                Ok(Expression::DataType(DataType::VarChar {
29192                                    length: Some(n),
29193                                    parenthesized_length: false,
29194                                }))
29195                            } else {
29196                                Ok(e)
29197                            }
29198                        } else {
29199                            // Plain VARCHAR2 / NVARCHAR2 without parens
29200                            Ok(Expression::DataType(DataType::VarChar {
29201                                length: Option::None,
29202                                parenthesized_length: false,
29203                            }))
29204                        }
29205                    } else {
29206                        Ok(e)
29207                    }
29208                }
29209
29210                Action::Nvl2Expand => {
29211                    // NVL2(a, b[, c]) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
29212                    // But keep as NVL2 for dialects that support it natively
29213                    let nvl2_native = matches!(
29214                        target,
29215                        DialectType::Oracle
29216                            | DialectType::Snowflake
29217                            | DialectType::Redshift
29218                            | DialectType::Teradata
29219                            | DialectType::Spark
29220                            | DialectType::Databricks
29221                    );
29222                    let (a, b, c) = if let Expression::Nvl2(nvl2) = e {
29223                        if nvl2_native {
29224                            return Ok(Expression::Nvl2(nvl2));
29225                        }
29226                        (nvl2.this, nvl2.true_value, Some(nvl2.false_value))
29227                    } else if let Expression::Function(f) = e {
29228                        if nvl2_native {
29229                            return Ok(Expression::Function(Box::new(Function::new(
29230                                "NVL2".to_string(),
29231                                f.args,
29232                            ))));
29233                        }
29234                        if f.args.len() < 2 {
29235                            return Ok(Expression::Function(f));
29236                        }
29237                        let mut args = f.args;
29238                        let a = args.remove(0);
29239                        let b = args.remove(0);
29240                        let c = if !args.is_empty() {
29241                            Some(args.remove(0))
29242                        } else {
29243                            Option::None
29244                        };
29245                        (a, b, c)
29246                    } else {
29247                        return Ok(e);
29248                    };
29249                    // Build: NOT (a IS NULL)
29250                    let is_null = Expression::IsNull(Box::new(IsNull {
29251                        this: a,
29252                        not: false,
29253                        postfix_form: false,
29254                    }));
29255                    let not_null = Expression::Not(Box::new(crate::expressions::UnaryOp {
29256                        this: is_null,
29257                        inferred_type: None,
29258                    }));
29259                    Ok(Expression::Case(Box::new(Case {
29260                        operand: Option::None,
29261                        whens: vec![(not_null, b)],
29262                        else_: c,
29263                        comments: Vec::new(),
29264                        inferred_type: None,
29265                    })))
29266                }
29267
29268                Action::IfnullToCoalesce => {
29269                    // IFNULL(a, b) -> COALESCE(a, b): clear original_name to output COALESCE
29270                    if let Expression::Coalesce(mut cf) = e {
29271                        cf.original_name = Option::None;
29272                        Ok(Expression::Coalesce(cf))
29273                    } else if let Expression::Function(f) = e {
29274                        Ok(Expression::Function(Box::new(Function::new(
29275                            "COALESCE".to_string(),
29276                            f.args,
29277                        ))))
29278                    } else {
29279                        Ok(e)
29280                    }
29281                }
29282
29283                Action::IsAsciiConvert => {
29284                    // IS_ASCII(x) -> dialect-specific ASCII check
29285                    if let Expression::Function(f) = e {
29286                        let arg = f.args.into_iter().next().unwrap();
29287                        match target {
29288                            DialectType::MySQL | DialectType::SingleStore | DialectType::TiDB => {
29289                                // REGEXP_LIKE(x, '^[[:ascii:]]*$')
29290                                Ok(Expression::Function(Box::new(Function::new(
29291                                    "REGEXP_LIKE".to_string(),
29292                                    vec![
29293                                        arg,
29294                                        Expression::Literal(Box::new(Literal::String(
29295                                            "^[[:ascii:]]*$".to_string(),
29296                                        ))),
29297                                    ],
29298                                ))))
29299                            }
29300                            DialectType::PostgreSQL
29301                            | DialectType::Redshift
29302                            | DialectType::Materialize
29303                            | DialectType::RisingWave => {
29304                                // (x ~ '^[[:ascii:]]*$')
29305                                Ok(Expression::Paren(Box::new(Paren {
29306                                    this: Expression::RegexpLike(Box::new(
29307                                        crate::expressions::RegexpFunc {
29308                                            this: arg,
29309                                            pattern: Expression::Literal(Box::new(
29310                                                Literal::String("^[[:ascii:]]*$".to_string()),
29311                                            )),
29312                                            flags: Option::None,
29313                                        },
29314                                    )),
29315                                    trailing_comments: Vec::new(),
29316                                })))
29317                            }
29318                            DialectType::SQLite => {
29319                                // (NOT x GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))
29320                                let hex_lit = Expression::Literal(Box::new(Literal::HexString(
29321                                    "2a5b5e012d7f5d2a".to_string(),
29322                                )));
29323                                let cast_expr = Expression::Cast(Box::new(Cast {
29324                                    this: hex_lit,
29325                                    to: DataType::Text,
29326                                    trailing_comments: Vec::new(),
29327                                    double_colon_syntax: false,
29328                                    format: Option::None,
29329                                    default: Option::None,
29330                                    inferred_type: None,
29331                                }));
29332                                let glob = Expression::Glob(Box::new(BinaryOp {
29333                                    left: arg,
29334                                    right: cast_expr,
29335                                    left_comments: Vec::new(),
29336                                    operator_comments: Vec::new(),
29337                                    trailing_comments: Vec::new(),
29338                                    inferred_type: None,
29339                                }));
29340                                Ok(Expression::Paren(Box::new(Paren {
29341                                    this: Expression::Not(Box::new(crate::expressions::UnaryOp {
29342                                        this: glob,
29343                                        inferred_type: None,
29344                                    })),
29345                                    trailing_comments: Vec::new(),
29346                                })))
29347                            }
29348                            DialectType::TSQL | DialectType::Fabric => {
29349                                // (PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, x) = 0)
29350                                let hex_lit = Expression::Literal(Box::new(Literal::HexNumber(
29351                                    "255b5e002d7f5d25".to_string(),
29352                                )));
29353                                let convert_expr = Expression::Convert(Box::new(
29354                                    crate::expressions::ConvertFunc {
29355                                        this: hex_lit,
29356                                        to: DataType::Text, // Text generates as VARCHAR(MAX) for TSQL
29357                                        style: None,
29358                                    },
29359                                ));
29360                                let collated = Expression::Collation(Box::new(
29361                                    crate::expressions::CollationExpr {
29362                                        this: convert_expr,
29363                                        collation: "Latin1_General_BIN".to_string(),
29364                                        quoted: false,
29365                                        double_quoted: false,
29366                                    },
29367                                ));
29368                                let patindex = Expression::Function(Box::new(Function::new(
29369                                    "PATINDEX".to_string(),
29370                                    vec![collated, arg],
29371                                )));
29372                                let zero =
29373                                    Expression::Literal(Box::new(Literal::Number("0".to_string())));
29374                                let eq_zero = Expression::Eq(Box::new(BinaryOp {
29375                                    left: patindex,
29376                                    right: zero,
29377                                    left_comments: Vec::new(),
29378                                    operator_comments: Vec::new(),
29379                                    trailing_comments: Vec::new(),
29380                                    inferred_type: None,
29381                                }));
29382                                Ok(Expression::Paren(Box::new(Paren {
29383                                    this: eq_zero,
29384                                    trailing_comments: Vec::new(),
29385                                })))
29386                            }
29387                            DialectType::Oracle => {
29388                                // NVL(REGEXP_LIKE(x, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)
29389                                // Build the pattern: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
29390                                let s1 = Expression::Literal(Box::new(Literal::String(
29391                                    "^[".to_string(),
29392                                )));
29393                                let chr1 = Expression::Function(Box::new(Function::new(
29394                                    "CHR".to_string(),
29395                                    vec![Expression::Literal(Box::new(Literal::Number(
29396                                        "1".to_string(),
29397                                    )))],
29398                                )));
29399                                let dash =
29400                                    Expression::Literal(Box::new(Literal::String("-".to_string())));
29401                                let chr127 = Expression::Function(Box::new(Function::new(
29402                                    "CHR".to_string(),
29403                                    vec![Expression::Literal(Box::new(Literal::Number(
29404                                        "127".to_string(),
29405                                    )))],
29406                                )));
29407                                let s2 = Expression::Literal(Box::new(Literal::String(
29408                                    "]*$".to_string(),
29409                                )));
29410                                // Build: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
29411                                let concat1 =
29412                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
29413                                        this: Box::new(s1),
29414                                        expression: Box::new(chr1),
29415                                        safe: None,
29416                                    }));
29417                                let concat2 =
29418                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
29419                                        this: Box::new(concat1),
29420                                        expression: Box::new(dash),
29421                                        safe: None,
29422                                    }));
29423                                let concat3 =
29424                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
29425                                        this: Box::new(concat2),
29426                                        expression: Box::new(chr127),
29427                                        safe: None,
29428                                    }));
29429                                let concat4 =
29430                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
29431                                        this: Box::new(concat3),
29432                                        expression: Box::new(s2),
29433                                        safe: None,
29434                                    }));
29435                                let regexp_like = Expression::Function(Box::new(Function::new(
29436                                    "REGEXP_LIKE".to_string(),
29437                                    vec![arg, concat4],
29438                                )));
29439                                // Use Column("TRUE") to output literal TRUE keyword (not boolean 1/0)
29440                                let true_expr =
29441                                    Expression::Column(Box::new(crate::expressions::Column {
29442                                        name: Identifier {
29443                                            name: "TRUE".to_string(),
29444                                            quoted: false,
29445                                            trailing_comments: Vec::new(),
29446                                            span: None,
29447                                        },
29448                                        table: None,
29449                                        join_mark: false,
29450                                        trailing_comments: Vec::new(),
29451                                        span: None,
29452                                        inferred_type: None,
29453                                    }));
29454                                let nvl = Expression::Function(Box::new(Function::new(
29455                                    "NVL".to_string(),
29456                                    vec![regexp_like, true_expr],
29457                                )));
29458                                Ok(nvl)
29459                            }
29460                            _ => Ok(Expression::Function(Box::new(Function::new(
29461                                "IS_ASCII".to_string(),
29462                                vec![arg],
29463                            )))),
29464                        }
29465                    } else {
29466                        Ok(e)
29467                    }
29468                }
29469
29470                Action::StrPositionConvert => {
29471                    // STR_POSITION(haystack, needle[, position[, occurrence]]) -> dialect-specific
29472                    if let Expression::Function(f) = e {
29473                        if f.args.len() < 2 {
29474                            return Ok(Expression::Function(f));
29475                        }
29476                        let mut args = f.args;
29477
29478                        let haystack = args.remove(0);
29479                        let needle = args.remove(0);
29480                        let position = if !args.is_empty() {
29481                            Some(args.remove(0))
29482                        } else {
29483                            Option::None
29484                        };
29485                        let occurrence = if !args.is_empty() {
29486                            Some(args.remove(0))
29487                        } else {
29488                            Option::None
29489                        };
29490
29491                        // Helper to build: STRPOS/INSTR(SUBSTRING(haystack, pos), needle) expansion
29492                        // Returns: CASE/IF WHEN func(SUBSTRING(haystack, pos), needle[, occ]) = 0 THEN 0 ELSE ... + pos - 1 END
29493                        fn build_position_expansion(
29494                            haystack: Expression,
29495                            needle: Expression,
29496                            pos: Expression,
29497                            occurrence: Option<Expression>,
29498                            inner_func: &str,
29499                            wrapper: &str, // "CASE", "IF", "IIF"
29500                        ) -> Expression {
29501                            let substr = Expression::Function(Box::new(Function::new(
29502                                "SUBSTRING".to_string(),
29503                                vec![haystack, pos.clone()],
29504                            )));
29505                            let mut inner_args = vec![substr, needle];
29506                            if let Some(occ) = occurrence {
29507                                inner_args.push(occ);
29508                            }
29509                            let inner_call = Expression::Function(Box::new(Function::new(
29510                                inner_func.to_string(),
29511                                inner_args,
29512                            )));
29513                            let zero =
29514                                Expression::Literal(Box::new(Literal::Number("0".to_string())));
29515                            let one =
29516                                Expression::Literal(Box::new(Literal::Number("1".to_string())));
29517                            let eq_zero = Expression::Eq(Box::new(BinaryOp {
29518                                left: inner_call.clone(),
29519                                right: zero.clone(),
29520                                left_comments: Vec::new(),
29521                                operator_comments: Vec::new(),
29522                                trailing_comments: Vec::new(),
29523                                inferred_type: None,
29524                            }));
29525                            let add_pos = Expression::Add(Box::new(BinaryOp {
29526                                left: inner_call,
29527                                right: pos,
29528                                left_comments: Vec::new(),
29529                                operator_comments: Vec::new(),
29530                                trailing_comments: Vec::new(),
29531                                inferred_type: None,
29532                            }));
29533                            let sub_one = Expression::Sub(Box::new(BinaryOp {
29534                                left: add_pos,
29535                                right: one,
29536                                left_comments: Vec::new(),
29537                                operator_comments: Vec::new(),
29538                                trailing_comments: Vec::new(),
29539                                inferred_type: None,
29540                            }));
29541
29542                            match wrapper {
29543                                "CASE" => Expression::Case(Box::new(Case {
29544                                    operand: Option::None,
29545                                    whens: vec![(eq_zero, zero)],
29546                                    else_: Some(sub_one),
29547                                    comments: Vec::new(),
29548                                    inferred_type: None,
29549                                })),
29550                                "IIF" => Expression::Function(Box::new(Function::new(
29551                                    "IIF".to_string(),
29552                                    vec![eq_zero, zero, sub_one],
29553                                ))),
29554                                _ => Expression::Function(Box::new(Function::new(
29555                                    "IF".to_string(),
29556                                    vec![eq_zero, zero, sub_one],
29557                                ))),
29558                            }
29559                        }
29560
29561                        match target {
29562                            // STRPOS group: Athena, DuckDB, Presto, Trino, Drill
29563                            DialectType::Athena
29564                            | DialectType::DuckDB
29565                            | DialectType::Presto
29566                            | DialectType::Trino
29567                            | DialectType::Drill => {
29568                                if let Some(pos) = position {
29569                                    let wrapper = if matches!(target, DialectType::DuckDB) {
29570                                        "CASE"
29571                                    } else {
29572                                        "IF"
29573                                    };
29574                                    let result = build_position_expansion(
29575                                        haystack, needle, pos, occurrence, "STRPOS", wrapper,
29576                                    );
29577                                    if matches!(target, DialectType::Drill) {
29578                                        // Drill uses backtick-quoted `IF`
29579                                        if let Expression::Function(mut f) = result {
29580                                            f.name = "`IF`".to_string();
29581                                            Ok(Expression::Function(f))
29582                                        } else {
29583                                            Ok(result)
29584                                        }
29585                                    } else {
29586                                        Ok(result)
29587                                    }
29588                                } else {
29589                                    Ok(Expression::Function(Box::new(Function::new(
29590                                        "STRPOS".to_string(),
29591                                        vec![haystack, needle],
29592                                    ))))
29593                                }
29594                            }
29595                            // SQLite: IIF wrapper
29596                            DialectType::SQLite => {
29597                                if let Some(pos) = position {
29598                                    Ok(build_position_expansion(
29599                                        haystack, needle, pos, occurrence, "INSTR", "IIF",
29600                                    ))
29601                                } else {
29602                                    Ok(Expression::Function(Box::new(Function::new(
29603                                        "INSTR".to_string(),
29604                                        vec![haystack, needle],
29605                                    ))))
29606                                }
29607                            }
29608                            // INSTR group: Teradata, BigQuery, Oracle
29609                            DialectType::Teradata | DialectType::BigQuery | DialectType::Oracle => {
29610                                let mut a = vec![haystack, needle];
29611                                if let Some(pos) = position {
29612                                    a.push(pos);
29613                                }
29614                                if let Some(occ) = occurrence {
29615                                    a.push(occ);
29616                                }
29617                                Ok(Expression::Function(Box::new(Function::new(
29618                                    "INSTR".to_string(),
29619                                    a,
29620                                ))))
29621                            }
29622                            // CHARINDEX group: Snowflake, TSQL
29623                            DialectType::Snowflake | DialectType::TSQL | DialectType::Fabric => {
29624                                let mut a = vec![needle, haystack];
29625                                if let Some(pos) = position {
29626                                    a.push(pos);
29627                                }
29628                                Ok(Expression::Function(Box::new(Function::new(
29629                                    "CHARINDEX".to_string(),
29630                                    a,
29631                                ))))
29632                            }
29633                            // POSITION(needle IN haystack): PostgreSQL, Materialize, RisingWave, Redshift
29634                            DialectType::PostgreSQL
29635                            | DialectType::Materialize
29636                            | DialectType::RisingWave
29637                            | DialectType::Redshift => {
29638                                if let Some(pos) = position {
29639                                    // Build: CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM pos)) = 0 THEN 0
29640                                    //   ELSE POSITION(...) + pos - 1 END
29641                                    let substr = Expression::Substring(Box::new(
29642                                        crate::expressions::SubstringFunc {
29643                                            this: haystack,
29644                                            start: pos.clone(),
29645                                            length: Option::None,
29646                                            from_for_syntax: true,
29647                                        },
29648                                    ));
29649                                    let pos_in = Expression::StrPosition(Box::new(
29650                                        crate::expressions::StrPosition {
29651                                            this: Box::new(substr),
29652                                            substr: Some(Box::new(needle)),
29653                                            position: Option::None,
29654                                            occurrence: Option::None,
29655                                        },
29656                                    ));
29657                                    let zero = Expression::Literal(Box::new(Literal::Number(
29658                                        "0".to_string(),
29659                                    )));
29660                                    let one = Expression::Literal(Box::new(Literal::Number(
29661                                        "1".to_string(),
29662                                    )));
29663                                    let eq_zero = Expression::Eq(Box::new(BinaryOp {
29664                                        left: pos_in.clone(),
29665                                        right: zero.clone(),
29666                                        left_comments: Vec::new(),
29667                                        operator_comments: Vec::new(),
29668                                        trailing_comments: Vec::new(),
29669                                        inferred_type: None,
29670                                    }));
29671                                    let add_pos = Expression::Add(Box::new(BinaryOp {
29672                                        left: pos_in,
29673                                        right: pos,
29674                                        left_comments: Vec::new(),
29675                                        operator_comments: Vec::new(),
29676                                        trailing_comments: Vec::new(),
29677                                        inferred_type: None,
29678                                    }));
29679                                    let sub_one = Expression::Sub(Box::new(BinaryOp {
29680                                        left: add_pos,
29681                                        right: one,
29682                                        left_comments: Vec::new(),
29683                                        operator_comments: Vec::new(),
29684                                        trailing_comments: Vec::new(),
29685                                        inferred_type: None,
29686                                    }));
29687                                    Ok(Expression::Case(Box::new(Case {
29688                                        operand: Option::None,
29689                                        whens: vec![(eq_zero, zero)],
29690                                        else_: Some(sub_one),
29691                                        comments: Vec::new(),
29692                                        inferred_type: None,
29693                                    })))
29694                                } else {
29695                                    Ok(Expression::StrPosition(Box::new(
29696                                        crate::expressions::StrPosition {
29697                                            this: Box::new(haystack),
29698                                            substr: Some(Box::new(needle)),
29699                                            position: Option::None,
29700                                            occurrence: Option::None,
29701                                        },
29702                                    )))
29703                                }
29704                            }
29705                            // LOCATE group: MySQL, Hive, Spark, Databricks, Doris
29706                            DialectType::MySQL
29707                            | DialectType::SingleStore
29708                            | DialectType::TiDB
29709                            | DialectType::Hive
29710                            | DialectType::Spark
29711                            | DialectType::Databricks
29712                            | DialectType::Doris
29713                            | DialectType::StarRocks => {
29714                                let mut a = vec![needle, haystack];
29715                                if let Some(pos) = position {
29716                                    a.push(pos);
29717                                }
29718                                Ok(Expression::Function(Box::new(Function::new(
29719                                    "LOCATE".to_string(),
29720                                    a,
29721                                ))))
29722                            }
29723                            // ClickHouse: POSITION(haystack, needle[, position])
29724                            DialectType::ClickHouse => {
29725                                let mut a = vec![haystack, needle];
29726                                if let Some(pos) = position {
29727                                    a.push(pos);
29728                                }
29729                                Ok(Expression::Function(Box::new(Function::new(
29730                                    "POSITION".to_string(),
29731                                    a,
29732                                ))))
29733                            }
29734                            _ => {
29735                                let mut a = vec![haystack, needle];
29736                                if let Some(pos) = position {
29737                                    a.push(pos);
29738                                }
29739                                if let Some(occ) = occurrence {
29740                                    a.push(occ);
29741                                }
29742                                Ok(Expression::Function(Box::new(Function::new(
29743                                    "STR_POSITION".to_string(),
29744                                    a,
29745                                ))))
29746                            }
29747                        }
29748                    } else {
29749                        Ok(e)
29750                    }
29751                }
29752
29753                Action::ArraySumConvert => {
29754                    // ARRAY_SUM(arr) -> dialect-specific
29755                    if let Expression::Function(f) = e {
29756                        let args = f.args;
29757                        match target {
29758                            DialectType::DuckDB => Ok(Expression::Function(Box::new(
29759                                Function::new("LIST_SUM".to_string(), args),
29760                            ))),
29761                            DialectType::Spark | DialectType::Databricks => {
29762                                // AGGREGATE(arr, 0, (acc, x) -> acc + x, acc -> acc)
29763                                let arr = args.into_iter().next().unwrap();
29764                                let zero =
29765                                    Expression::Literal(Box::new(Literal::Number("0".to_string())));
29766                                let acc_id = Identifier::new("acc");
29767                                let x_id = Identifier::new("x");
29768                                let acc = Expression::Identifier(acc_id.clone());
29769                                let x = Expression::Identifier(x_id.clone());
29770                                let add = Expression::Add(Box::new(BinaryOp {
29771                                    left: acc.clone(),
29772                                    right: x,
29773                                    left_comments: Vec::new(),
29774                                    operator_comments: Vec::new(),
29775                                    trailing_comments: Vec::new(),
29776                                    inferred_type: None,
29777                                }));
29778                                let lambda1 =
29779                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29780                                        parameters: vec![acc_id.clone(), x_id],
29781                                        body: add,
29782                                        colon: false,
29783                                        parameter_types: Vec::new(),
29784                                    }));
29785                                let lambda2 =
29786                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29787                                        parameters: vec![acc_id],
29788                                        body: acc,
29789                                        colon: false,
29790                                        parameter_types: Vec::new(),
29791                                    }));
29792                                Ok(Expression::Function(Box::new(Function::new(
29793                                    "AGGREGATE".to_string(),
29794                                    vec![arr, zero, lambda1, lambda2],
29795                                ))))
29796                            }
29797                            DialectType::Presto | DialectType::Athena => {
29798                                // Presto/Athena keep ARRAY_SUM natively
29799                                Ok(Expression::Function(Box::new(Function::new(
29800                                    "ARRAY_SUM".to_string(),
29801                                    args,
29802                                ))))
29803                            }
29804                            DialectType::Trino => {
29805                                // REDUCE(arr, 0, (acc, x) -> acc + x, acc -> acc)
29806                                if args.len() == 1 {
29807                                    let arr = args.into_iter().next().unwrap();
29808                                    let zero = Expression::Literal(Box::new(Literal::Number(
29809                                        "0".to_string(),
29810                                    )));
29811                                    let acc_id = Identifier::new("acc");
29812                                    let x_id = Identifier::new("x");
29813                                    let acc = Expression::Identifier(acc_id.clone());
29814                                    let x = Expression::Identifier(x_id.clone());
29815                                    let add = Expression::Add(Box::new(BinaryOp {
29816                                        left: acc.clone(),
29817                                        right: x,
29818                                        left_comments: Vec::new(),
29819                                        operator_comments: Vec::new(),
29820                                        trailing_comments: Vec::new(),
29821                                        inferred_type: None,
29822                                    }));
29823                                    let lambda1 = Expression::Lambda(Box::new(
29824                                        crate::expressions::LambdaExpr {
29825                                            parameters: vec![acc_id.clone(), x_id],
29826                                            body: add,
29827                                            colon: false,
29828                                            parameter_types: Vec::new(),
29829                                        },
29830                                    ));
29831                                    let lambda2 = Expression::Lambda(Box::new(
29832                                        crate::expressions::LambdaExpr {
29833                                            parameters: vec![acc_id],
29834                                            body: acc,
29835                                            colon: false,
29836                                            parameter_types: Vec::new(),
29837                                        },
29838                                    ));
29839                                    Ok(Expression::Function(Box::new(Function::new(
29840                                        "REDUCE".to_string(),
29841                                        vec![arr, zero, lambda1, lambda2],
29842                                    ))))
29843                                } else {
29844                                    Ok(Expression::Function(Box::new(Function::new(
29845                                        "ARRAY_SUM".to_string(),
29846                                        args,
29847                                    ))))
29848                                }
29849                            }
29850                            DialectType::ClickHouse => {
29851                                // arraySum(lambda, arr) or arraySum(arr)
29852                                Ok(Expression::Function(Box::new(Function::new(
29853                                    "arraySum".to_string(),
29854                                    args,
29855                                ))))
29856                            }
29857                            _ => Ok(Expression::Function(Box::new(Function::new(
29858                                "ARRAY_SUM".to_string(),
29859                                args,
29860                            )))),
29861                        }
29862                    } else {
29863                        Ok(e)
29864                    }
29865                }
29866
29867                Action::ArraySizeConvert => {
29868                    if let Expression::Function(f) = e {
29869                        Ok(Expression::Function(Box::new(Function::new(
29870                            "REPEATED_COUNT".to_string(),
29871                            f.args,
29872                        ))))
29873                    } else {
29874                        Ok(e)
29875                    }
29876                }
29877
29878                Action::ArrayAnyConvert => {
29879                    if let Expression::Function(f) = e {
29880                        let mut args = f.args;
29881                        if args.len() == 2 {
29882                            let arr = args.remove(0);
29883                            let lambda = args.remove(0);
29884
29885                            // Extract lambda parameter name and body
29886                            let (param_name, pred_body) =
29887                                if let Expression::Lambda(ref lam) = lambda {
29888                                    let name = if let Some(p) = lam.parameters.first() {
29889                                        p.name.clone()
29890                                    } else {
29891                                        "x".to_string()
29892                                    };
29893                                    (name, lam.body.clone())
29894                                } else {
29895                                    ("x".to_string(), lambda.clone())
29896                                };
29897
29898                            // Helper: build a function call Expression
29899                            let make_func = |name: &str, args: Vec<Expression>| -> Expression {
29900                                Expression::Function(Box::new(Function::new(
29901                                    name.to_string(),
29902                                    args,
29903                                )))
29904                            };
29905
29906                            // Helper: build (len_func(arr) = 0 OR len_func(filter_expr) <> 0) wrapped in Paren
29907                            let build_filter_pattern = |len_func: &str,
29908                                                        len_args_extra: Vec<Expression>,
29909                                                        filter_expr: Expression|
29910                             -> Expression {
29911                                // len_func(arr, ...extra) = 0
29912                                let mut len_arr_args = vec![arr.clone()];
29913                                len_arr_args.extend(len_args_extra.clone());
29914                                let len_arr = make_func(len_func, len_arr_args);
29915                                let eq_zero = Expression::Eq(Box::new(BinaryOp::new(
29916                                    len_arr,
29917                                    Expression::number(0),
29918                                )));
29919
29920                                // len_func(filter_expr, ...extra) <> 0
29921                                let mut len_filter_args = vec![filter_expr];
29922                                len_filter_args.extend(len_args_extra);
29923                                let len_filter = make_func(len_func, len_filter_args);
29924                                let neq_zero = Expression::Neq(Box::new(BinaryOp::new(
29925                                    len_filter,
29926                                    Expression::number(0),
29927                                )));
29928
29929                                // (eq_zero OR neq_zero)
29930                                let or_expr =
29931                                    Expression::Or(Box::new(BinaryOp::new(eq_zero, neq_zero)));
29932                                Expression::Paren(Box::new(Paren {
29933                                    this: or_expr,
29934                                    trailing_comments: Vec::new(),
29935                                }))
29936                            };
29937
29938                            match target {
29939                                DialectType::Trino | DialectType::Presto | DialectType::Athena => {
29940                                    Ok(make_func("ANY_MATCH", vec![arr, lambda]))
29941                                }
29942                                DialectType::ClickHouse => {
29943                                    // (LENGTH(arr) = 0 OR LENGTH(arrayFilter(x -> pred, arr)) <> 0)
29944                                    // ClickHouse arrayFilter takes lambda first, then array
29945                                    let filter_expr =
29946                                        make_func("arrayFilter", vec![lambda, arr.clone()]);
29947                                    Ok(build_filter_pattern("LENGTH", vec![], filter_expr))
29948                                }
29949                                DialectType::Databricks | DialectType::Spark => {
29950                                    // (SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)
29951                                    let filter_expr =
29952                                        make_func("FILTER", vec![arr.clone(), lambda]);
29953                                    Ok(build_filter_pattern("SIZE", vec![], filter_expr))
29954                                }
29955                                DialectType::DuckDB => {
29956                                    // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(LIST_FILTER(arr, x -> pred)) <> 0)
29957                                    let filter_expr =
29958                                        make_func("LIST_FILTER", vec![arr.clone(), lambda]);
29959                                    Ok(build_filter_pattern("ARRAY_LENGTH", vec![], filter_expr))
29960                                }
29961                                DialectType::Teradata => {
29962                                    // (CARDINALITY(arr) = 0 OR CARDINALITY(FILTER(arr, x -> pred)) <> 0)
29963                                    let filter_expr =
29964                                        make_func("FILTER", vec![arr.clone(), lambda]);
29965                                    Ok(build_filter_pattern("CARDINALITY", vec![], filter_expr))
29966                                }
29967                                DialectType::BigQuery => {
29968                                    // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS x WHERE pred)) <> 0)
29969                                    // Build: SELECT x FROM UNNEST(arr) AS x WHERE pred
29970                                    let param_col = Expression::column(&param_name);
29971                                    let unnest_expr = Expression::Unnest(Box::new(
29972                                        crate::expressions::UnnestFunc {
29973                                            this: arr.clone(),
29974                                            expressions: vec![],
29975                                            with_ordinality: false,
29976                                            alias: Some(Identifier::new(&param_name)),
29977                                            offset_alias: None,
29978                                        },
29979                                    ));
29980                                    let mut sel = crate::expressions::Select::default();
29981                                    sel.expressions = vec![param_col];
29982                                    sel.from = Some(crate::expressions::From {
29983                                        expressions: vec![unnest_expr],
29984                                    });
29985                                    sel.where_clause =
29986                                        Some(crate::expressions::Where { this: pred_body });
29987                                    let array_subquery =
29988                                        make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
29989                                    Ok(build_filter_pattern("ARRAY_LENGTH", vec![], array_subquery))
29990                                }
29991                                DialectType::PostgreSQL => {
29992                                    // (ARRAY_LENGTH(arr, 1) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred), 1) <> 0)
29993                                    // Build: SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred
29994                                    let param_col = Expression::column(&param_name);
29995                                    // For PostgreSQL, UNNEST uses AS _t0(x) syntax - use TableAlias
29996                                    let unnest_with_alias =
29997                                        Expression::Alias(Box::new(crate::expressions::Alias {
29998                                            this: Expression::Unnest(Box::new(
29999                                                crate::expressions::UnnestFunc {
30000                                                    this: arr.clone(),
30001                                                    expressions: vec![],
30002                                                    with_ordinality: false,
30003                                                    alias: None,
30004                                                    offset_alias: None,
30005                                                },
30006                                            )),
30007                                            alias: Identifier::new("_t0"),
30008                                            column_aliases: vec![Identifier::new(&param_name)],
30009                                            alias_explicit_as: false,
30010                                            alias_keyword: None,
30011                                            pre_alias_comments: Vec::new(),
30012                                            trailing_comments: Vec::new(),
30013                                            inferred_type: None,
30014                                        }));
30015                                    let mut sel = crate::expressions::Select::default();
30016                                    sel.expressions = vec![param_col];
30017                                    sel.from = Some(crate::expressions::From {
30018                                        expressions: vec![unnest_with_alias],
30019                                    });
30020                                    sel.where_clause =
30021                                        Some(crate::expressions::Where { this: pred_body });
30022                                    let array_subquery =
30023                                        make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
30024                                    Ok(build_filter_pattern(
30025                                        "ARRAY_LENGTH",
30026                                        vec![Expression::number(1)],
30027                                        array_subquery,
30028                                    ))
30029                                }
30030                                _ => Ok(Expression::Function(Box::new(Function::new(
30031                                    "ARRAY_ANY".to_string(),
30032                                    vec![arr, lambda],
30033                                )))),
30034                            }
30035                        } else {
30036                            Ok(Expression::Function(Box::new(Function::new(
30037                                "ARRAY_ANY".to_string(),
30038                                args,
30039                            ))))
30040                        }
30041                    } else {
30042                        Ok(e)
30043                    }
30044                }
30045
30046                Action::DecodeSimplify => {
30047                    // DECODE(x, search1, result1, ..., default) -> CASE WHEN ... THEN result1 ... [ELSE default] END
30048                    // For literal search values: CASE WHEN x = search THEN result
30049                    // For NULL search: CASE WHEN x IS NULL THEN result
30050                    // For non-literal (column, expr): CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
30051                    fn is_decode_literal(e: &Expression) -> bool {
30052                        matches!(
30053                            e,
30054                            Expression::Literal(_) | Expression::Boolean(_) | Expression::Neg(_)
30055                        )
30056                    }
30057
30058                    let build_decode_case =
30059                        |this_expr: Expression,
30060                         pairs: Vec<(Expression, Expression)>,
30061                         default: Option<Expression>| {
30062                            let whens: Vec<(Expression, Expression)> = pairs
30063                                .into_iter()
30064                                .map(|(search, result)| {
30065                                    if matches!(&search, Expression::Null(_)) {
30066                                        // NULL search -> IS NULL
30067                                        let condition = Expression::Is(Box::new(BinaryOp {
30068                                            left: this_expr.clone(),
30069                                            right: Expression::Null(crate::expressions::Null),
30070                                            left_comments: Vec::new(),
30071                                            operator_comments: Vec::new(),
30072                                            trailing_comments: Vec::new(),
30073                                            inferred_type: None,
30074                                        }));
30075                                        (condition, result)
30076                                    } else if is_decode_literal(&search)
30077                                        || is_decode_literal(&this_expr)
30078                                    {
30079                                        // At least one side is a literal -> simple equality (no NULL check needed)
30080                                        let eq = Expression::Eq(Box::new(BinaryOp {
30081                                            left: this_expr.clone(),
30082                                            right: search,
30083                                            left_comments: Vec::new(),
30084                                            operator_comments: Vec::new(),
30085                                            trailing_comments: Vec::new(),
30086                                            inferred_type: None,
30087                                        }));
30088                                        (eq, result)
30089                                    } else {
30090                                        // Non-literal -> null-safe comparison
30091                                        let needs_paren = matches!(
30092                                            &search,
30093                                            Expression::Eq(_)
30094                                                | Expression::Neq(_)
30095                                                | Expression::Gt(_)
30096                                                | Expression::Gte(_)
30097                                                | Expression::Lt(_)
30098                                                | Expression::Lte(_)
30099                                        );
30100                                        let search_ref = if needs_paren {
30101                                            Expression::Paren(Box::new(crate::expressions::Paren {
30102                                                this: search.clone(),
30103                                                trailing_comments: Vec::new(),
30104                                            }))
30105                                        } else {
30106                                            search.clone()
30107                                        };
30108                                        // Build: x = search OR (x IS NULL AND search IS NULL)
30109                                        let eq = Expression::Eq(Box::new(BinaryOp {
30110                                            left: this_expr.clone(),
30111                                            right: search_ref,
30112                                            left_comments: Vec::new(),
30113                                            operator_comments: Vec::new(),
30114                                            trailing_comments: Vec::new(),
30115                                            inferred_type: None,
30116                                        }));
30117                                        let search_in_null = if needs_paren {
30118                                            Expression::Paren(Box::new(crate::expressions::Paren {
30119                                                this: search.clone(),
30120                                                trailing_comments: Vec::new(),
30121                                            }))
30122                                        } else {
30123                                            search.clone()
30124                                        };
30125                                        let x_is_null = Expression::Is(Box::new(BinaryOp {
30126                                            left: this_expr.clone(),
30127                                            right: Expression::Null(crate::expressions::Null),
30128                                            left_comments: Vec::new(),
30129                                            operator_comments: Vec::new(),
30130                                            trailing_comments: Vec::new(),
30131                                            inferred_type: None,
30132                                        }));
30133                                        let search_is_null = Expression::Is(Box::new(BinaryOp {
30134                                            left: search_in_null,
30135                                            right: Expression::Null(crate::expressions::Null),
30136                                            left_comments: Vec::new(),
30137                                            operator_comments: Vec::new(),
30138                                            trailing_comments: Vec::new(),
30139                                            inferred_type: None,
30140                                        }));
30141                                        let both_null = Expression::And(Box::new(BinaryOp {
30142                                            left: x_is_null,
30143                                            right: search_is_null,
30144                                            left_comments: Vec::new(),
30145                                            operator_comments: Vec::new(),
30146                                            trailing_comments: Vec::new(),
30147                                            inferred_type: None,
30148                                        }));
30149                                        let condition = Expression::Or(Box::new(BinaryOp {
30150                                            left: eq,
30151                                            right: Expression::Paren(Box::new(
30152                                                crate::expressions::Paren {
30153                                                    this: both_null,
30154                                                    trailing_comments: Vec::new(),
30155                                                },
30156                                            )),
30157                                            left_comments: Vec::new(),
30158                                            operator_comments: Vec::new(),
30159                                            trailing_comments: Vec::new(),
30160                                            inferred_type: None,
30161                                        }));
30162                                        (condition, result)
30163                                    }
30164                                })
30165                                .collect();
30166                            Expression::Case(Box::new(Case {
30167                                operand: None,
30168                                whens,
30169                                else_: default,
30170                                comments: Vec::new(),
30171                                inferred_type: None,
30172                            }))
30173                        };
30174
30175                    if let Expression::Decode(decode) = e {
30176                        Ok(build_decode_case(
30177                            decode.this,
30178                            decode.search_results,
30179                            decode.default,
30180                        ))
30181                    } else if let Expression::DecodeCase(dc) = e {
30182                        // DecodeCase has flat expressions: [x, s1, r1, s2, r2, ..., default?]
30183                        let mut exprs = dc.expressions;
30184                        if exprs.len() < 3 {
30185                            return Ok(Expression::DecodeCase(Box::new(
30186                                crate::expressions::DecodeCase { expressions: exprs },
30187                            )));
30188                        }
30189                        let this_expr = exprs.remove(0);
30190                        let mut pairs = Vec::new();
30191                        let mut default = None;
30192                        let mut i = 0;
30193                        while i + 1 < exprs.len() {
30194                            pairs.push((exprs[i].clone(), exprs[i + 1].clone()));
30195                            i += 2;
30196                        }
30197                        if i < exprs.len() {
30198                            // Odd remaining element is the default
30199                            default = Some(exprs[i].clone());
30200                        }
30201                        Ok(build_decode_case(this_expr, pairs, default))
30202                    } else {
30203                        Ok(e)
30204                    }
30205                }
30206
30207                Action::CreateTableLikeToCtas => {
30208                    // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
30209                    if let Expression::CreateTable(ct) = e {
30210                        let like_source = ct.constraints.iter().find_map(|c| {
30211                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
30212                                Some(source.clone())
30213                            } else {
30214                                None
30215                            }
30216                        });
30217                        if let Some(source_table) = like_source {
30218                            let mut new_ct = *ct;
30219                            new_ct.constraints.clear();
30220                            // Build: SELECT * FROM b LIMIT 0
30221                            let select = Expression::Select(Box::new(crate::expressions::Select {
30222                                expressions: vec![Expression::Star(crate::expressions::Star {
30223                                    table: None,
30224                                    except: None,
30225                                    replace: None,
30226                                    rename: None,
30227                                    trailing_comments: Vec::new(),
30228                                    span: None,
30229                                })],
30230                                from: Some(crate::expressions::From {
30231                                    expressions: vec![Expression::Table(Box::new(source_table))],
30232                                }),
30233                                limit: Some(crate::expressions::Limit {
30234                                    this: Expression::Literal(Box::new(Literal::Number(
30235                                        "0".to_string(),
30236                                    ))),
30237                                    percent: false,
30238                                    comments: Vec::new(),
30239                                }),
30240                                ..Default::default()
30241                            }));
30242                            new_ct.as_select = Some(select);
30243                            Ok(Expression::CreateTable(Box::new(new_ct)))
30244                        } else {
30245                            Ok(Expression::CreateTable(ct))
30246                        }
30247                    } else {
30248                        Ok(e)
30249                    }
30250                }
30251
30252                Action::CreateTableLikeToSelectInto => {
30253                    // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
30254                    if let Expression::CreateTable(ct) = e {
30255                        let like_source = ct.constraints.iter().find_map(|c| {
30256                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
30257                                Some(source.clone())
30258                            } else {
30259                                None
30260                            }
30261                        });
30262                        if let Some(source_table) = like_source {
30263                            let mut aliased_source = source_table;
30264                            aliased_source.alias = Some(Identifier::new("temp"));
30265                            // Build: SELECT TOP 0 * INTO a FROM b AS temp
30266                            let select = Expression::Select(Box::new(crate::expressions::Select {
30267                                expressions: vec![Expression::Star(crate::expressions::Star {
30268                                    table: None,
30269                                    except: None,
30270                                    replace: None,
30271                                    rename: None,
30272                                    trailing_comments: Vec::new(),
30273                                    span: None,
30274                                })],
30275                                from: Some(crate::expressions::From {
30276                                    expressions: vec![Expression::Table(Box::new(aliased_source))],
30277                                }),
30278                                into: Some(crate::expressions::SelectInto {
30279                                    this: Expression::Table(Box::new(ct.name.clone())),
30280                                    temporary: false,
30281                                    unlogged: false,
30282                                    bulk_collect: false,
30283                                    expressions: Vec::new(),
30284                                }),
30285                                top: Some(crate::expressions::Top {
30286                                    this: Expression::Literal(Box::new(Literal::Number(
30287                                        "0".to_string(),
30288                                    ))),
30289                                    percent: false,
30290                                    with_ties: false,
30291                                    parenthesized: false,
30292                                }),
30293                                ..Default::default()
30294                            }));
30295                            Ok(select)
30296                        } else {
30297                            Ok(Expression::CreateTable(ct))
30298                        }
30299                    } else {
30300                        Ok(e)
30301                    }
30302                }
30303
30304                Action::CreateTableLikeToAs => {
30305                    // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
30306                    if let Expression::CreateTable(ct) = e {
30307                        let like_source = ct.constraints.iter().find_map(|c| {
30308                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
30309                                Some(source.clone())
30310                            } else {
30311                                None
30312                            }
30313                        });
30314                        if let Some(source_table) = like_source {
30315                            let mut new_ct = *ct;
30316                            new_ct.constraints.clear();
30317                            // AS b (just a table reference, not a SELECT)
30318                            new_ct.as_select = Some(Expression::Table(Box::new(source_table)));
30319                            Ok(Expression::CreateTable(Box::new(new_ct)))
30320                        } else {
30321                            Ok(Expression::CreateTable(ct))
30322                        }
30323                    } else {
30324                        Ok(e)
30325                    }
30326                }
30327
30328                Action::TsOrDsToDateConvert => {
30329                    // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific date conversion
30330                    if let Expression::Function(f) = e {
30331                        let mut args = f.args;
30332                        let this = args.remove(0);
30333                        let fmt = if !args.is_empty() {
30334                            match &args[0] {
30335                                Expression::Literal(lit)
30336                                    if matches!(lit.as_ref(), Literal::String(_)) =>
30337                                {
30338                                    let Literal::String(s) = lit.as_ref() else {
30339                                        unreachable!()
30340                                    };
30341                                    Some(s.clone())
30342                                }
30343                                _ => None,
30344                            }
30345                        } else {
30346                            None
30347                        };
30348                        Ok(Expression::TsOrDsToDate(Box::new(
30349                            crate::expressions::TsOrDsToDate {
30350                                this: Box::new(this),
30351                                format: fmt,
30352                                safe: None,
30353                            },
30354                        )))
30355                    } else {
30356                        Ok(e)
30357                    }
30358                }
30359
30360                Action::TsOrDsToDateStrConvert => {
30361                    // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
30362                    if let Expression::Function(f) = e {
30363                        let arg = f.args.into_iter().next().unwrap();
30364                        let str_type = match target {
30365                            DialectType::DuckDB
30366                            | DialectType::PostgreSQL
30367                            | DialectType::Materialize => DataType::Text,
30368                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
30369                                DataType::Custom {
30370                                    name: "STRING".to_string(),
30371                                }
30372                            }
30373                            DialectType::Presto
30374                            | DialectType::Trino
30375                            | DialectType::Athena
30376                            | DialectType::Drill => DataType::VarChar {
30377                                length: None,
30378                                parenthesized_length: false,
30379                            },
30380                            DialectType::MySQL | DialectType::Doris | DialectType::StarRocks => {
30381                                DataType::Custom {
30382                                    name: "STRING".to_string(),
30383                                }
30384                            }
30385                            _ => DataType::VarChar {
30386                                length: None,
30387                                parenthesized_length: false,
30388                            },
30389                        };
30390                        let cast_expr = Expression::Cast(Box::new(Cast {
30391                            this: arg,
30392                            to: str_type,
30393                            double_colon_syntax: false,
30394                            trailing_comments: Vec::new(),
30395                            format: None,
30396                            default: None,
30397                            inferred_type: None,
30398                        }));
30399                        Ok(Expression::Substring(Box::new(
30400                            crate::expressions::SubstringFunc {
30401                                this: cast_expr,
30402                                start: Expression::number(1),
30403                                length: Some(Expression::number(10)),
30404                                from_for_syntax: false,
30405                            },
30406                        )))
30407                    } else {
30408                        Ok(e)
30409                    }
30410                }
30411
30412                Action::DateStrToDateConvert => {
30413                    // DATE_STR_TO_DATE(x) -> dialect-specific
30414                    if let Expression::Function(f) = e {
30415                        let arg = f.args.into_iter().next().unwrap();
30416                        match target {
30417                            DialectType::SQLite => {
30418                                // SQLite: just the bare expression (dates are strings)
30419                                Ok(arg)
30420                            }
30421                            _ => Ok(Expression::Cast(Box::new(Cast {
30422                                this: arg,
30423                                to: DataType::Date,
30424                                double_colon_syntax: false,
30425                                trailing_comments: Vec::new(),
30426                                format: None,
30427                                default: None,
30428                                inferred_type: None,
30429                            }))),
30430                        }
30431                    } else {
30432                        Ok(e)
30433                    }
30434                }
30435
30436                Action::TimeStrToDateConvert => {
30437                    // TIME_STR_TO_DATE(x) -> dialect-specific
30438                    if let Expression::Function(f) = e {
30439                        let arg = f.args.into_iter().next().unwrap();
30440                        match target {
30441                            DialectType::Hive
30442                            | DialectType::Doris
30443                            | DialectType::StarRocks
30444                            | DialectType::Snowflake => Ok(Expression::Function(Box::new(
30445                                Function::new("TO_DATE".to_string(), vec![arg]),
30446                            ))),
30447                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30448                                // Presto: CAST(x AS TIMESTAMP)
30449                                Ok(Expression::Cast(Box::new(Cast {
30450                                    this: arg,
30451                                    to: DataType::Timestamp {
30452                                        timezone: false,
30453                                        precision: None,
30454                                    },
30455                                    double_colon_syntax: false,
30456                                    trailing_comments: Vec::new(),
30457                                    format: None,
30458                                    default: None,
30459                                    inferred_type: None,
30460                                })))
30461                            }
30462                            _ => {
30463                                // Default: CAST(x AS DATE)
30464                                Ok(Expression::Cast(Box::new(Cast {
30465                                    this: arg,
30466                                    to: DataType::Date,
30467                                    double_colon_syntax: false,
30468                                    trailing_comments: Vec::new(),
30469                                    format: None,
30470                                    default: None,
30471                                    inferred_type: None,
30472                                })))
30473                            }
30474                        }
30475                    } else {
30476                        Ok(e)
30477                    }
30478                }
30479
30480                Action::TimeStrToTimeConvert => {
30481                    // TIME_STR_TO_TIME(x[, zone]) -> dialect-specific CAST to timestamp type
30482                    if let Expression::Function(f) = e {
30483                        let mut args = f.args;
30484                        let this = args.remove(0);
30485                        let zone = if !args.is_empty() {
30486                            match &args[0] {
30487                                Expression::Literal(lit)
30488                                    if matches!(lit.as_ref(), Literal::String(_)) =>
30489                                {
30490                                    let Literal::String(s) = lit.as_ref() else {
30491                                        unreachable!()
30492                                    };
30493                                    Some(s.clone())
30494                                }
30495                                _ => None,
30496                            }
30497                        } else {
30498                            None
30499                        };
30500                        let has_zone = zone.is_some();
30501
30502                        match target {
30503                            DialectType::SQLite => {
30504                                // SQLite: just the bare expression
30505                                Ok(this)
30506                            }
30507                            DialectType::MySQL => {
30508                                if has_zone {
30509                                    // MySQL with zone: TIMESTAMP(x)
30510                                    Ok(Expression::Function(Box::new(Function::new(
30511                                        "TIMESTAMP".to_string(),
30512                                        vec![this],
30513                                    ))))
30514                                } else {
30515                                    // MySQL: CAST(x AS DATETIME) or with precision
30516                                    // Use DataType::Custom to avoid MySQL's transform_cast converting
30517                                    // CAST(x AS TIMESTAMP) -> TIMESTAMP(x)
30518                                    let precision = if let Expression::Literal(ref lit) = this {
30519                                        if let Literal::String(ref s) = lit.as_ref() {
30520                                            if let Some(dot_pos) = s.rfind('.') {
30521                                                let frac = &s[dot_pos + 1..];
30522                                                let digit_count = frac
30523                                                    .chars()
30524                                                    .take_while(|c| c.is_ascii_digit())
30525                                                    .count();
30526                                                if digit_count > 0 {
30527                                                    Some(digit_count)
30528                                                } else {
30529                                                    None
30530                                                }
30531                                            } else {
30532                                                None
30533                                            }
30534                                        } else {
30535                                            None
30536                                        }
30537                                    } else {
30538                                        None
30539                                    };
30540                                    let type_name = match precision {
30541                                        Some(p) => format!("DATETIME({})", p),
30542                                        None => "DATETIME".to_string(),
30543                                    };
30544                                    Ok(Expression::Cast(Box::new(Cast {
30545                                        this,
30546                                        to: DataType::Custom { name: type_name },
30547                                        double_colon_syntax: false,
30548                                        trailing_comments: Vec::new(),
30549                                        format: None,
30550                                        default: None,
30551                                        inferred_type: None,
30552                                    })))
30553                                }
30554                            }
30555                            DialectType::ClickHouse => {
30556                                if has_zone {
30557                                    // ClickHouse with zone: CAST(x AS DateTime64(6, 'zone'))
30558                                    // We need to strip the timezone offset from the literal if present
30559                                    let clean_this = if let Expression::Literal(ref lit) = this {
30560                                        if let Literal::String(ref s) = lit.as_ref() {
30561                                            // Strip timezone offset like "-08:00" or "+00:00"
30562                                            let re_offset = s.rfind(|c: char| c == '+' || c == '-');
30563                                            if let Some(offset_pos) = re_offset {
30564                                                if offset_pos > 10 {
30565                                                    // After the date part
30566                                                    let trimmed = s[..offset_pos].to_string();
30567                                                    Expression::Literal(Box::new(Literal::String(
30568                                                        trimmed,
30569                                                    )))
30570                                                } else {
30571                                                    this.clone()
30572                                                }
30573                                            } else {
30574                                                this.clone()
30575                                            }
30576                                        } else {
30577                                            this.clone()
30578                                        }
30579                                    } else {
30580                                        this.clone()
30581                                    };
30582                                    let zone_str = zone.unwrap();
30583                                    // Build: CAST(x AS DateTime64(6, 'zone'))
30584                                    let type_name = format!("DateTime64(6, '{}')", zone_str);
30585                                    Ok(Expression::Cast(Box::new(Cast {
30586                                        this: clean_this,
30587                                        to: DataType::Custom { name: type_name },
30588                                        double_colon_syntax: false,
30589                                        trailing_comments: Vec::new(),
30590                                        format: None,
30591                                        default: None,
30592                                        inferred_type: None,
30593                                    })))
30594                                } else {
30595                                    Ok(Expression::Cast(Box::new(Cast {
30596                                        this,
30597                                        to: DataType::Custom {
30598                                            name: "DateTime64(6)".to_string(),
30599                                        },
30600                                        double_colon_syntax: false,
30601                                        trailing_comments: Vec::new(),
30602                                        format: None,
30603                                        default: None,
30604                                        inferred_type: None,
30605                                    })))
30606                                }
30607                            }
30608                            DialectType::BigQuery => {
30609                                if has_zone {
30610                                    // BigQuery with zone: CAST(x AS TIMESTAMP)
30611                                    Ok(Expression::Cast(Box::new(Cast {
30612                                        this,
30613                                        to: DataType::Timestamp {
30614                                            timezone: false,
30615                                            precision: None,
30616                                        },
30617                                        double_colon_syntax: false,
30618                                        trailing_comments: Vec::new(),
30619                                        format: None,
30620                                        default: None,
30621                                        inferred_type: None,
30622                                    })))
30623                                } else {
30624                                    // BigQuery: CAST(x AS DATETIME) - Timestamp{tz:false} renders as DATETIME for BigQuery
30625                                    Ok(Expression::Cast(Box::new(Cast {
30626                                        this,
30627                                        to: DataType::Custom {
30628                                            name: "DATETIME".to_string(),
30629                                        },
30630                                        double_colon_syntax: false,
30631                                        trailing_comments: Vec::new(),
30632                                        format: None,
30633                                        default: None,
30634                                        inferred_type: None,
30635                                    })))
30636                                }
30637                            }
30638                            DialectType::Doris => {
30639                                // Doris: CAST(x AS DATETIME)
30640                                Ok(Expression::Cast(Box::new(Cast {
30641                                    this,
30642                                    to: DataType::Custom {
30643                                        name: "DATETIME".to_string(),
30644                                    },
30645                                    double_colon_syntax: false,
30646                                    trailing_comments: Vec::new(),
30647                                    format: None,
30648                                    default: None,
30649                                    inferred_type: None,
30650                                })))
30651                            }
30652                            DialectType::TSQL | DialectType::Fabric => {
30653                                if has_zone {
30654                                    // TSQL with zone: CAST(x AS DATETIMEOFFSET) AT TIME ZONE 'UTC'
30655                                    let cast_expr = Expression::Cast(Box::new(Cast {
30656                                        this,
30657                                        to: DataType::Custom {
30658                                            name: "DATETIMEOFFSET".to_string(),
30659                                        },
30660                                        double_colon_syntax: false,
30661                                        trailing_comments: Vec::new(),
30662                                        format: None,
30663                                        default: None,
30664                                        inferred_type: None,
30665                                    }));
30666                                    Ok(Expression::AtTimeZone(Box::new(
30667                                        crate::expressions::AtTimeZone {
30668                                            this: cast_expr,
30669                                            zone: Expression::Literal(Box::new(Literal::String(
30670                                                "UTC".to_string(),
30671                                            ))),
30672                                        },
30673                                    )))
30674                                } else {
30675                                    // TSQL: CAST(x AS DATETIME2)
30676                                    Ok(Expression::Cast(Box::new(Cast {
30677                                        this,
30678                                        to: DataType::Custom {
30679                                            name: "DATETIME2".to_string(),
30680                                        },
30681                                        double_colon_syntax: false,
30682                                        trailing_comments: Vec::new(),
30683                                        format: None,
30684                                        default: None,
30685                                        inferred_type: None,
30686                                    })))
30687                                }
30688                            }
30689                            DialectType::DuckDB => {
30690                                if has_zone {
30691                                    // DuckDB with zone: CAST(x AS TIMESTAMPTZ)
30692                                    Ok(Expression::Cast(Box::new(Cast {
30693                                        this,
30694                                        to: DataType::Timestamp {
30695                                            timezone: true,
30696                                            precision: None,
30697                                        },
30698                                        double_colon_syntax: false,
30699                                        trailing_comments: Vec::new(),
30700                                        format: None,
30701                                        default: None,
30702                                        inferred_type: None,
30703                                    })))
30704                                } else {
30705                                    // DuckDB: CAST(x AS TIMESTAMP)
30706                                    Ok(Expression::Cast(Box::new(Cast {
30707                                        this,
30708                                        to: DataType::Timestamp {
30709                                            timezone: false,
30710                                            precision: None,
30711                                        },
30712                                        double_colon_syntax: false,
30713                                        trailing_comments: Vec::new(),
30714                                        format: None,
30715                                        default: None,
30716                                        inferred_type: None,
30717                                    })))
30718                                }
30719                            }
30720                            DialectType::PostgreSQL
30721                            | DialectType::Materialize
30722                            | DialectType::RisingWave => {
30723                                if has_zone {
30724                                    // PostgreSQL with zone: CAST(x AS TIMESTAMPTZ)
30725                                    Ok(Expression::Cast(Box::new(Cast {
30726                                        this,
30727                                        to: DataType::Timestamp {
30728                                            timezone: true,
30729                                            precision: None,
30730                                        },
30731                                        double_colon_syntax: false,
30732                                        trailing_comments: Vec::new(),
30733                                        format: None,
30734                                        default: None,
30735                                        inferred_type: None,
30736                                    })))
30737                                } else {
30738                                    // PostgreSQL: CAST(x AS TIMESTAMP)
30739                                    Ok(Expression::Cast(Box::new(Cast {
30740                                        this,
30741                                        to: DataType::Timestamp {
30742                                            timezone: false,
30743                                            precision: None,
30744                                        },
30745                                        double_colon_syntax: false,
30746                                        trailing_comments: Vec::new(),
30747                                        format: None,
30748                                        default: None,
30749                                        inferred_type: None,
30750                                    })))
30751                                }
30752                            }
30753                            DialectType::Snowflake => {
30754                                if has_zone {
30755                                    // Snowflake with zone: CAST(x AS TIMESTAMPTZ)
30756                                    Ok(Expression::Cast(Box::new(Cast {
30757                                        this,
30758                                        to: DataType::Timestamp {
30759                                            timezone: true,
30760                                            precision: None,
30761                                        },
30762                                        double_colon_syntax: false,
30763                                        trailing_comments: Vec::new(),
30764                                        format: None,
30765                                        default: None,
30766                                        inferred_type: None,
30767                                    })))
30768                                } else {
30769                                    // Snowflake: CAST(x AS TIMESTAMP)
30770                                    Ok(Expression::Cast(Box::new(Cast {
30771                                        this,
30772                                        to: DataType::Timestamp {
30773                                            timezone: false,
30774                                            precision: None,
30775                                        },
30776                                        double_colon_syntax: false,
30777                                        trailing_comments: Vec::new(),
30778                                        format: None,
30779                                        default: None,
30780                                        inferred_type: None,
30781                                    })))
30782                                }
30783                            }
30784                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30785                                if has_zone {
30786                                    // Presto/Trino with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
30787                                    // Check for precision from sub-second digits
30788                                    let precision = if let Expression::Literal(ref lit) = this {
30789                                        if let Literal::String(ref s) = lit.as_ref() {
30790                                            if let Some(dot_pos) = s.rfind('.') {
30791                                                let frac = &s[dot_pos + 1..];
30792                                                let digit_count = frac
30793                                                    .chars()
30794                                                    .take_while(|c| c.is_ascii_digit())
30795                                                    .count();
30796                                                if digit_count > 0
30797                                                    && matches!(target, DialectType::Trino)
30798                                                {
30799                                                    Some(digit_count as u32)
30800                                                } else {
30801                                                    None
30802                                                }
30803                                            } else {
30804                                                None
30805                                            }
30806                                        } else {
30807                                            None
30808                                        }
30809                                    } else {
30810                                        None
30811                                    };
30812                                    let dt = if let Some(prec) = precision {
30813                                        DataType::Timestamp {
30814                                            timezone: true,
30815                                            precision: Some(prec),
30816                                        }
30817                                    } else {
30818                                        DataType::Timestamp {
30819                                            timezone: true,
30820                                            precision: None,
30821                                        }
30822                                    };
30823                                    Ok(Expression::Cast(Box::new(Cast {
30824                                        this,
30825                                        to: dt,
30826                                        double_colon_syntax: false,
30827                                        trailing_comments: Vec::new(),
30828                                        format: None,
30829                                        default: None,
30830                                        inferred_type: None,
30831                                    })))
30832                                } else {
30833                                    // Check for sub-second precision for Trino
30834                                    let precision = if let Expression::Literal(ref lit) = this {
30835                                        if let Literal::String(ref s) = lit.as_ref() {
30836                                            if let Some(dot_pos) = s.rfind('.') {
30837                                                let frac = &s[dot_pos + 1..];
30838                                                let digit_count = frac
30839                                                    .chars()
30840                                                    .take_while(|c| c.is_ascii_digit())
30841                                                    .count();
30842                                                if digit_count > 0
30843                                                    && matches!(target, DialectType::Trino)
30844                                                {
30845                                                    Some(digit_count as u32)
30846                                                } else {
30847                                                    None
30848                                                }
30849                                            } else {
30850                                                None
30851                                            }
30852                                        } else {
30853                                            None
30854                                        }
30855                                    } else {
30856                                        None
30857                                    };
30858                                    let dt = DataType::Timestamp {
30859                                        timezone: false,
30860                                        precision,
30861                                    };
30862                                    Ok(Expression::Cast(Box::new(Cast {
30863                                        this,
30864                                        to: dt,
30865                                        double_colon_syntax: false,
30866                                        trailing_comments: Vec::new(),
30867                                        format: None,
30868                                        default: None,
30869                                        inferred_type: None,
30870                                    })))
30871                                }
30872                            }
30873                            DialectType::Redshift => {
30874                                if has_zone {
30875                                    // Redshift with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
30876                                    Ok(Expression::Cast(Box::new(Cast {
30877                                        this,
30878                                        to: DataType::Timestamp {
30879                                            timezone: true,
30880                                            precision: None,
30881                                        },
30882                                        double_colon_syntax: false,
30883                                        trailing_comments: Vec::new(),
30884                                        format: None,
30885                                        default: None,
30886                                        inferred_type: None,
30887                                    })))
30888                                } else {
30889                                    // Redshift: CAST(x AS TIMESTAMP)
30890                                    Ok(Expression::Cast(Box::new(Cast {
30891                                        this,
30892                                        to: DataType::Timestamp {
30893                                            timezone: false,
30894                                            precision: None,
30895                                        },
30896                                        double_colon_syntax: false,
30897                                        trailing_comments: Vec::new(),
30898                                        format: None,
30899                                        default: None,
30900                                        inferred_type: None,
30901                                    })))
30902                                }
30903                            }
30904                            _ => {
30905                                // Default: CAST(x AS TIMESTAMP)
30906                                Ok(Expression::Cast(Box::new(Cast {
30907                                    this,
30908                                    to: DataType::Timestamp {
30909                                        timezone: false,
30910                                        precision: None,
30911                                    },
30912                                    double_colon_syntax: false,
30913                                    trailing_comments: Vec::new(),
30914                                    format: None,
30915                                    default: None,
30916                                    inferred_type: None,
30917                                })))
30918                            }
30919                        }
30920                    } else {
30921                        Ok(e)
30922                    }
30923                }
30924
30925                Action::DateToDateStrConvert => {
30926                    // DATE_TO_DATE_STR(x) -> CAST(x AS text_type) per dialect
30927                    if let Expression::Function(f) = e {
30928                        let arg = f.args.into_iter().next().unwrap();
30929                        let str_type = match target {
30930                            DialectType::DuckDB => DataType::Text,
30931                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
30932                                DataType::Custom {
30933                                    name: "STRING".to_string(),
30934                                }
30935                            }
30936                            DialectType::Presto
30937                            | DialectType::Trino
30938                            | DialectType::Athena
30939                            | DialectType::Drill => DataType::VarChar {
30940                                length: None,
30941                                parenthesized_length: false,
30942                            },
30943                            _ => DataType::VarChar {
30944                                length: None,
30945                                parenthesized_length: false,
30946                            },
30947                        };
30948                        Ok(Expression::Cast(Box::new(Cast {
30949                            this: arg,
30950                            to: str_type,
30951                            double_colon_syntax: false,
30952                            trailing_comments: Vec::new(),
30953                            format: None,
30954                            default: None,
30955                            inferred_type: None,
30956                        })))
30957                    } else {
30958                        Ok(e)
30959                    }
30960                }
30961
30962                Action::DateToDiConvert => {
30963                    // DATE_TO_DI(x) -> CAST(format_func(x, fmt) AS INT)
30964                    if let Expression::Function(f) = e {
30965                        let arg = f.args.into_iter().next().unwrap();
30966                        let inner = match target {
30967                            DialectType::DuckDB => {
30968                                // STRFTIME(x, '%Y%m%d')
30969                                Expression::Function(Box::new(Function::new(
30970                                    "STRFTIME".to_string(),
30971                                    vec![arg, Expression::string("%Y%m%d")],
30972                                )))
30973                            }
30974                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
30975                                // DATE_FORMAT(x, 'yyyyMMdd')
30976                                Expression::Function(Box::new(Function::new(
30977                                    "DATE_FORMAT".to_string(),
30978                                    vec![arg, Expression::string("yyyyMMdd")],
30979                                )))
30980                            }
30981                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30982                                // DATE_FORMAT(x, '%Y%m%d')
30983                                Expression::Function(Box::new(Function::new(
30984                                    "DATE_FORMAT".to_string(),
30985                                    vec![arg, Expression::string("%Y%m%d")],
30986                                )))
30987                            }
30988                            DialectType::Drill => {
30989                                // TO_DATE(x, 'yyyyMMdd')
30990                                Expression::Function(Box::new(Function::new(
30991                                    "TO_DATE".to_string(),
30992                                    vec![arg, Expression::string("yyyyMMdd")],
30993                                )))
30994                            }
30995                            _ => {
30996                                // Default: STRFTIME(x, '%Y%m%d')
30997                                Expression::Function(Box::new(Function::new(
30998                                    "STRFTIME".to_string(),
30999                                    vec![arg, Expression::string("%Y%m%d")],
31000                                )))
31001                            }
31002                        };
31003                        // Use INT (not INTEGER) for Presto/Trino
31004                        let int_type = match target {
31005                            DialectType::Presto
31006                            | DialectType::Trino
31007                            | DialectType::Athena
31008                            | DialectType::TSQL
31009                            | DialectType::Fabric
31010                            | DialectType::SQLite
31011                            | DialectType::Redshift => DataType::Custom {
31012                                name: "INT".to_string(),
31013                            },
31014                            _ => DataType::Int {
31015                                length: None,
31016                                integer_spelling: false,
31017                            },
31018                        };
31019                        Ok(Expression::Cast(Box::new(Cast {
31020                            this: inner,
31021                            to: int_type,
31022                            double_colon_syntax: false,
31023                            trailing_comments: Vec::new(),
31024                            format: None,
31025                            default: None,
31026                            inferred_type: None,
31027                        })))
31028                    } else {
31029                        Ok(e)
31030                    }
31031                }
31032
31033                Action::DiToDateConvert => {
31034                    // DI_TO_DATE(x) -> dialect-specific integer-to-date conversion
31035                    if let Expression::Function(f) = e {
31036                        let arg = f.args.into_iter().next().unwrap();
31037                        match target {
31038                            DialectType::DuckDB => {
31039                                // CAST(STRPTIME(CAST(x AS TEXT), '%Y%m%d') AS DATE)
31040                                let cast_text = Expression::Cast(Box::new(Cast {
31041                                    this: arg,
31042                                    to: DataType::Text,
31043                                    double_colon_syntax: false,
31044                                    trailing_comments: Vec::new(),
31045                                    format: None,
31046                                    default: None,
31047                                    inferred_type: None,
31048                                }));
31049                                let strptime = Expression::Function(Box::new(Function::new(
31050                                    "STRPTIME".to_string(),
31051                                    vec![cast_text, Expression::string("%Y%m%d")],
31052                                )));
31053                                Ok(Expression::Cast(Box::new(Cast {
31054                                    this: strptime,
31055                                    to: DataType::Date,
31056                                    double_colon_syntax: false,
31057                                    trailing_comments: Vec::new(),
31058                                    format: None,
31059                                    default: None,
31060                                    inferred_type: None,
31061                                })))
31062                            }
31063                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
31064                                // TO_DATE(CAST(x AS STRING), 'yyyyMMdd')
31065                                let cast_str = Expression::Cast(Box::new(Cast {
31066                                    this: arg,
31067                                    to: DataType::Custom {
31068                                        name: "STRING".to_string(),
31069                                    },
31070                                    double_colon_syntax: false,
31071                                    trailing_comments: Vec::new(),
31072                                    format: None,
31073                                    default: None,
31074                                    inferred_type: None,
31075                                }));
31076                                Ok(Expression::Function(Box::new(Function::new(
31077                                    "TO_DATE".to_string(),
31078                                    vec![cast_str, Expression::string("yyyyMMdd")],
31079                                ))))
31080                            }
31081                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31082                                // CAST(DATE_PARSE(CAST(x AS VARCHAR), '%Y%m%d') AS DATE)
31083                                let cast_varchar = Expression::Cast(Box::new(Cast {
31084                                    this: arg,
31085                                    to: DataType::VarChar {
31086                                        length: None,
31087                                        parenthesized_length: false,
31088                                    },
31089                                    double_colon_syntax: false,
31090                                    trailing_comments: Vec::new(),
31091                                    format: None,
31092                                    default: None,
31093                                    inferred_type: None,
31094                                }));
31095                                let date_parse = Expression::Function(Box::new(Function::new(
31096                                    "DATE_PARSE".to_string(),
31097                                    vec![cast_varchar, Expression::string("%Y%m%d")],
31098                                )));
31099                                Ok(Expression::Cast(Box::new(Cast {
31100                                    this: date_parse,
31101                                    to: DataType::Date,
31102                                    double_colon_syntax: false,
31103                                    trailing_comments: Vec::new(),
31104                                    format: None,
31105                                    default: None,
31106                                    inferred_type: None,
31107                                })))
31108                            }
31109                            DialectType::Drill => {
31110                                // TO_DATE(CAST(x AS VARCHAR), 'yyyyMMdd')
31111                                let cast_varchar = Expression::Cast(Box::new(Cast {
31112                                    this: arg,
31113                                    to: DataType::VarChar {
31114                                        length: None,
31115                                        parenthesized_length: false,
31116                                    },
31117                                    double_colon_syntax: false,
31118                                    trailing_comments: Vec::new(),
31119                                    format: None,
31120                                    default: None,
31121                                    inferred_type: None,
31122                                }));
31123                                Ok(Expression::Function(Box::new(Function::new(
31124                                    "TO_DATE".to_string(),
31125                                    vec![cast_varchar, Expression::string("yyyyMMdd")],
31126                                ))))
31127                            }
31128                            _ => Ok(Expression::Function(Box::new(Function::new(
31129                                "DI_TO_DATE".to_string(),
31130                                vec![arg],
31131                            )))),
31132                        }
31133                    } else {
31134                        Ok(e)
31135                    }
31136                }
31137
31138                Action::TsOrDiToDiConvert => {
31139                    // TS_OR_DI_TO_DI(x) -> CAST(SUBSTR(REPLACE(CAST(x AS type), '-', ''), 1, 8) AS INT)
31140                    if let Expression::Function(f) = e {
31141                        let arg = f.args.into_iter().next().unwrap();
31142                        let str_type = match target {
31143                            DialectType::DuckDB => DataType::Text,
31144                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
31145                                DataType::Custom {
31146                                    name: "STRING".to_string(),
31147                                }
31148                            }
31149                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31150                                DataType::VarChar {
31151                                    length: None,
31152                                    parenthesized_length: false,
31153                                }
31154                            }
31155                            _ => DataType::VarChar {
31156                                length: None,
31157                                parenthesized_length: false,
31158                            },
31159                        };
31160                        let cast_str = Expression::Cast(Box::new(Cast {
31161                            this: arg,
31162                            to: str_type,
31163                            double_colon_syntax: false,
31164                            trailing_comments: Vec::new(),
31165                            format: None,
31166                            default: None,
31167                            inferred_type: None,
31168                        }));
31169                        let replace_expr = Expression::Function(Box::new(Function::new(
31170                            "REPLACE".to_string(),
31171                            vec![cast_str, Expression::string("-"), Expression::string("")],
31172                        )));
31173                        let substr_name = match target {
31174                            DialectType::DuckDB
31175                            | DialectType::Hive
31176                            | DialectType::Spark
31177                            | DialectType::Databricks => "SUBSTR",
31178                            _ => "SUBSTR",
31179                        };
31180                        let substr = Expression::Function(Box::new(Function::new(
31181                            substr_name.to_string(),
31182                            vec![replace_expr, Expression::number(1), Expression::number(8)],
31183                        )));
31184                        // Use INT (not INTEGER) for Presto/Trino etc.
31185                        let int_type = match target {
31186                            DialectType::Presto
31187                            | DialectType::Trino
31188                            | DialectType::Athena
31189                            | DialectType::TSQL
31190                            | DialectType::Fabric
31191                            | DialectType::SQLite
31192                            | DialectType::Redshift => DataType::Custom {
31193                                name: "INT".to_string(),
31194                            },
31195                            _ => DataType::Int {
31196                                length: None,
31197                                integer_spelling: false,
31198                            },
31199                        };
31200                        Ok(Expression::Cast(Box::new(Cast {
31201                            this: substr,
31202                            to: int_type,
31203                            double_colon_syntax: false,
31204                            trailing_comments: Vec::new(),
31205                            format: None,
31206                            default: None,
31207                            inferred_type: None,
31208                        })))
31209                    } else {
31210                        Ok(e)
31211                    }
31212                }
31213
31214                Action::UnixToStrConvert => {
31215                    // UNIX_TO_STR(x, fmt) -> convert to Expression::UnixToStr for generator
31216                    if let Expression::Function(f) = e {
31217                        let mut args = f.args;
31218                        let this = args.remove(0);
31219                        let fmt_expr = if !args.is_empty() {
31220                            Some(args.remove(0))
31221                        } else {
31222                            None
31223                        };
31224
31225                        // Check if format is a string literal
31226                        let fmt_str = fmt_expr.as_ref().and_then(|f| {
31227                            if let Expression::Literal(lit) = f {
31228                                if let Literal::String(s) = lit.as_ref() {
31229                                    Some(s.clone())
31230                                } else {
31231                                    None
31232                                }
31233                            } else {
31234                                None
31235                            }
31236                        });
31237
31238                        if let Some(fmt_string) = fmt_str {
31239                            // String literal format -> use UnixToStr expression (generator handles it)
31240                            Ok(Expression::UnixToStr(Box::new(
31241                                crate::expressions::UnixToStr {
31242                                    this: Box::new(this),
31243                                    format: Some(fmt_string),
31244                                },
31245                            )))
31246                        } else if let Some(fmt_e) = fmt_expr {
31247                            // Non-literal format (e.g., identifier `y`) -> build target expression directly
31248                            match target {
31249                                DialectType::DuckDB => {
31250                                    // STRFTIME(TO_TIMESTAMP(x), y)
31251                                    let to_ts = Expression::Function(Box::new(Function::new(
31252                                        "TO_TIMESTAMP".to_string(),
31253                                        vec![this],
31254                                    )));
31255                                    Ok(Expression::Function(Box::new(Function::new(
31256                                        "STRFTIME".to_string(),
31257                                        vec![to_ts, fmt_e],
31258                                    ))))
31259                                }
31260                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31261                                    // DATE_FORMAT(FROM_UNIXTIME(x), y)
31262                                    let from_unix = Expression::Function(Box::new(Function::new(
31263                                        "FROM_UNIXTIME".to_string(),
31264                                        vec![this],
31265                                    )));
31266                                    Ok(Expression::Function(Box::new(Function::new(
31267                                        "DATE_FORMAT".to_string(),
31268                                        vec![from_unix, fmt_e],
31269                                    ))))
31270                                }
31271                                DialectType::Hive
31272                                | DialectType::Spark
31273                                | DialectType::Databricks
31274                                | DialectType::Doris
31275                                | DialectType::StarRocks => {
31276                                    // FROM_UNIXTIME(x, y)
31277                                    Ok(Expression::Function(Box::new(Function::new(
31278                                        "FROM_UNIXTIME".to_string(),
31279                                        vec![this, fmt_e],
31280                                    ))))
31281                                }
31282                                _ => {
31283                                    // Default: keep as UNIX_TO_STR(x, y)
31284                                    Ok(Expression::Function(Box::new(Function::new(
31285                                        "UNIX_TO_STR".to_string(),
31286                                        vec![this, fmt_e],
31287                                    ))))
31288                                }
31289                            }
31290                        } else {
31291                            Ok(Expression::UnixToStr(Box::new(
31292                                crate::expressions::UnixToStr {
31293                                    this: Box::new(this),
31294                                    format: None,
31295                                },
31296                            )))
31297                        }
31298                    } else {
31299                        Ok(e)
31300                    }
31301                }
31302
31303                Action::UnixToTimeConvert => {
31304                    // UNIX_TO_TIME(x) -> convert to Expression::UnixToTime for generator
31305                    if let Expression::Function(f) = e {
31306                        let arg = f.args.into_iter().next().unwrap();
31307                        Ok(Expression::UnixToTime(Box::new(
31308                            crate::expressions::UnixToTime {
31309                                this: Box::new(arg),
31310                                scale: None,
31311                                zone: None,
31312                                hours: None,
31313                                minutes: None,
31314                                format: None,
31315                                target_type: None,
31316                            },
31317                        )))
31318                    } else {
31319                        Ok(e)
31320                    }
31321                }
31322
31323                Action::UnixToTimeStrConvert => {
31324                    // UNIX_TO_TIME_STR(x) -> dialect-specific
31325                    if let Expression::Function(f) = e {
31326                        let arg = f.args.into_iter().next().unwrap();
31327                        match target {
31328                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
31329                                // FROM_UNIXTIME(x)
31330                                Ok(Expression::Function(Box::new(Function::new(
31331                                    "FROM_UNIXTIME".to_string(),
31332                                    vec![arg],
31333                                ))))
31334                            }
31335                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31336                                // CAST(FROM_UNIXTIME(x) AS VARCHAR)
31337                                let from_unix = Expression::Function(Box::new(Function::new(
31338                                    "FROM_UNIXTIME".to_string(),
31339                                    vec![arg],
31340                                )));
31341                                Ok(Expression::Cast(Box::new(Cast {
31342                                    this: from_unix,
31343                                    to: DataType::VarChar {
31344                                        length: None,
31345                                        parenthesized_length: false,
31346                                    },
31347                                    double_colon_syntax: false,
31348                                    trailing_comments: Vec::new(),
31349                                    format: None,
31350                                    default: None,
31351                                    inferred_type: None,
31352                                })))
31353                            }
31354                            DialectType::DuckDB => {
31355                                // CAST(TO_TIMESTAMP(x) AS TEXT)
31356                                let to_ts = Expression::Function(Box::new(Function::new(
31357                                    "TO_TIMESTAMP".to_string(),
31358                                    vec![arg],
31359                                )));
31360                                Ok(Expression::Cast(Box::new(Cast {
31361                                    this: to_ts,
31362                                    to: DataType::Text,
31363                                    double_colon_syntax: false,
31364                                    trailing_comments: Vec::new(),
31365                                    format: None,
31366                                    default: None,
31367                                    inferred_type: None,
31368                                })))
31369                            }
31370                            _ => Ok(Expression::Function(Box::new(Function::new(
31371                                "UNIX_TO_TIME_STR".to_string(),
31372                                vec![arg],
31373                            )))),
31374                        }
31375                    } else {
31376                        Ok(e)
31377                    }
31378                }
31379
31380                Action::TimeToUnixConvert => {
31381                    // TIME_TO_UNIX(x) -> convert to Expression::TimeToUnix for generator
31382                    if let Expression::Function(f) = e {
31383                        let arg = f.args.into_iter().next().unwrap();
31384                        Ok(Expression::TimeToUnix(Box::new(
31385                            crate::expressions::UnaryFunc {
31386                                this: arg,
31387                                original_name: None,
31388                                inferred_type: None,
31389                            },
31390                        )))
31391                    } else {
31392                        Ok(e)
31393                    }
31394                }
31395
31396                Action::TimeToStrConvert => {
31397                    // TIME_TO_STR(x, fmt) -> convert to Expression::TimeToStr for generator
31398                    if let Expression::Function(f) = e {
31399                        let mut args = f.args;
31400                        let this = args.remove(0);
31401                        let fmt = match args.remove(0) {
31402                            Expression::Literal(lit)
31403                                if matches!(lit.as_ref(), Literal::String(_)) =>
31404                            {
31405                                let Literal::String(s) = lit.as_ref() else {
31406                                    unreachable!()
31407                                };
31408                                s.clone()
31409                            }
31410                            other => {
31411                                return Ok(Expression::Function(Box::new(Function::new(
31412                                    "TIME_TO_STR".to_string(),
31413                                    vec![this, other],
31414                                ))));
31415                            }
31416                        };
31417                        Ok(Expression::TimeToStr(Box::new(
31418                            crate::expressions::TimeToStr {
31419                                this: Box::new(this),
31420                                format: fmt,
31421                                culture: None,
31422                                zone: None,
31423                            },
31424                        )))
31425                    } else {
31426                        Ok(e)
31427                    }
31428                }
31429
31430                Action::StrToUnixConvert => {
31431                    // STR_TO_UNIX(x, fmt) -> convert to Expression::StrToUnix for generator
31432                    if let Expression::Function(f) = e {
31433                        let mut args = f.args;
31434                        let this = args.remove(0);
31435                        let fmt = match args.remove(0) {
31436                            Expression::Literal(lit)
31437                                if matches!(lit.as_ref(), Literal::String(_)) =>
31438                            {
31439                                let Literal::String(s) = lit.as_ref() else {
31440                                    unreachable!()
31441                                };
31442                                s.clone()
31443                            }
31444                            other => {
31445                                return Ok(Expression::Function(Box::new(Function::new(
31446                                    "STR_TO_UNIX".to_string(),
31447                                    vec![this, other],
31448                                ))));
31449                            }
31450                        };
31451                        Ok(Expression::StrToUnix(Box::new(
31452                            crate::expressions::StrToUnix {
31453                                this: Some(Box::new(this)),
31454                                format: Some(fmt),
31455                            },
31456                        )))
31457                    } else {
31458                        Ok(e)
31459                    }
31460                }
31461
31462                Action::TimeStrToUnixConvert => {
31463                    // TIME_STR_TO_UNIX(x) -> dialect-specific
31464                    if let Expression::Function(f) = e {
31465                        let arg = f.args.into_iter().next().unwrap();
31466                        match target {
31467                            DialectType::DuckDB => {
31468                                // EPOCH(CAST(x AS TIMESTAMP))
31469                                let cast_ts = Expression::Cast(Box::new(Cast {
31470                                    this: arg,
31471                                    to: DataType::Timestamp {
31472                                        timezone: false,
31473                                        precision: None,
31474                                    },
31475                                    double_colon_syntax: false,
31476                                    trailing_comments: Vec::new(),
31477                                    format: None,
31478                                    default: None,
31479                                    inferred_type: None,
31480                                }));
31481                                Ok(Expression::Function(Box::new(Function::new(
31482                                    "EPOCH".to_string(),
31483                                    vec![cast_ts],
31484                                ))))
31485                            }
31486                            DialectType::Hive
31487                            | DialectType::Doris
31488                            | DialectType::StarRocks
31489                            | DialectType::MySQL => {
31490                                // UNIX_TIMESTAMP(x)
31491                                Ok(Expression::Function(Box::new(Function::new(
31492                                    "UNIX_TIMESTAMP".to_string(),
31493                                    vec![arg],
31494                                ))))
31495                            }
31496                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31497                                // TO_UNIXTIME(DATE_PARSE(x, '%Y-%m-%d %T'))
31498                                let date_parse = Expression::Function(Box::new(Function::new(
31499                                    "DATE_PARSE".to_string(),
31500                                    vec![arg, Expression::string("%Y-%m-%d %T")],
31501                                )));
31502                                Ok(Expression::Function(Box::new(Function::new(
31503                                    "TO_UNIXTIME".to_string(),
31504                                    vec![date_parse],
31505                                ))))
31506                            }
31507                            _ => Ok(Expression::Function(Box::new(Function::new(
31508                                "TIME_STR_TO_UNIX".to_string(),
31509                                vec![arg],
31510                            )))),
31511                        }
31512                    } else {
31513                        Ok(e)
31514                    }
31515                }
31516
31517                Action::TimeToTimeStrConvert => {
31518                    // TIME_TO_TIME_STR(x) -> CAST(x AS str_type) per dialect
31519                    if let Expression::Function(f) = e {
31520                        let arg = f.args.into_iter().next().unwrap();
31521                        let str_type = match target {
31522                            DialectType::DuckDB => DataType::Text,
31523                            DialectType::Hive
31524                            | DialectType::Spark
31525                            | DialectType::Databricks
31526                            | DialectType::Doris
31527                            | DialectType::StarRocks => DataType::Custom {
31528                                name: "STRING".to_string(),
31529                            },
31530                            DialectType::Redshift => DataType::Custom {
31531                                name: "VARCHAR(MAX)".to_string(),
31532                            },
31533                            _ => DataType::VarChar {
31534                                length: None,
31535                                parenthesized_length: false,
31536                            },
31537                        };
31538                        Ok(Expression::Cast(Box::new(Cast {
31539                            this: arg,
31540                            to: str_type,
31541                            double_colon_syntax: false,
31542                            trailing_comments: Vec::new(),
31543                            format: None,
31544                            default: None,
31545                            inferred_type: None,
31546                        })))
31547                    } else {
31548                        Ok(e)
31549                    }
31550                }
31551
31552                Action::DateTruncSwapArgs => {
31553                    // DATE_TRUNC('unit', x) from Generic -> target-specific
31554                    if let Expression::Function(f) = e {
31555                        if f.args.len() == 2 {
31556                            let unit_arg = f.args[0].clone();
31557                            let expr_arg = f.args[1].clone();
31558                            // Extract unit string from the first arg
31559                            let unit_str = match &unit_arg {
31560                                Expression::Literal(lit)
31561                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31562                                {
31563                                    let Literal::String(s) = lit.as_ref() else {
31564                                        unreachable!()
31565                                    };
31566                                    s.to_ascii_uppercase()
31567                                }
31568                                _ => return Ok(Expression::Function(f)),
31569                            };
31570                            match target {
31571                                DialectType::BigQuery => {
31572                                    // BigQuery: DATE_TRUNC(x, UNIT) - unquoted unit
31573                                    let unit_ident =
31574                                        Expression::Column(Box::new(crate::expressions::Column {
31575                                            name: crate::expressions::Identifier::new(unit_str),
31576                                            table: None,
31577                                            join_mark: false,
31578                                            trailing_comments: Vec::new(),
31579                                            span: None,
31580                                            inferred_type: None,
31581                                        }));
31582                                    Ok(Expression::Function(Box::new(Function::new(
31583                                        "DATE_TRUNC".to_string(),
31584                                        vec![expr_arg, unit_ident],
31585                                    ))))
31586                                }
31587                                DialectType::Doris => {
31588                                    // Doris: DATE_TRUNC(x, 'UNIT')
31589                                    Ok(Expression::Function(Box::new(Function::new(
31590                                        "DATE_TRUNC".to_string(),
31591                                        vec![expr_arg, Expression::string(&unit_str)],
31592                                    ))))
31593                                }
31594                                DialectType::StarRocks => {
31595                                    // StarRocks: DATE_TRUNC('UNIT', x) - keep standard order
31596                                    Ok(Expression::Function(Box::new(Function::new(
31597                                        "DATE_TRUNC".to_string(),
31598                                        vec![Expression::string(&unit_str), expr_arg],
31599                                    ))))
31600                                }
31601                                DialectType::Spark | DialectType::Databricks => {
31602                                    // Spark: TRUNC(x, 'UNIT')
31603                                    Ok(Expression::Function(Box::new(Function::new(
31604                                        "TRUNC".to_string(),
31605                                        vec![expr_arg, Expression::string(&unit_str)],
31606                                    ))))
31607                                }
31608                                DialectType::MySQL => {
31609                                    // MySQL: complex expansion based on unit
31610                                    Self::date_trunc_to_mysql(&unit_str, &expr_arg)
31611                                }
31612                                _ => Ok(Expression::Function(f)),
31613                            }
31614                        } else {
31615                            Ok(Expression::Function(f))
31616                        }
31617                    } else {
31618                        Ok(e)
31619                    }
31620                }
31621
31622                Action::TimestampTruncConvert => {
31623                    // TIMESTAMP_TRUNC(x, UNIT[, tz]) from Generic -> target-specific
31624                    if let Expression::Function(f) = e {
31625                        if f.args.len() >= 2 {
31626                            let expr_arg = f.args[0].clone();
31627                            let unit_arg = f.args[1].clone();
31628                            let tz_arg = if f.args.len() >= 3 {
31629                                Some(f.args[2].clone())
31630                            } else {
31631                                None
31632                            };
31633                            // Extract unit string
31634                            let unit_str = match &unit_arg {
31635                                Expression::Literal(lit)
31636                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31637                                {
31638                                    let Literal::String(s) = lit.as_ref() else {
31639                                        unreachable!()
31640                                    };
31641                                    s.to_ascii_uppercase()
31642                                }
31643                                Expression::Column(c) => c.name.name.to_ascii_uppercase(),
31644                                _ => {
31645                                    return Ok(Expression::Function(f));
31646                                }
31647                            };
31648                            match target {
31649                                DialectType::Spark | DialectType::Databricks => {
31650                                    // Spark: DATE_TRUNC('UNIT', x)
31651                                    Ok(Expression::Function(Box::new(Function::new(
31652                                        "DATE_TRUNC".to_string(),
31653                                        vec![Expression::string(&unit_str), expr_arg],
31654                                    ))))
31655                                }
31656                                DialectType::Doris | DialectType::StarRocks => {
31657                                    // Doris: DATE_TRUNC(x, 'UNIT')
31658                                    Ok(Expression::Function(Box::new(Function::new(
31659                                        "DATE_TRUNC".to_string(),
31660                                        vec![expr_arg, Expression::string(&unit_str)],
31661                                    ))))
31662                                }
31663                                DialectType::BigQuery => {
31664                                    // BigQuery: TIMESTAMP_TRUNC(x, UNIT) - keep but with unquoted unit
31665                                    let unit_ident =
31666                                        Expression::Column(Box::new(crate::expressions::Column {
31667                                            name: crate::expressions::Identifier::new(unit_str),
31668                                            table: None,
31669                                            join_mark: false,
31670                                            trailing_comments: Vec::new(),
31671                                            span: None,
31672                                            inferred_type: None,
31673                                        }));
31674                                    let mut args = vec![expr_arg, unit_ident];
31675                                    if let Some(tz) = tz_arg {
31676                                        args.push(tz);
31677                                    }
31678                                    Ok(Expression::Function(Box::new(Function::new(
31679                                        "TIMESTAMP_TRUNC".to_string(),
31680                                        args,
31681                                    ))))
31682                                }
31683                                DialectType::DuckDB => {
31684                                    // DuckDB with timezone: DATE_TRUNC('UNIT', x AT TIME ZONE 'tz') AT TIME ZONE 'tz'
31685                                    if let Some(tz) = tz_arg {
31686                                        let tz_str = match &tz {
31687                                            Expression::Literal(lit)
31688                                                if matches!(lit.as_ref(), Literal::String(_)) =>
31689                                            {
31690                                                let Literal::String(s) = lit.as_ref() else {
31691                                                    unreachable!()
31692                                                };
31693                                                s.clone()
31694                                            }
31695                                            _ => "UTC".to_string(),
31696                                        };
31697                                        // x AT TIME ZONE 'tz'
31698                                        let at_tz = Expression::AtTimeZone(Box::new(
31699                                            crate::expressions::AtTimeZone {
31700                                                this: expr_arg,
31701                                                zone: Expression::string(&tz_str),
31702                                            },
31703                                        ));
31704                                        // DATE_TRUNC('UNIT', x AT TIME ZONE 'tz')
31705                                        let trunc = Expression::Function(Box::new(Function::new(
31706                                            "DATE_TRUNC".to_string(),
31707                                            vec![Expression::string(&unit_str), at_tz],
31708                                        )));
31709                                        // DATE_TRUNC(...) AT TIME ZONE 'tz'
31710                                        Ok(Expression::AtTimeZone(Box::new(
31711                                            crate::expressions::AtTimeZone {
31712                                                this: trunc,
31713                                                zone: Expression::string(&tz_str),
31714                                            },
31715                                        )))
31716                                    } else {
31717                                        Ok(Expression::Function(Box::new(Function::new(
31718                                            "DATE_TRUNC".to_string(),
31719                                            vec![Expression::string(&unit_str), expr_arg],
31720                                        ))))
31721                                    }
31722                                }
31723                                DialectType::Presto
31724                                | DialectType::Trino
31725                                | DialectType::Athena
31726                                | DialectType::Snowflake => {
31727                                    // Presto/Snowflake: DATE_TRUNC('UNIT', x) - drop timezone
31728                                    Ok(Expression::Function(Box::new(Function::new(
31729                                        "DATE_TRUNC".to_string(),
31730                                        vec![Expression::string(&unit_str), expr_arg],
31731                                    ))))
31732                                }
31733                                _ => {
31734                                    // For most dialects: DATE_TRUNC('UNIT', x) + tz handling
31735                                    let mut args = vec![Expression::string(&unit_str), expr_arg];
31736                                    if let Some(tz) = tz_arg {
31737                                        args.push(tz);
31738                                    }
31739                                    Ok(Expression::Function(Box::new(Function::new(
31740                                        "DATE_TRUNC".to_string(),
31741                                        args,
31742                                    ))))
31743                                }
31744                            }
31745                        } else {
31746                            Ok(Expression::Function(f))
31747                        }
31748                    } else {
31749                        Ok(e)
31750                    }
31751                }
31752
31753                Action::StrToDateConvert => {
31754                    // STR_TO_DATE(x, fmt) from Generic -> dialect-specific date parsing
31755                    if let Expression::Function(f) = e {
31756                        if f.args.len() == 2 {
31757                            let mut args = f.args;
31758                            let this = args.remove(0);
31759                            let fmt_expr = args.remove(0);
31760                            let fmt_str = match &fmt_expr {
31761                                Expression::Literal(lit)
31762                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31763                                {
31764                                    let Literal::String(s) = lit.as_ref() else {
31765                                        unreachable!()
31766                                    };
31767                                    Some(s.clone())
31768                                }
31769                                _ => None,
31770                            };
31771                            let default_date = "%Y-%m-%d";
31772                            let default_time = "%Y-%m-%d %H:%M:%S";
31773                            let is_default = fmt_str
31774                                .as_ref()
31775                                .map_or(false, |f| f == default_date || f == default_time);
31776
31777                            if is_default {
31778                                // Default format: handle per-dialect
31779                                match target {
31780                                    DialectType::MySQL
31781                                    | DialectType::Doris
31782                                    | DialectType::StarRocks => {
31783                                        // Keep STR_TO_DATE(x, fmt) as-is
31784                                        Ok(Expression::Function(Box::new(Function::new(
31785                                            "STR_TO_DATE".to_string(),
31786                                            vec![this, fmt_expr],
31787                                        ))))
31788                                    }
31789                                    DialectType::Hive => {
31790                                        // Hive: CAST(x AS DATE)
31791                                        Ok(Expression::Cast(Box::new(Cast {
31792                                            this,
31793                                            to: DataType::Date,
31794                                            double_colon_syntax: false,
31795                                            trailing_comments: Vec::new(),
31796                                            format: None,
31797                                            default: None,
31798                                            inferred_type: None,
31799                                        })))
31800                                    }
31801                                    DialectType::Presto
31802                                    | DialectType::Trino
31803                                    | DialectType::Athena => {
31804                                        // Presto: CAST(DATE_PARSE(x, '%Y-%m-%d') AS DATE)
31805                                        let date_parse =
31806                                            Expression::Function(Box::new(Function::new(
31807                                                "DATE_PARSE".to_string(),
31808                                                vec![this, fmt_expr],
31809                                            )));
31810                                        Ok(Expression::Cast(Box::new(Cast {
31811                                            this: date_parse,
31812                                            to: DataType::Date,
31813                                            double_colon_syntax: false,
31814                                            trailing_comments: Vec::new(),
31815                                            format: None,
31816                                            default: None,
31817                                            inferred_type: None,
31818                                        })))
31819                                    }
31820                                    _ => {
31821                                        // Others: TsOrDsToDate (delegates to generator)
31822                                        Ok(Expression::TsOrDsToDate(Box::new(
31823                                            crate::expressions::TsOrDsToDate {
31824                                                this: Box::new(this),
31825                                                format: None,
31826                                                safe: None,
31827                                            },
31828                                        )))
31829                                    }
31830                                }
31831                            } else if let Some(fmt) = fmt_str {
31832                                match target {
31833                                    DialectType::Doris
31834                                    | DialectType::StarRocks
31835                                    | DialectType::MySQL => {
31836                                        // Keep STR_TO_DATE but with normalized format (%H:%M:%S -> %T, %-d -> %e)
31837                                        let mut normalized = fmt.clone();
31838                                        normalized = normalized.replace("%-d", "%e");
31839                                        normalized = normalized.replace("%-m", "%c");
31840                                        normalized = normalized.replace("%H:%M:%S", "%T");
31841                                        Ok(Expression::Function(Box::new(Function::new(
31842                                            "STR_TO_DATE".to_string(),
31843                                            vec![this, Expression::string(&normalized)],
31844                                        ))))
31845                                    }
31846                                    DialectType::Hive => {
31847                                        // Hive: CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS DATE)
31848                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
31849                                        let unix_ts =
31850                                            Expression::Function(Box::new(Function::new(
31851                                                "UNIX_TIMESTAMP".to_string(),
31852                                                vec![this, Expression::string(&java_fmt)],
31853                                            )));
31854                                        let from_unix =
31855                                            Expression::Function(Box::new(Function::new(
31856                                                "FROM_UNIXTIME".to_string(),
31857                                                vec![unix_ts],
31858                                            )));
31859                                        Ok(Expression::Cast(Box::new(Cast {
31860                                            this: from_unix,
31861                                            to: DataType::Date,
31862                                            double_colon_syntax: false,
31863                                            trailing_comments: Vec::new(),
31864                                            format: None,
31865                                            default: None,
31866                                            inferred_type: None,
31867                                        })))
31868                                    }
31869                                    DialectType::Spark | DialectType::Databricks => {
31870                                        // Spark: TO_DATE(x, java_fmt)
31871                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
31872                                        Ok(Expression::Function(Box::new(Function::new(
31873                                            "TO_DATE".to_string(),
31874                                            vec![this, Expression::string(&java_fmt)],
31875                                        ))))
31876                                    }
31877                                    DialectType::Drill => {
31878                                        // Drill: TO_DATE(x, java_fmt) with T quoted as 'T' in Java format
31879                                        // The generator's string literal escaping will double the quotes: 'T' -> ''T''
31880                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
31881                                        let java_fmt = java_fmt.replace('T', "'T'");
31882                                        Ok(Expression::Function(Box::new(Function::new(
31883                                            "TO_DATE".to_string(),
31884                                            vec![this, Expression::string(&java_fmt)],
31885                                        ))))
31886                                    }
31887                                    _ => {
31888                                        // For other dialects: use TsOrDsToDate which delegates to generator
31889                                        Ok(Expression::TsOrDsToDate(Box::new(
31890                                            crate::expressions::TsOrDsToDate {
31891                                                this: Box::new(this),
31892                                                format: Some(fmt),
31893                                                safe: None,
31894                                            },
31895                                        )))
31896                                    }
31897                                }
31898                            } else {
31899                                // Non-string format - keep as-is
31900                                let mut new_args = Vec::new();
31901                                new_args.push(this);
31902                                new_args.push(fmt_expr);
31903                                Ok(Expression::Function(Box::new(Function::new(
31904                                    "STR_TO_DATE".to_string(),
31905                                    new_args,
31906                                ))))
31907                            }
31908                        } else {
31909                            Ok(Expression::Function(f))
31910                        }
31911                    } else {
31912                        Ok(e)
31913                    }
31914                }
31915
31916                Action::TsOrDsAddConvert => {
31917                    // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
31918                    if let Expression::Function(f) = e {
31919                        if f.args.len() == 3 {
31920                            let mut args = f.args;
31921                            let x = args.remove(0);
31922                            let n = args.remove(0);
31923                            let unit_expr = args.remove(0);
31924                            let unit_str = match &unit_expr {
31925                                Expression::Literal(lit)
31926                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31927                                {
31928                                    let Literal::String(s) = lit.as_ref() else {
31929                                        unreachable!()
31930                                    };
31931                                    s.to_ascii_uppercase()
31932                                }
31933                                _ => "DAY".to_string(),
31934                            };
31935
31936                            match target {
31937                                DialectType::Hive
31938                                | DialectType::Spark
31939                                | DialectType::Databricks => {
31940                                    // DATE_ADD(x, n) - only supports DAY unit
31941                                    Ok(Expression::Function(Box::new(Function::new(
31942                                        "DATE_ADD".to_string(),
31943                                        vec![x, n],
31944                                    ))))
31945                                }
31946                                DialectType::MySQL => {
31947                                    // DATE_ADD(x, INTERVAL n UNIT)
31948                                    let iu = match unit_str.as_str() {
31949                                        "YEAR" => crate::expressions::IntervalUnit::Year,
31950                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
31951                                        "MONTH" => crate::expressions::IntervalUnit::Month,
31952                                        "WEEK" => crate::expressions::IntervalUnit::Week,
31953                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
31954                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
31955                                        "SECOND" => crate::expressions::IntervalUnit::Second,
31956                                        _ => crate::expressions::IntervalUnit::Day,
31957                                    };
31958                                    let interval = Expression::Interval(Box::new(
31959                                        crate::expressions::Interval {
31960                                            this: Some(n),
31961                                            unit: Some(
31962                                                crate::expressions::IntervalUnitSpec::Simple {
31963                                                    unit: iu,
31964                                                    use_plural: false,
31965                                                },
31966                                            ),
31967                                        },
31968                                    ));
31969                                    Ok(Expression::Function(Box::new(Function::new(
31970                                        "DATE_ADD".to_string(),
31971                                        vec![x, interval],
31972                                    ))))
31973                                }
31974                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31975                                    // DATE_ADD('UNIT', n, CAST(CAST(x AS TIMESTAMP) AS DATE))
31976                                    let cast_ts = Expression::Cast(Box::new(Cast {
31977                                        this: x,
31978                                        to: DataType::Timestamp {
31979                                            precision: None,
31980                                            timezone: false,
31981                                        },
31982                                        double_colon_syntax: false,
31983                                        trailing_comments: Vec::new(),
31984                                        format: None,
31985                                        default: None,
31986                                        inferred_type: None,
31987                                    }));
31988                                    let cast_date = Expression::Cast(Box::new(Cast {
31989                                        this: cast_ts,
31990                                        to: DataType::Date,
31991                                        double_colon_syntax: false,
31992                                        trailing_comments: Vec::new(),
31993                                        format: None,
31994                                        default: None,
31995                                        inferred_type: None,
31996                                    }));
31997                                    Ok(Expression::Function(Box::new(Function::new(
31998                                        "DATE_ADD".to_string(),
31999                                        vec![Expression::string(&unit_str), n, cast_date],
32000                                    ))))
32001                                }
32002                                DialectType::DuckDB => {
32003                                    // CAST(x AS DATE) + INTERVAL n UNIT
32004                                    let cast_date = Expression::Cast(Box::new(Cast {
32005                                        this: x,
32006                                        to: DataType::Date,
32007                                        double_colon_syntax: false,
32008                                        trailing_comments: Vec::new(),
32009                                        format: None,
32010                                        default: None,
32011                                        inferred_type: None,
32012                                    }));
32013                                    let iu = match unit_str.as_str() {
32014                                        "YEAR" => crate::expressions::IntervalUnit::Year,
32015                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
32016                                        "MONTH" => crate::expressions::IntervalUnit::Month,
32017                                        "WEEK" => crate::expressions::IntervalUnit::Week,
32018                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
32019                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
32020                                        "SECOND" => crate::expressions::IntervalUnit::Second,
32021                                        _ => crate::expressions::IntervalUnit::Day,
32022                                    };
32023                                    let interval = Expression::Interval(Box::new(
32024                                        crate::expressions::Interval {
32025                                            this: Some(n),
32026                                            unit: Some(
32027                                                crate::expressions::IntervalUnitSpec::Simple {
32028                                                    unit: iu,
32029                                                    use_plural: false,
32030                                                },
32031                                            ),
32032                                        },
32033                                    ));
32034                                    Ok(Expression::Add(Box::new(crate::expressions::BinaryOp {
32035                                        left: cast_date,
32036                                        right: interval,
32037                                        left_comments: Vec::new(),
32038                                        operator_comments: Vec::new(),
32039                                        trailing_comments: Vec::new(),
32040                                        inferred_type: None,
32041                                    })))
32042                                }
32043                                DialectType::Drill => {
32044                                    // DATE_ADD(CAST(x AS DATE), INTERVAL n UNIT)
32045                                    let cast_date = Expression::Cast(Box::new(Cast {
32046                                        this: x,
32047                                        to: DataType::Date,
32048                                        double_colon_syntax: false,
32049                                        trailing_comments: Vec::new(),
32050                                        format: None,
32051                                        default: None,
32052                                        inferred_type: None,
32053                                    }));
32054                                    let iu = match unit_str.as_str() {
32055                                        "YEAR" => crate::expressions::IntervalUnit::Year,
32056                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
32057                                        "MONTH" => crate::expressions::IntervalUnit::Month,
32058                                        "WEEK" => crate::expressions::IntervalUnit::Week,
32059                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
32060                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
32061                                        "SECOND" => crate::expressions::IntervalUnit::Second,
32062                                        _ => crate::expressions::IntervalUnit::Day,
32063                                    };
32064                                    let interval = Expression::Interval(Box::new(
32065                                        crate::expressions::Interval {
32066                                            this: Some(n),
32067                                            unit: Some(
32068                                                crate::expressions::IntervalUnitSpec::Simple {
32069                                                    unit: iu,
32070                                                    use_plural: false,
32071                                                },
32072                                            ),
32073                                        },
32074                                    ));
32075                                    Ok(Expression::Function(Box::new(Function::new(
32076                                        "DATE_ADD".to_string(),
32077                                        vec![cast_date, interval],
32078                                    ))))
32079                                }
32080                                _ => {
32081                                    // Default: keep as TS_OR_DS_ADD
32082                                    Ok(Expression::Function(Box::new(Function::new(
32083                                        "TS_OR_DS_ADD".to_string(),
32084                                        vec![x, n, unit_expr],
32085                                    ))))
32086                                }
32087                            }
32088                        } else {
32089                            Ok(Expression::Function(f))
32090                        }
32091                    } else {
32092                        Ok(e)
32093                    }
32094                }
32095
32096                Action::DateFromUnixDateConvert => {
32097                    // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
32098                    if let Expression::Function(f) = e {
32099                        // Keep as-is for dialects that support DATE_FROM_UNIX_DATE natively
32100                        if matches!(
32101                            target,
32102                            DialectType::Spark | DialectType::Databricks | DialectType::BigQuery
32103                        ) {
32104                            return Ok(Expression::Function(Box::new(Function::new(
32105                                "DATE_FROM_UNIX_DATE".to_string(),
32106                                f.args,
32107                            ))));
32108                        }
32109                        let n = f.args.into_iter().next().unwrap();
32110                        let epoch_date = Expression::Cast(Box::new(Cast {
32111                            this: Expression::string("1970-01-01"),
32112                            to: DataType::Date,
32113                            double_colon_syntax: false,
32114                            trailing_comments: Vec::new(),
32115                            format: None,
32116                            default: None,
32117                            inferred_type: None,
32118                        }));
32119                        match target {
32120                            DialectType::DuckDB => {
32121                                // CAST('1970-01-01' AS DATE) + INTERVAL n DAY
32122                                let interval =
32123                                    Expression::Interval(Box::new(crate::expressions::Interval {
32124                                        this: Some(n),
32125                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
32126                                            unit: crate::expressions::IntervalUnit::Day,
32127                                            use_plural: false,
32128                                        }),
32129                                    }));
32130                                Ok(Expression::Add(Box::new(
32131                                    crate::expressions::BinaryOp::new(epoch_date, interval),
32132                                )))
32133                            }
32134                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32135                                // DATE_ADD('DAY', n, CAST('1970-01-01' AS DATE))
32136                                Ok(Expression::Function(Box::new(Function::new(
32137                                    "DATE_ADD".to_string(),
32138                                    vec![Expression::string("DAY"), n, epoch_date],
32139                                ))))
32140                            }
32141                            DialectType::Snowflake | DialectType::Redshift | DialectType::TSQL => {
32142                                // DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
32143                                Ok(Expression::Function(Box::new(Function::new(
32144                                    "DATEADD".to_string(),
32145                                    vec![
32146                                        Expression::Identifier(Identifier::new("DAY")),
32147                                        n,
32148                                        epoch_date,
32149                                    ],
32150                                ))))
32151                            }
32152                            DialectType::BigQuery => {
32153                                // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
32154                                let interval =
32155                                    Expression::Interval(Box::new(crate::expressions::Interval {
32156                                        this: Some(n),
32157                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
32158                                            unit: crate::expressions::IntervalUnit::Day,
32159                                            use_plural: false,
32160                                        }),
32161                                    }));
32162                                Ok(Expression::Function(Box::new(Function::new(
32163                                    "DATE_ADD".to_string(),
32164                                    vec![epoch_date, interval],
32165                                ))))
32166                            }
32167                            DialectType::MySQL
32168                            | DialectType::Doris
32169                            | DialectType::StarRocks
32170                            | DialectType::Drill => {
32171                                // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
32172                                let interval =
32173                                    Expression::Interval(Box::new(crate::expressions::Interval {
32174                                        this: Some(n),
32175                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
32176                                            unit: crate::expressions::IntervalUnit::Day,
32177                                            use_plural: false,
32178                                        }),
32179                                    }));
32180                                Ok(Expression::Function(Box::new(Function::new(
32181                                    "DATE_ADD".to_string(),
32182                                    vec![epoch_date, interval],
32183                                ))))
32184                            }
32185                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32186                                // DATE_ADD(CAST('1970-01-01' AS DATE), n)
32187                                Ok(Expression::Function(Box::new(Function::new(
32188                                    "DATE_ADD".to_string(),
32189                                    vec![epoch_date, n],
32190                                ))))
32191                            }
32192                            DialectType::PostgreSQL
32193                            | DialectType::Materialize
32194                            | DialectType::RisingWave => {
32195                                // CAST('1970-01-01' AS DATE) + INTERVAL 'n DAY'
32196                                let n_str = match &n {
32197                                    Expression::Literal(lit)
32198                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
32199                                    {
32200                                        let Literal::Number(s) = lit.as_ref() else {
32201                                            unreachable!()
32202                                        };
32203                                        s.clone()
32204                                    }
32205                                    _ => Self::expr_to_string_static(&n),
32206                                };
32207                                let interval =
32208                                    Expression::Interval(Box::new(crate::expressions::Interval {
32209                                        this: Some(Expression::string(&format!("{} DAY", n_str))),
32210                                        unit: None,
32211                                    }));
32212                                Ok(Expression::Add(Box::new(
32213                                    crate::expressions::BinaryOp::new(epoch_date, interval),
32214                                )))
32215                            }
32216                            _ => {
32217                                // Default: keep as-is
32218                                Ok(Expression::Function(Box::new(Function::new(
32219                                    "DATE_FROM_UNIX_DATE".to_string(),
32220                                    vec![n],
32221                                ))))
32222                            }
32223                        }
32224                    } else {
32225                        Ok(e)
32226                    }
32227                }
32228
32229                Action::ArrayRemoveConvert => {
32230                    // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter
32231                    if let Expression::ArrayRemove(bf) = e {
32232                        let arr = bf.this;
32233                        let target_val = bf.expression;
32234                        match target {
32235                            DialectType::DuckDB => {
32236                                let u_id = crate::expressions::Identifier::new("_u");
32237                                let lambda =
32238                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
32239                                        parameters: vec![u_id.clone()],
32240                                        body: Expression::Neq(Box::new(BinaryOp {
32241                                            left: Expression::Identifier(u_id),
32242                                            right: target_val,
32243                                            left_comments: Vec::new(),
32244                                            operator_comments: Vec::new(),
32245                                            trailing_comments: Vec::new(),
32246                                            inferred_type: None,
32247                                        })),
32248                                        colon: false,
32249                                        parameter_types: Vec::new(),
32250                                    }));
32251                                Ok(Expression::Function(Box::new(Function::new(
32252                                    "LIST_FILTER".to_string(),
32253                                    vec![arr, lambda],
32254                                ))))
32255                            }
32256                            DialectType::ClickHouse => {
32257                                let u_id = crate::expressions::Identifier::new("_u");
32258                                let lambda =
32259                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
32260                                        parameters: vec![u_id.clone()],
32261                                        body: Expression::Neq(Box::new(BinaryOp {
32262                                            left: Expression::Identifier(u_id),
32263                                            right: target_val,
32264                                            left_comments: Vec::new(),
32265                                            operator_comments: Vec::new(),
32266                                            trailing_comments: Vec::new(),
32267                                            inferred_type: None,
32268                                        })),
32269                                        colon: false,
32270                                        parameter_types: Vec::new(),
32271                                    }));
32272                                Ok(Expression::Function(Box::new(Function::new(
32273                                    "arrayFilter".to_string(),
32274                                    vec![lambda, arr],
32275                                ))))
32276                            }
32277                            DialectType::BigQuery => {
32278                                // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
32279                                let u_id = crate::expressions::Identifier::new("_u");
32280                                let u_col =
32281                                    Expression::Column(Box::new(crate::expressions::Column {
32282                                        name: u_id.clone(),
32283                                        table: None,
32284                                        join_mark: false,
32285                                        trailing_comments: Vec::new(),
32286                                        span: None,
32287                                        inferred_type: None,
32288                                    }));
32289                                let unnest_expr =
32290                                    Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
32291                                        this: arr,
32292                                        expressions: Vec::new(),
32293                                        with_ordinality: false,
32294                                        alias: None,
32295                                        offset_alias: None,
32296                                    }));
32297                                let aliased_unnest =
32298                                    Expression::Alias(Box::new(crate::expressions::Alias {
32299                                        this: unnest_expr,
32300                                        alias: u_id.clone(),
32301                                        column_aliases: Vec::new(),
32302                                        alias_explicit_as: false,
32303                                        alias_keyword: None,
32304                                        pre_alias_comments: Vec::new(),
32305                                        trailing_comments: Vec::new(),
32306                                        inferred_type: None,
32307                                    }));
32308                                let where_cond = Expression::Neq(Box::new(BinaryOp {
32309                                    left: u_col.clone(),
32310                                    right: target_val,
32311                                    left_comments: Vec::new(),
32312                                    operator_comments: Vec::new(),
32313                                    trailing_comments: Vec::new(),
32314                                    inferred_type: None,
32315                                }));
32316                                let subquery = Expression::Select(Box::new(
32317                                    crate::expressions::Select::new()
32318                                        .column(u_col)
32319                                        .from(aliased_unnest)
32320                                        .where_(where_cond),
32321                                ));
32322                                Ok(Expression::ArrayFunc(Box::new(
32323                                    crate::expressions::ArrayConstructor {
32324                                        expressions: vec![subquery],
32325                                        bracket_notation: false,
32326                                        use_list_keyword: false,
32327                                    },
32328                                )))
32329                            }
32330                            _ => Ok(Expression::ArrayRemove(Box::new(
32331                                crate::expressions::BinaryFunc {
32332                                    original_name: None,
32333                                    this: arr,
32334                                    expression: target_val,
32335                                    inferred_type: None,
32336                                },
32337                            ))),
32338                        }
32339                    } else {
32340                        Ok(e)
32341                    }
32342                }
32343
32344                Action::ArrayReverseConvert => {
32345                    // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
32346                    if let Expression::ArrayReverse(af) = e {
32347                        Ok(Expression::Function(Box::new(Function::new(
32348                            "arrayReverse".to_string(),
32349                            vec![af.this],
32350                        ))))
32351                    } else {
32352                        Ok(e)
32353                    }
32354                }
32355
32356                Action::JsonKeysConvert => {
32357                    // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS
32358                    if let Expression::JsonKeys(uf) = e {
32359                        match target {
32360                            DialectType::Spark | DialectType::Databricks => {
32361                                Ok(Expression::Function(Box::new(Function::new(
32362                                    "JSON_OBJECT_KEYS".to_string(),
32363                                    vec![uf.this],
32364                                ))))
32365                            }
32366                            DialectType::Snowflake => Ok(Expression::Function(Box::new(
32367                                Function::new("OBJECT_KEYS".to_string(), vec![uf.this]),
32368                            ))),
32369                            _ => Ok(Expression::JsonKeys(uf)),
32370                        }
32371                    } else {
32372                        Ok(e)
32373                    }
32374                }
32375
32376                Action::ParseJsonStrip => {
32377                    // PARSE_JSON(x) -> x (strip wrapper for SQLite/Doris)
32378                    if let Expression::ParseJson(uf) = e {
32379                        Ok(uf.this)
32380                    } else {
32381                        Ok(e)
32382                    }
32383                }
32384
32385                Action::ArraySizeDrill => {
32386                    // ARRAY_SIZE(x) -> REPEATED_COUNT(x) for Drill
32387                    if let Expression::ArraySize(uf) = e {
32388                        Ok(Expression::Function(Box::new(Function::new(
32389                            "REPEATED_COUNT".to_string(),
32390                            vec![uf.this],
32391                        ))))
32392                    } else {
32393                        Ok(e)
32394                    }
32395                }
32396
32397                Action::WeekOfYearToWeekIso => {
32398                    // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake (cross-dialect normalization)
32399                    if let Expression::WeekOfYear(uf) = e {
32400                        Ok(Expression::Function(Box::new(Function::new(
32401                            "WEEKISO".to_string(),
32402                            vec![uf.this],
32403                        ))))
32404                    } else {
32405                        Ok(e)
32406                    }
32407                }
32408            }
32409        })
32410    }
32411
32412    /// Convert DATE_TRUNC('unit', x) to MySQL-specific expansion
32413    fn date_trunc_to_mysql(unit: &str, expr: &Expression) -> Result<Expression> {
32414        use crate::expressions::Function;
32415        match unit {
32416            "DAY" => {
32417                // DATE(x)
32418                Ok(Expression::Function(Box::new(Function::new(
32419                    "DATE".to_string(),
32420                    vec![expr.clone()],
32421                ))))
32422            }
32423            "WEEK" => {
32424                // STR_TO_DATE(CONCAT(YEAR(x), ' ', WEEK(x, 1), ' 1'), '%Y %u %w')
32425                let year_x = Expression::Function(Box::new(Function::new(
32426                    "YEAR".to_string(),
32427                    vec![expr.clone()],
32428                )));
32429                let week_x = Expression::Function(Box::new(Function::new(
32430                    "WEEK".to_string(),
32431                    vec![expr.clone(), Expression::number(1)],
32432                )));
32433                let concat_args = vec![
32434                    year_x,
32435                    Expression::string(" "),
32436                    week_x,
32437                    Expression::string(" 1"),
32438                ];
32439                let concat = Expression::Function(Box::new(Function::new(
32440                    "CONCAT".to_string(),
32441                    concat_args,
32442                )));
32443                Ok(Expression::Function(Box::new(Function::new(
32444                    "STR_TO_DATE".to_string(),
32445                    vec![concat, Expression::string("%Y %u %w")],
32446                ))))
32447            }
32448            "MONTH" => {
32449                // STR_TO_DATE(CONCAT(YEAR(x), ' ', MONTH(x), ' 1'), '%Y %c %e')
32450                let year_x = Expression::Function(Box::new(Function::new(
32451                    "YEAR".to_string(),
32452                    vec![expr.clone()],
32453                )));
32454                let month_x = Expression::Function(Box::new(Function::new(
32455                    "MONTH".to_string(),
32456                    vec![expr.clone()],
32457                )));
32458                let concat_args = vec![
32459                    year_x,
32460                    Expression::string(" "),
32461                    month_x,
32462                    Expression::string(" 1"),
32463                ];
32464                let concat = Expression::Function(Box::new(Function::new(
32465                    "CONCAT".to_string(),
32466                    concat_args,
32467                )));
32468                Ok(Expression::Function(Box::new(Function::new(
32469                    "STR_TO_DATE".to_string(),
32470                    vec![concat, Expression::string("%Y %c %e")],
32471                ))))
32472            }
32473            "QUARTER" => {
32474                // STR_TO_DATE(CONCAT(YEAR(x), ' ', QUARTER(x) * 3 - 2, ' 1'), '%Y %c %e')
32475                let year_x = Expression::Function(Box::new(Function::new(
32476                    "YEAR".to_string(),
32477                    vec![expr.clone()],
32478                )));
32479                let quarter_x = Expression::Function(Box::new(Function::new(
32480                    "QUARTER".to_string(),
32481                    vec![expr.clone()],
32482                )));
32483                // QUARTER(x) * 3 - 2
32484                let mul = Expression::Mul(Box::new(crate::expressions::BinaryOp {
32485                    left: quarter_x,
32486                    right: Expression::number(3),
32487                    left_comments: Vec::new(),
32488                    operator_comments: Vec::new(),
32489                    trailing_comments: Vec::new(),
32490                    inferred_type: None,
32491                }));
32492                let sub = Expression::Sub(Box::new(crate::expressions::BinaryOp {
32493                    left: mul,
32494                    right: Expression::number(2),
32495                    left_comments: Vec::new(),
32496                    operator_comments: Vec::new(),
32497                    trailing_comments: Vec::new(),
32498                    inferred_type: None,
32499                }));
32500                let concat_args = vec![
32501                    year_x,
32502                    Expression::string(" "),
32503                    sub,
32504                    Expression::string(" 1"),
32505                ];
32506                let concat = Expression::Function(Box::new(Function::new(
32507                    "CONCAT".to_string(),
32508                    concat_args,
32509                )));
32510                Ok(Expression::Function(Box::new(Function::new(
32511                    "STR_TO_DATE".to_string(),
32512                    vec![concat, Expression::string("%Y %c %e")],
32513                ))))
32514            }
32515            "YEAR" => {
32516                // STR_TO_DATE(CONCAT(YEAR(x), ' 1 1'), '%Y %c %e')
32517                let year_x = Expression::Function(Box::new(Function::new(
32518                    "YEAR".to_string(),
32519                    vec![expr.clone()],
32520                )));
32521                let concat_args = vec![year_x, Expression::string(" 1 1")];
32522                let concat = Expression::Function(Box::new(Function::new(
32523                    "CONCAT".to_string(),
32524                    concat_args,
32525                )));
32526                Ok(Expression::Function(Box::new(Function::new(
32527                    "STR_TO_DATE".to_string(),
32528                    vec![concat, Expression::string("%Y %c %e")],
32529                ))))
32530            }
32531            _ => {
32532                // Unsupported unit -> keep as DATE_TRUNC
32533                Ok(Expression::Function(Box::new(Function::new(
32534                    "DATE_TRUNC".to_string(),
32535                    vec![Expression::string(unit), expr.clone()],
32536                ))))
32537            }
32538        }
32539    }
32540
32541    /// Check if a DataType is or contains VARCHAR/CHAR (for Spark VARCHAR->STRING normalization)
32542    fn has_varchar_char_type(dt: &crate::expressions::DataType) -> bool {
32543        use crate::expressions::DataType;
32544        match dt {
32545            DataType::VarChar { .. } | DataType::Char { .. } => true,
32546            DataType::Struct { fields, .. } => fields
32547                .iter()
32548                .any(|f| Self::has_varchar_char_type(&f.data_type)),
32549            _ => false,
32550        }
32551    }
32552
32553    /// Recursively normalize VARCHAR/CHAR to STRING in a DataType (for Spark)
32554    fn normalize_varchar_to_string(
32555        dt: crate::expressions::DataType,
32556    ) -> crate::expressions::DataType {
32557        use crate::expressions::DataType;
32558        match dt {
32559            DataType::VarChar { .. } | DataType::Char { .. } => DataType::Custom {
32560                name: "STRING".to_string(),
32561            },
32562            DataType::Struct { fields, nested } => {
32563                let fields = fields
32564                    .into_iter()
32565                    .map(|mut f| {
32566                        f.data_type = Self::normalize_varchar_to_string(f.data_type);
32567                        f
32568                    })
32569                    .collect();
32570                DataType::Struct { fields, nested }
32571            }
32572            other => other,
32573        }
32574    }
32575
32576    /// Normalize an interval string like '1day' or '  2   days  ' to proper INTERVAL expression
32577    fn normalize_interval_string(expr: Expression, target: DialectType) -> Expression {
32578        if let Expression::Literal(ref lit) = expr {
32579            if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
32580                // Try to parse patterns like '1day', '1 day', '2 days', '  2   days  '
32581                let trimmed = s.trim();
32582
32583                // Find where digits end and unit text begins
32584                let digit_end = trimmed
32585                    .find(|c: char| !c.is_ascii_digit())
32586                    .unwrap_or(trimmed.len());
32587                if digit_end == 0 || digit_end == trimmed.len() {
32588                    return expr;
32589                }
32590                let num = &trimmed[..digit_end];
32591                let unit_text = trimmed[digit_end..].trim().to_ascii_uppercase();
32592                if unit_text.is_empty() {
32593                    return expr;
32594                }
32595
32596                let known_units = [
32597                    "DAY", "DAYS", "HOUR", "HOURS", "MINUTE", "MINUTES", "SECOND", "SECONDS",
32598                    "WEEK", "WEEKS", "MONTH", "MONTHS", "YEAR", "YEARS",
32599                ];
32600                if !known_units.contains(&unit_text.as_str()) {
32601                    return expr;
32602                }
32603
32604                let unit_str = unit_text.clone();
32605                // Singularize
32606                let unit_singular = if unit_str.ends_with('S') && unit_str.len() > 3 {
32607                    &unit_str[..unit_str.len() - 1]
32608                } else {
32609                    &unit_str
32610                };
32611                let unit = unit_singular;
32612
32613                match target {
32614                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32615                        // INTERVAL '2' DAY
32616                        let iu = match unit {
32617                            "DAY" => crate::expressions::IntervalUnit::Day,
32618                            "HOUR" => crate::expressions::IntervalUnit::Hour,
32619                            "MINUTE" => crate::expressions::IntervalUnit::Minute,
32620                            "SECOND" => crate::expressions::IntervalUnit::Second,
32621                            "WEEK" => crate::expressions::IntervalUnit::Week,
32622                            "MONTH" => crate::expressions::IntervalUnit::Month,
32623                            "YEAR" => crate::expressions::IntervalUnit::Year,
32624                            _ => return expr,
32625                        };
32626                        return Expression::Interval(Box::new(crate::expressions::Interval {
32627                            this: Some(Expression::string(num)),
32628                            unit: Some(crate::expressions::IntervalUnitSpec::Simple {
32629                                unit: iu,
32630                                use_plural: false,
32631                            }),
32632                        }));
32633                    }
32634                    DialectType::PostgreSQL | DialectType::Redshift | DialectType::DuckDB => {
32635                        // INTERVAL '2 DAYS'
32636                        let plural = if num != "1" && !unit_str.ends_with('S') {
32637                            format!("{} {}S", num, unit)
32638                        } else if unit_str.ends_with('S') {
32639                            format!("{} {}", num, unit_str)
32640                        } else {
32641                            format!("{} {}", num, unit)
32642                        };
32643                        return Expression::Interval(Box::new(crate::expressions::Interval {
32644                            this: Some(Expression::string(&plural)),
32645                            unit: None,
32646                        }));
32647                    }
32648                    _ => {
32649                        // Spark/Databricks/Hive: INTERVAL '1' DAY
32650                        let iu = match unit {
32651                            "DAY" => crate::expressions::IntervalUnit::Day,
32652                            "HOUR" => crate::expressions::IntervalUnit::Hour,
32653                            "MINUTE" => crate::expressions::IntervalUnit::Minute,
32654                            "SECOND" => crate::expressions::IntervalUnit::Second,
32655                            "WEEK" => crate::expressions::IntervalUnit::Week,
32656                            "MONTH" => crate::expressions::IntervalUnit::Month,
32657                            "YEAR" => crate::expressions::IntervalUnit::Year,
32658                            _ => return expr,
32659                        };
32660                        return Expression::Interval(Box::new(crate::expressions::Interval {
32661                            this: Some(Expression::string(num)),
32662                            unit: Some(crate::expressions::IntervalUnitSpec::Simple {
32663                                unit: iu,
32664                                use_plural: false,
32665                            }),
32666                        }));
32667                    }
32668                }
32669            }
32670        }
32671        // If it's already an INTERVAL expression, pass through
32672        expr
32673    }
32674
32675    /// Rewrite SELECT expressions containing UNNEST into expanded form with CROSS JOINs.
32676    /// DuckDB: SELECT UNNEST(arr1), UNNEST(arr2) ->
32677    /// BigQuery: SELECT IF(pos = pos_2, col, NULL) AS col, ... FROM UNNEST(GENERATE_ARRAY(0, ...)) AS pos CROSS JOIN ...
32678    /// Presto:  SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, ... FROM UNNEST(SEQUENCE(1, ...)) AS _u(pos) CROSS JOIN ...
32679    fn rewrite_unnest_expansion(
32680        select: &crate::expressions::Select,
32681        target: DialectType,
32682    ) -> Option<crate::expressions::Select> {
32683        use crate::expressions::{
32684            Alias, BinaryOp, Column, From, Function, Identifier, Join, JoinKind, Literal,
32685            UnnestFunc,
32686        };
32687
32688        let index_offset: i64 = match target {
32689            DialectType::Presto | DialectType::Trino => 1,
32690            _ => 0, // BigQuery, Snowflake
32691        };
32692
32693        let if_func_name = match target {
32694            DialectType::Snowflake => "IFF",
32695            _ => "IF",
32696        };
32697
32698        let array_length_func = match target {
32699            DialectType::BigQuery => "ARRAY_LENGTH",
32700            DialectType::Presto | DialectType::Trino => "CARDINALITY",
32701            DialectType::Snowflake => "ARRAY_SIZE",
32702            _ => "ARRAY_LENGTH",
32703        };
32704
32705        let use_table_aliases = matches!(
32706            target,
32707            DialectType::Presto | DialectType::Trino | DialectType::Snowflake
32708        );
32709        let null_third_arg = matches!(target, DialectType::BigQuery | DialectType::Snowflake);
32710
32711        fn make_col(name: &str, table: Option<&str>) -> Expression {
32712            if let Some(tbl) = table {
32713                Expression::boxed_column(Column {
32714                    name: Identifier::new(name.to_string()),
32715                    table: Some(Identifier::new(tbl.to_string())),
32716                    join_mark: false,
32717                    trailing_comments: Vec::new(),
32718                    span: None,
32719                    inferred_type: None,
32720                })
32721            } else {
32722                Expression::Identifier(Identifier::new(name.to_string()))
32723            }
32724        }
32725
32726        fn make_join(this: Expression) -> Join {
32727            Join {
32728                this,
32729                on: None,
32730                using: Vec::new(),
32731                kind: JoinKind::Cross,
32732                use_inner_keyword: false,
32733                use_outer_keyword: false,
32734                deferred_condition: false,
32735                join_hint: None,
32736                match_condition: None,
32737                pivots: Vec::new(),
32738                comments: Vec::new(),
32739                nesting_group: 0,
32740                directed: false,
32741            }
32742        }
32743
32744        // Collect UNNEST info from SELECT expressions
32745        struct UnnestInfo {
32746            arr_expr: Expression,
32747            col_alias: String,
32748            pos_alias: String,
32749            source_alias: String,
32750            original_expr: Expression,
32751            has_outer_alias: Option<String>,
32752        }
32753
32754        let mut unnest_infos: Vec<UnnestInfo> = Vec::new();
32755        let mut col_counter = 0usize;
32756        let mut pos_counter = 1usize;
32757        let mut source_counter = 1usize;
32758
32759        fn extract_unnest_arg(expr: &Expression) -> Option<Expression> {
32760            match expr {
32761                Expression::Unnest(u) => Some(u.this.clone()),
32762                Expression::Function(f)
32763                    if f.name.eq_ignore_ascii_case("UNNEST") && !f.args.is_empty() =>
32764                {
32765                    Some(f.args[0].clone())
32766                }
32767                Expression::Alias(a) => extract_unnest_arg(&a.this),
32768                Expression::Add(op)
32769                | Expression::Sub(op)
32770                | Expression::Mul(op)
32771                | Expression::Div(op) => {
32772                    extract_unnest_arg(&op.left).or_else(|| extract_unnest_arg(&op.right))
32773                }
32774                _ => None,
32775            }
32776        }
32777
32778        fn get_alias_name(expr: &Expression) -> Option<String> {
32779            if let Expression::Alias(a) = expr {
32780                Some(a.alias.name.clone())
32781            } else {
32782                None
32783            }
32784        }
32785
32786        for sel_expr in &select.expressions {
32787            if let Some(arr) = extract_unnest_arg(sel_expr) {
32788                col_counter += 1;
32789                pos_counter += 1;
32790                source_counter += 1;
32791
32792                let col_alias = if col_counter == 1 {
32793                    "col".to_string()
32794                } else {
32795                    format!("col_{}", col_counter)
32796                };
32797                let pos_alias = format!("pos_{}", pos_counter);
32798                let source_alias = format!("_u_{}", source_counter);
32799                let has_outer_alias = get_alias_name(sel_expr);
32800
32801                unnest_infos.push(UnnestInfo {
32802                    arr_expr: arr,
32803                    col_alias,
32804                    pos_alias,
32805                    source_alias,
32806                    original_expr: sel_expr.clone(),
32807                    has_outer_alias,
32808                });
32809            }
32810        }
32811
32812        if unnest_infos.is_empty() {
32813            return None;
32814        }
32815
32816        let series_alias = "pos".to_string();
32817        let series_source_alias = "_u".to_string();
32818        let tbl_ref = if use_table_aliases {
32819            Some(series_source_alias.as_str())
32820        } else {
32821            None
32822        };
32823
32824        // Build new SELECT expressions
32825        let mut new_select_exprs = Vec::new();
32826        for info in &unnest_infos {
32827            let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
32828            let src_ref = if use_table_aliases {
32829                Some(info.source_alias.as_str())
32830            } else {
32831                None
32832            };
32833
32834            let pos_col = make_col(&series_alias, tbl_ref);
32835            let unnest_pos_col = make_col(&info.pos_alias, src_ref);
32836            let col_ref = make_col(actual_col_name, src_ref);
32837
32838            let eq_cond = Expression::Eq(Box::new(BinaryOp::new(
32839                pos_col.clone(),
32840                unnest_pos_col.clone(),
32841            )));
32842            let mut if_args = vec![eq_cond, col_ref];
32843            if null_third_arg {
32844                if_args.push(Expression::Null(crate::expressions::Null));
32845            }
32846
32847            let if_expr =
32848                Expression::Function(Box::new(Function::new(if_func_name.to_string(), if_args)));
32849            let final_expr = Self::replace_unnest_with_if(&info.original_expr, &if_expr);
32850
32851            new_select_exprs.push(Expression::Alias(Box::new(Alias::new(
32852                final_expr,
32853                Identifier::new(actual_col_name.clone()),
32854            ))));
32855        }
32856
32857        // Build array size expressions for GREATEST
32858        let size_exprs: Vec<Expression> = unnest_infos
32859            .iter()
32860            .map(|info| {
32861                Expression::Function(Box::new(Function::new(
32862                    array_length_func.to_string(),
32863                    vec![info.arr_expr.clone()],
32864                )))
32865            })
32866            .collect();
32867
32868        let greatest =
32869            Expression::Function(Box::new(Function::new("GREATEST".to_string(), size_exprs)));
32870
32871        let series_end = if index_offset == 0 {
32872            Expression::Sub(Box::new(BinaryOp::new(
32873                greatest,
32874                Expression::Literal(Box::new(Literal::Number("1".to_string()))),
32875            )))
32876        } else {
32877            greatest
32878        };
32879
32880        // Build the position array source
32881        let series_unnest_expr = match target {
32882            DialectType::BigQuery => {
32883                let gen_array = Expression::Function(Box::new(Function::new(
32884                    "GENERATE_ARRAY".to_string(),
32885                    vec![
32886                        Expression::Literal(Box::new(Literal::Number("0".to_string()))),
32887                        series_end,
32888                    ],
32889                )));
32890                Expression::Unnest(Box::new(UnnestFunc {
32891                    this: gen_array,
32892                    expressions: Vec::new(),
32893                    with_ordinality: false,
32894                    alias: None,
32895                    offset_alias: None,
32896                }))
32897            }
32898            DialectType::Presto | DialectType::Trino => {
32899                let sequence = Expression::Function(Box::new(Function::new(
32900                    "SEQUENCE".to_string(),
32901                    vec![
32902                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
32903                        series_end,
32904                    ],
32905                )));
32906                Expression::Unnest(Box::new(UnnestFunc {
32907                    this: sequence,
32908                    expressions: Vec::new(),
32909                    with_ordinality: false,
32910                    alias: None,
32911                    offset_alias: None,
32912                }))
32913            }
32914            DialectType::Snowflake => {
32915                let range_end = Expression::Add(Box::new(BinaryOp::new(
32916                    Expression::Paren(Box::new(crate::expressions::Paren {
32917                        this: series_end,
32918                        trailing_comments: Vec::new(),
32919                    })),
32920                    Expression::Literal(Box::new(Literal::Number("1".to_string()))),
32921                )));
32922                let gen_range = Expression::Function(Box::new(Function::new(
32923                    "ARRAY_GENERATE_RANGE".to_string(),
32924                    vec![
32925                        Expression::Literal(Box::new(Literal::Number("0".to_string()))),
32926                        range_end,
32927                    ],
32928                )));
32929                let flatten_arg =
32930                    Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
32931                        name: Identifier::new("INPUT".to_string()),
32932                        value: gen_range,
32933                        separator: crate::expressions::NamedArgSeparator::DArrow,
32934                    }));
32935                let flatten = Expression::Function(Box::new(Function::new(
32936                    "FLATTEN".to_string(),
32937                    vec![flatten_arg],
32938                )));
32939                Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])))
32940            }
32941            _ => return None,
32942        };
32943
32944        // Build series alias expression
32945        let series_alias_expr = if use_table_aliases {
32946            let col_aliases = if matches!(target, DialectType::Snowflake) {
32947                vec![
32948                    Identifier::new("seq".to_string()),
32949                    Identifier::new("key".to_string()),
32950                    Identifier::new("path".to_string()),
32951                    Identifier::new("index".to_string()),
32952                    Identifier::new(series_alias.clone()),
32953                    Identifier::new("this".to_string()),
32954                ]
32955            } else {
32956                vec![Identifier::new(series_alias.clone())]
32957            };
32958            Expression::Alias(Box::new(Alias {
32959                this: series_unnest_expr,
32960                alias: Identifier::new(series_source_alias.clone()),
32961                column_aliases: col_aliases,
32962                alias_explicit_as: false,
32963                alias_keyword: None,
32964                pre_alias_comments: Vec::new(),
32965                trailing_comments: Vec::new(),
32966                inferred_type: None,
32967            }))
32968        } else {
32969            Expression::Alias(Box::new(Alias::new(
32970                series_unnest_expr,
32971                Identifier::new(series_alias.clone()),
32972            )))
32973        };
32974
32975        // Build CROSS JOINs for each UNNEST
32976        let mut joins = Vec::new();
32977        for info in &unnest_infos {
32978            let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
32979
32980            let unnest_join_expr = match target {
32981                DialectType::BigQuery => {
32982                    // UNNEST([1,2,3]) AS col WITH OFFSET AS pos_2
32983                    let unnest = UnnestFunc {
32984                        this: info.arr_expr.clone(),
32985                        expressions: Vec::new(),
32986                        with_ordinality: true,
32987                        alias: Some(Identifier::new(actual_col_name.clone())),
32988                        offset_alias: Some(Identifier::new(info.pos_alias.clone())),
32989                    };
32990                    Expression::Unnest(Box::new(unnest))
32991                }
32992                DialectType::Presto | DialectType::Trino => {
32993                    let unnest = UnnestFunc {
32994                        this: info.arr_expr.clone(),
32995                        expressions: Vec::new(),
32996                        with_ordinality: true,
32997                        alias: None,
32998                        offset_alias: None,
32999                    };
33000                    Expression::Alias(Box::new(Alias {
33001                        this: Expression::Unnest(Box::new(unnest)),
33002                        alias: Identifier::new(info.source_alias.clone()),
33003                        column_aliases: vec![
33004                            Identifier::new(actual_col_name.clone()),
33005                            Identifier::new(info.pos_alias.clone()),
33006                        ],
33007                        alias_explicit_as: false,
33008                        alias_keyword: None,
33009                        pre_alias_comments: Vec::new(),
33010                        trailing_comments: Vec::new(),
33011                        inferred_type: None,
33012                    }))
33013                }
33014                DialectType::Snowflake => {
33015                    let flatten_arg =
33016                        Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
33017                            name: Identifier::new("INPUT".to_string()),
33018                            value: info.arr_expr.clone(),
33019                            separator: crate::expressions::NamedArgSeparator::DArrow,
33020                        }));
33021                    let flatten = Expression::Function(Box::new(Function::new(
33022                        "FLATTEN".to_string(),
33023                        vec![flatten_arg],
33024                    )));
33025                    let table_fn = Expression::Function(Box::new(Function::new(
33026                        "TABLE".to_string(),
33027                        vec![flatten],
33028                    )));
33029                    Expression::Alias(Box::new(Alias {
33030                        this: table_fn,
33031                        alias: Identifier::new(info.source_alias.clone()),
33032                        column_aliases: vec![
33033                            Identifier::new("seq".to_string()),
33034                            Identifier::new("key".to_string()),
33035                            Identifier::new("path".to_string()),
33036                            Identifier::new(info.pos_alias.clone()),
33037                            Identifier::new(actual_col_name.clone()),
33038                            Identifier::new("this".to_string()),
33039                        ],
33040                        alias_explicit_as: false,
33041                        alias_keyword: None,
33042                        pre_alias_comments: Vec::new(),
33043                        trailing_comments: Vec::new(),
33044                        inferred_type: None,
33045                    }))
33046                }
33047                _ => return None,
33048            };
33049
33050            joins.push(make_join(unnest_join_expr));
33051        }
33052
33053        // Build WHERE clause
33054        let mut where_conditions: Vec<Expression> = Vec::new();
33055        for info in &unnest_infos {
33056            let src_ref = if use_table_aliases {
33057                Some(info.source_alias.as_str())
33058            } else {
33059                None
33060            };
33061            let pos_col = make_col(&series_alias, tbl_ref);
33062            let unnest_pos_col = make_col(&info.pos_alias, src_ref);
33063
33064            let arr_size = Expression::Function(Box::new(Function::new(
33065                array_length_func.to_string(),
33066                vec![info.arr_expr.clone()],
33067            )));
33068
33069            let size_ref = if index_offset == 0 {
33070                Expression::Paren(Box::new(crate::expressions::Paren {
33071                    this: Expression::Sub(Box::new(BinaryOp::new(
33072                        arr_size,
33073                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
33074                    ))),
33075                    trailing_comments: Vec::new(),
33076                }))
33077            } else {
33078                arr_size
33079            };
33080
33081            let eq = Expression::Eq(Box::new(BinaryOp::new(
33082                pos_col.clone(),
33083                unnest_pos_col.clone(),
33084            )));
33085            let gt = Expression::Gt(Box::new(BinaryOp::new(pos_col, size_ref.clone())));
33086            let pos_eq_size = Expression::Eq(Box::new(BinaryOp::new(unnest_pos_col, size_ref)));
33087            let and_cond = Expression::And(Box::new(BinaryOp::new(gt, pos_eq_size)));
33088            let paren_and = Expression::Paren(Box::new(crate::expressions::Paren {
33089                this: and_cond,
33090                trailing_comments: Vec::new(),
33091            }));
33092            let or_cond = Expression::Or(Box::new(BinaryOp::new(eq, paren_and)));
33093
33094            where_conditions.push(or_cond);
33095        }
33096
33097        let where_expr = if where_conditions.len() == 1 {
33098            // Single condition: no parens needed
33099            where_conditions.into_iter().next().unwrap()
33100        } else {
33101            // Multiple conditions: wrap each OR in parens, then combine with AND
33102            let wrap = |e: Expression| {
33103                Expression::Paren(Box::new(crate::expressions::Paren {
33104                    this: e,
33105                    trailing_comments: Vec::new(),
33106                }))
33107            };
33108            let mut iter = where_conditions.into_iter();
33109            let first = wrap(iter.next().unwrap());
33110            let second = wrap(iter.next().unwrap());
33111            let mut combined = Expression::Paren(Box::new(crate::expressions::Paren {
33112                this: Expression::And(Box::new(BinaryOp::new(first, second))),
33113                trailing_comments: Vec::new(),
33114            }));
33115            for cond in iter {
33116                combined = Expression::And(Box::new(BinaryOp::new(combined, wrap(cond))));
33117            }
33118            combined
33119        };
33120
33121        // Build the new SELECT
33122        let mut new_select = select.clone();
33123        new_select.expressions = new_select_exprs;
33124
33125        if new_select.from.is_some() {
33126            let mut all_joins = vec![make_join(series_alias_expr)];
33127            all_joins.extend(joins);
33128            new_select.joins.extend(all_joins);
33129        } else {
33130            new_select.from = Some(From {
33131                expressions: vec![series_alias_expr],
33132            });
33133            new_select.joins.extend(joins);
33134        }
33135
33136        if let Some(ref existing_where) = new_select.where_clause {
33137            let combined = Expression::And(Box::new(BinaryOp::new(
33138                existing_where.this.clone(),
33139                where_expr,
33140            )));
33141            new_select.where_clause = Some(crate::expressions::Where { this: combined });
33142        } else {
33143            new_select.where_clause = Some(crate::expressions::Where { this: where_expr });
33144        }
33145
33146        Some(new_select)
33147    }
33148
33149    /// Helper to replace UNNEST(...) inside an expression with a replacement expression.
33150    fn replace_unnest_with_if(original: &Expression, replacement: &Expression) -> Expression {
33151        match original {
33152            Expression::Unnest(_) => replacement.clone(),
33153            Expression::Function(f) if f.name.eq_ignore_ascii_case("UNNEST") => replacement.clone(),
33154            Expression::Alias(a) => Self::replace_unnest_with_if(&a.this, replacement),
33155            Expression::Add(op) => {
33156                let left = Self::replace_unnest_with_if(&op.left, replacement);
33157                let right = Self::replace_unnest_with_if(&op.right, replacement);
33158                Expression::Add(Box::new(crate::expressions::BinaryOp::new(left, right)))
33159            }
33160            Expression::Sub(op) => {
33161                let left = Self::replace_unnest_with_if(&op.left, replacement);
33162                let right = Self::replace_unnest_with_if(&op.right, replacement);
33163                Expression::Sub(Box::new(crate::expressions::BinaryOp::new(left, right)))
33164            }
33165            Expression::Mul(op) => {
33166                let left = Self::replace_unnest_with_if(&op.left, replacement);
33167                let right = Self::replace_unnest_with_if(&op.right, replacement);
33168                Expression::Mul(Box::new(crate::expressions::BinaryOp::new(left, right)))
33169            }
33170            Expression::Div(op) => {
33171                let left = Self::replace_unnest_with_if(&op.left, replacement);
33172                let right = Self::replace_unnest_with_if(&op.right, replacement);
33173                Expression::Div(Box::new(crate::expressions::BinaryOp::new(left, right)))
33174            }
33175            _ => original.clone(),
33176        }
33177    }
33178
33179    /// Decompose a JSON path like `$.y[0].z` into individual parts: `["y", "0", "z"]`.
33180    /// Strips `$` prefix, handles bracket notation, quoted strings, and removes `[*]` wildcards.
33181    fn decompose_json_path(path: &str) -> Vec<String> {
33182        let mut parts = Vec::new();
33183        let path = if path.starts_with("$.") {
33184            &path[2..]
33185        } else if path.starts_with('$') {
33186            &path[1..]
33187        } else {
33188            path
33189        };
33190        if path.is_empty() {
33191            return parts;
33192        }
33193        let mut current = String::new();
33194        let chars: Vec<char> = path.chars().collect();
33195        let mut i = 0;
33196        while i < chars.len() {
33197            match chars[i] {
33198                '.' => {
33199                    if !current.is_empty() {
33200                        parts.push(current.clone());
33201                        current.clear();
33202                    }
33203                    i += 1;
33204                }
33205                '[' => {
33206                    if !current.is_empty() {
33207                        parts.push(current.clone());
33208                        current.clear();
33209                    }
33210                    i += 1;
33211                    let mut bracket_content = String::new();
33212                    while i < chars.len() && chars[i] != ']' {
33213                        if chars[i] == '"' || chars[i] == '\'' {
33214                            let quote = chars[i];
33215                            i += 1;
33216                            while i < chars.len() && chars[i] != quote {
33217                                bracket_content.push(chars[i]);
33218                                i += 1;
33219                            }
33220                            if i < chars.len() {
33221                                i += 1;
33222                            }
33223                        } else {
33224                            bracket_content.push(chars[i]);
33225                            i += 1;
33226                        }
33227                    }
33228                    if i < chars.len() {
33229                        i += 1;
33230                    }
33231                    if bracket_content != "*" {
33232                        parts.push(bracket_content);
33233                    }
33234                }
33235                _ => {
33236                    current.push(chars[i]);
33237                    i += 1;
33238                }
33239            }
33240        }
33241        if !current.is_empty() {
33242            parts.push(current);
33243        }
33244        parts
33245    }
33246
33247    /// Strip `$` prefix from a JSON path, keeping the rest.
33248    /// `$.y[0].z` -> `y[0].z`, `$["a b"]` -> `["a b"]`
33249    fn strip_json_dollar_prefix(path: &str) -> String {
33250        if path.starts_with("$.") {
33251            path[2..].to_string()
33252        } else if path.starts_with('$') {
33253            path[1..].to_string()
33254        } else {
33255            path.to_string()
33256        }
33257    }
33258
33259    /// Strip `[*]` wildcards from a JSON path.
33260    /// `$.y[*]` -> `$.y`, `$.y[*].z` -> `$.y.z`
33261    fn strip_json_wildcards(path: &str) -> String {
33262        path.replace("[*]", "")
33263            .replace("..", ".") // Clean double dots from `$.y[*].z` -> `$.y..z`
33264            .trim_end_matches('.')
33265            .to_string()
33266    }
33267
33268    /// Convert bracket notation to dot notation for JSON paths.
33269    /// `$["a b"]` -> `$."a b"`, `$["key"]` -> `$.key`
33270    fn bracket_to_dot_notation(path: &str) -> String {
33271        let mut result = String::new();
33272        let chars: Vec<char> = path.chars().collect();
33273        let mut i = 0;
33274        while i < chars.len() {
33275            if chars[i] == '[' {
33276                // Read bracket content
33277                i += 1;
33278                let mut bracket_content = String::new();
33279                let mut is_quoted = false;
33280                let mut _quote_char = '"';
33281                while i < chars.len() && chars[i] != ']' {
33282                    if chars[i] == '"' || chars[i] == '\'' {
33283                        is_quoted = true;
33284                        _quote_char = chars[i];
33285                        i += 1;
33286                        while i < chars.len() && chars[i] != _quote_char {
33287                            bracket_content.push(chars[i]);
33288                            i += 1;
33289                        }
33290                        if i < chars.len() {
33291                            i += 1;
33292                        }
33293                    } else {
33294                        bracket_content.push(chars[i]);
33295                        i += 1;
33296                    }
33297                }
33298                if i < chars.len() {
33299                    i += 1;
33300                } // skip ]
33301                if bracket_content == "*" {
33302                    // Keep wildcard as-is
33303                    result.push_str("[*]");
33304                } else if is_quoted {
33305                    // Quoted bracket -> dot notation with quotes
33306                    result.push('.');
33307                    result.push('"');
33308                    result.push_str(&bracket_content);
33309                    result.push('"');
33310                } else {
33311                    // Numeric index -> keep as bracket
33312                    result.push('[');
33313                    result.push_str(&bracket_content);
33314                    result.push(']');
33315                }
33316            } else {
33317                result.push(chars[i]);
33318                i += 1;
33319            }
33320        }
33321        result
33322    }
33323
33324    /// Convert JSON path bracket quoted strings to use single quotes instead of double quotes.
33325    /// `$["a b"]` -> `$['a b']`
33326    fn bracket_to_single_quotes(path: &str) -> String {
33327        let mut result = String::new();
33328        let chars: Vec<char> = path.chars().collect();
33329        let mut i = 0;
33330        while i < chars.len() {
33331            if chars[i] == '[' && i + 1 < chars.len() && chars[i + 1] == '"' {
33332                result.push('[');
33333                result.push('\'');
33334                i += 2; // skip [ and "
33335                while i < chars.len() && chars[i] != '"' {
33336                    result.push(chars[i]);
33337                    i += 1;
33338                }
33339                if i < chars.len() {
33340                    i += 1;
33341                } // skip closing "
33342                result.push('\'');
33343            } else {
33344                result.push(chars[i]);
33345                i += 1;
33346            }
33347        }
33348        result
33349    }
33350
33351    /// Transform TSQL SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake
33352    /// or PostgreSQL #temp -> TEMPORARY.
33353    /// Also strips # from INSERT INTO #table for non-TSQL targets.
33354    fn transform_select_into(
33355        expr: Expression,
33356        _source: DialectType,
33357        target: DialectType,
33358    ) -> Expression {
33359        use crate::expressions::{CreateTable, Expression, TableRef};
33360
33361        // Handle INSERT INTO #temp -> INSERT INTO temp for non-TSQL targets
33362        if let Expression::Insert(ref insert) = expr {
33363            if insert.table.name.name.starts_with('#')
33364                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
33365            {
33366                let mut new_insert = insert.clone();
33367                new_insert.table.name.name =
33368                    insert.table.name.name.trim_start_matches('#').to_string();
33369                return Expression::Insert(new_insert);
33370            }
33371            return expr;
33372        }
33373
33374        if let Expression::Select(ref select) = expr {
33375            if let Some(ref into) = select.into {
33376                let table_name_raw = match &into.this {
33377                    Expression::Table(tr) => tr.name.name.clone(),
33378                    Expression::Identifier(id) => id.name.clone(),
33379                    _ => String::new(),
33380                };
33381                let is_temp = table_name_raw.starts_with('#') || into.temporary;
33382                let clean_name = table_name_raw.trim_start_matches('#').to_string();
33383
33384                match target {
33385                    DialectType::DuckDB | DialectType::Snowflake => {
33386                        // SELECT INTO -> CREATE TABLE AS SELECT
33387                        let mut new_select = select.clone();
33388                        new_select.into = None;
33389                        let ct = CreateTable {
33390                            name: TableRef::new(clean_name),
33391                            on_cluster: None,
33392                            columns: Vec::new(),
33393                            constraints: Vec::new(),
33394                            if_not_exists: false,
33395                            temporary: is_temp,
33396                            or_replace: false,
33397                            table_modifier: None,
33398                            as_select: Some(Expression::Select(new_select)),
33399                            as_select_parenthesized: false,
33400                            on_commit: None,
33401                            clone_source: None,
33402                            clone_at_clause: None,
33403                            shallow_clone: false,
33404                            deep_clone: false,
33405                            is_copy: false,
33406                            leading_comments: Vec::new(),
33407                            with_properties: Vec::new(),
33408                            teradata_post_name_options: Vec::new(),
33409                            with_data: None,
33410                            with_statistics: None,
33411                            teradata_indexes: Vec::new(),
33412                            with_cte: None,
33413                            properties: Vec::new(),
33414                            partition_of: None,
33415                            post_table_properties: Vec::new(),
33416                            mysql_table_options: Vec::new(),
33417                            inherits: Vec::new(),
33418                            on_property: None,
33419                            copy_grants: false,
33420                            using_template: None,
33421                            rollup: None,
33422                            uuid: None,
33423                            with_partition_columns: Vec::new(),
33424                            with_connection: None,
33425                        };
33426                        return Expression::CreateTable(Box::new(ct));
33427                    }
33428                    DialectType::PostgreSQL | DialectType::Redshift => {
33429                        // PostgreSQL: #foo -> INTO TEMPORARY foo
33430                        if is_temp && !into.temporary {
33431                            let mut new_select = select.clone();
33432                            let mut new_into = into.clone();
33433                            new_into.temporary = true;
33434                            new_into.unlogged = false;
33435                            new_into.this = Expression::Table(Box::new(TableRef::new(clean_name)));
33436                            new_select.into = Some(new_into);
33437                            Expression::Select(new_select)
33438                        } else {
33439                            expr
33440                        }
33441                    }
33442                    _ => expr,
33443                }
33444            } else {
33445                expr
33446            }
33447        } else {
33448            expr
33449        }
33450    }
33451
33452    /// Transform CREATE TABLE WITH properties for cross-dialect transpilation.
33453    /// Handles FORMAT, PARTITIONED_BY, and other Presto WITH properties.
33454    fn transform_create_table_properties(
33455        ct: &mut crate::expressions::CreateTable,
33456        _source: DialectType,
33457        target: DialectType,
33458    ) {
33459        use crate::expressions::{
33460            BinaryOp, BooleanLiteral, Expression, FileFormatProperty, Identifier, Literal,
33461            Properties,
33462        };
33463
33464        // Helper to convert a raw property value string to the correct Expression
33465        let value_to_expr = |v: &str| -> Expression {
33466            let trimmed = v.trim();
33467            // Check if it's a quoted string (starts and ends with ')
33468            if trimmed.starts_with('\'') && trimmed.ends_with('\'') {
33469                Expression::Literal(Box::new(Literal::String(
33470                    trimmed[1..trimmed.len() - 1].to_string(),
33471                )))
33472            }
33473            // Check if it's a number
33474            else if trimmed.parse::<i64>().is_ok() || trimmed.parse::<f64>().is_ok() {
33475                Expression::Literal(Box::new(Literal::Number(trimmed.to_string())))
33476            }
33477            // Check if it's ARRAY[...] or ARRAY(...)
33478            else if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
33479                // Convert ARRAY['y'] to ARRAY('y') for Hive/Spark
33480                let inner = trimmed
33481                    .trim_start_matches(|c: char| c.is_alphabetic()) // Remove ARRAY
33482                    .trim_start_matches('[')
33483                    .trim_start_matches('(')
33484                    .trim_end_matches(']')
33485                    .trim_end_matches(')');
33486                let elements: Vec<Expression> = inner
33487                    .split(',')
33488                    .map(|e| {
33489                        let elem = e.trim().trim_matches('\'');
33490                        Expression::Literal(Box::new(Literal::String(elem.to_string())))
33491                    })
33492                    .collect();
33493                Expression::Function(Box::new(crate::expressions::Function::new(
33494                    "ARRAY".to_string(),
33495                    elements,
33496                )))
33497            }
33498            // Otherwise, just output as identifier (unquoted)
33499            else {
33500                Expression::Identifier(Identifier::new(trimmed.to_string()))
33501            }
33502        };
33503
33504        if ct.with_properties.is_empty() && ct.properties.is_empty() {
33505            return;
33506        }
33507
33508        // Handle Presto-style WITH properties
33509        if !ct.with_properties.is_empty() {
33510            // Extract FORMAT property and remaining properties
33511            let mut format_value: Option<String> = None;
33512            let mut partitioned_by: Option<String> = None;
33513            let mut other_props: Vec<(String, String)> = Vec::new();
33514
33515            for (key, value) in ct.with_properties.drain(..) {
33516                if key.eq_ignore_ascii_case("FORMAT") {
33517                    // Strip surrounding quotes from value if present
33518                    format_value = Some(value.trim_matches('\'').to_string());
33519                } else if key.eq_ignore_ascii_case("PARTITIONED_BY") {
33520                    partitioned_by = Some(value);
33521                } else {
33522                    other_props.push((key, value));
33523                }
33524            }
33525
33526            match target {
33527                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
33528                    // Presto: keep WITH properties but lowercase 'format' key
33529                    if let Some(fmt) = format_value {
33530                        ct.with_properties
33531                            .push(("format".to_string(), format!("'{}'", fmt)));
33532                    }
33533                    if let Some(part) = partitioned_by {
33534                        // Convert (col1, col2) to ARRAY['col1', 'col2'] format
33535                        let trimmed = part.trim();
33536                        let inner = trimmed.trim_start_matches('(').trim_end_matches(')');
33537                        // Also handle ARRAY['...'] format - keep as-is
33538                        if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
33539                            ct.with_properties
33540                                .push(("PARTITIONED_BY".to_string(), part));
33541                        } else {
33542                            // Parse column names from the parenthesized list
33543                            let cols: Vec<&str> = inner
33544                                .split(',')
33545                                .map(|c| c.trim().trim_matches('"').trim_matches('\''))
33546                                .collect();
33547                            let array_val = format!(
33548                                "ARRAY[{}]",
33549                                cols.iter()
33550                                    .map(|c| format!("'{}'", c))
33551                                    .collect::<Vec<_>>()
33552                                    .join(", ")
33553                            );
33554                            ct.with_properties
33555                                .push(("PARTITIONED_BY".to_string(), array_val));
33556                        }
33557                    }
33558                    ct.with_properties.extend(other_props);
33559                }
33560                DialectType::Hive => {
33561                    // Hive: FORMAT -> STORED AS, other props -> TBLPROPERTIES
33562                    if let Some(fmt) = format_value {
33563                        ct.properties.push(Expression::FileFormatProperty(Box::new(
33564                            FileFormatProperty {
33565                                this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
33566                                expressions: vec![],
33567                                hive_format: Some(Box::new(Expression::Boolean(BooleanLiteral {
33568                                    value: true,
33569                                }))),
33570                            },
33571                        )));
33572                    }
33573                    if let Some(_part) = partitioned_by {
33574                        // PARTITIONED_BY handling is complex - move columns to partitioned by
33575                        // For now, the partition columns are extracted from the column list
33576                        Self::apply_partitioned_by(ct, &_part, target);
33577                    }
33578                    if !other_props.is_empty() {
33579                        let eq_exprs: Vec<Expression> = other_props
33580                            .into_iter()
33581                            .map(|(k, v)| {
33582                                Expression::Eq(Box::new(BinaryOp::new(
33583                                    Expression::Literal(Box::new(Literal::String(k))),
33584                                    value_to_expr(&v),
33585                                )))
33586                            })
33587                            .collect();
33588                        ct.properties
33589                            .push(Expression::Properties(Box::new(Properties {
33590                                expressions: eq_exprs,
33591                            })));
33592                    }
33593                }
33594                DialectType::Spark | DialectType::Databricks => {
33595                    // Spark: FORMAT -> USING, other props -> TBLPROPERTIES
33596                    if let Some(fmt) = format_value {
33597                        ct.properties.push(Expression::FileFormatProperty(Box::new(
33598                            FileFormatProperty {
33599                                this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
33600                                expressions: vec![],
33601                                hive_format: None, // None means USING syntax
33602                            },
33603                        )));
33604                    }
33605                    if let Some(_part) = partitioned_by {
33606                        Self::apply_partitioned_by(ct, &_part, target);
33607                    }
33608                    if !other_props.is_empty() {
33609                        let eq_exprs: Vec<Expression> = other_props
33610                            .into_iter()
33611                            .map(|(k, v)| {
33612                                Expression::Eq(Box::new(BinaryOp::new(
33613                                    Expression::Literal(Box::new(Literal::String(k))),
33614                                    value_to_expr(&v),
33615                                )))
33616                            })
33617                            .collect();
33618                        ct.properties
33619                            .push(Expression::Properties(Box::new(Properties {
33620                                expressions: eq_exprs,
33621                            })));
33622                    }
33623                }
33624                DialectType::DuckDB => {
33625                    // DuckDB: strip all WITH properties (FORMAT, PARTITIONED_BY, etc.)
33626                    // Keep nothing
33627                }
33628                _ => {
33629                    // For other dialects, keep WITH properties as-is
33630                    if let Some(fmt) = format_value {
33631                        ct.with_properties
33632                            .push(("FORMAT".to_string(), format!("'{}'", fmt)));
33633                    }
33634                    if let Some(part) = partitioned_by {
33635                        ct.with_properties
33636                            .push(("PARTITIONED_BY".to_string(), part));
33637                    }
33638                    ct.with_properties.extend(other_props);
33639                }
33640            }
33641        }
33642
33643        // Handle STORED AS 'PARQUET' (quoted format name) -> STORED AS PARQUET (unquoted)
33644        // and Hive STORED AS -> Presto WITH (format=...) conversion
33645        if !ct.properties.is_empty() {
33646            let is_presto_target = matches!(
33647                target,
33648                DialectType::Presto | DialectType::Trino | DialectType::Athena
33649            );
33650            let is_duckdb_target = matches!(target, DialectType::DuckDB);
33651
33652            if is_presto_target || is_duckdb_target {
33653                let mut new_properties = Vec::new();
33654                for prop in ct.properties.drain(..) {
33655                    match &prop {
33656                        Expression::FileFormatProperty(ffp) => {
33657                            if is_presto_target {
33658                                // Convert STORED AS/USING to WITH (format=...)
33659                                if let Some(ref fmt_expr) = ffp.this {
33660                                    let fmt_str = match fmt_expr.as_ref() {
33661                                        Expression::Identifier(id) => id.name.clone(),
33662                                        Expression::Literal(lit)
33663                                            if matches!(lit.as_ref(), Literal::String(_)) =>
33664                                        {
33665                                            let Literal::String(s) = lit.as_ref() else {
33666                                                unreachable!()
33667                                            };
33668                                            s.clone()
33669                                        }
33670                                        _ => {
33671                                            new_properties.push(prop);
33672                                            continue;
33673                                        }
33674                                    };
33675                                    ct.with_properties
33676                                        .push(("format".to_string(), format!("'{}'", fmt_str)));
33677                                }
33678                            }
33679                            // DuckDB: just strip file format properties
33680                        }
33681                        // Convert TBLPROPERTIES to WITH properties for Presto target
33682                        Expression::Properties(props) if is_presto_target => {
33683                            for expr in &props.expressions {
33684                                if let Expression::Eq(eq) = expr {
33685                                    // Extract key and value from the Eq expression
33686                                    let key = match &eq.left {
33687                                        Expression::Literal(lit)
33688                                            if matches!(lit.as_ref(), Literal::String(_)) =>
33689                                        {
33690                                            let Literal::String(s) = lit.as_ref() else {
33691                                                unreachable!()
33692                                            };
33693                                            s.clone()
33694                                        }
33695                                        Expression::Identifier(id) => id.name.clone(),
33696                                        _ => continue,
33697                                    };
33698                                    let value = match &eq.right {
33699                                        Expression::Literal(lit)
33700                                            if matches!(lit.as_ref(), Literal::String(_)) =>
33701                                        {
33702                                            let Literal::String(s) = lit.as_ref() else {
33703                                                unreachable!()
33704                                            };
33705                                            format!("'{}'", s)
33706                                        }
33707                                        Expression::Literal(lit)
33708                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
33709                                        {
33710                                            let Literal::Number(n) = lit.as_ref() else {
33711                                                unreachable!()
33712                                            };
33713                                            n.clone()
33714                                        }
33715                                        Expression::Identifier(id) => id.name.clone(),
33716                                        _ => continue,
33717                                    };
33718                                    ct.with_properties.push((key, value));
33719                                }
33720                            }
33721                        }
33722                        // Convert PartitionedByProperty for Presto target
33723                        Expression::PartitionedByProperty(ref pbp) if is_presto_target => {
33724                            // Check if it contains ColumnDef expressions (Hive-style with types)
33725                            if let Expression::Tuple(ref tuple) = *pbp.this {
33726                                let mut col_names: Vec<String> = Vec::new();
33727                                let mut col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
33728                                let mut has_col_defs = false;
33729                                for expr in &tuple.expressions {
33730                                    if let Expression::ColumnDef(ref cd) = expr {
33731                                        has_col_defs = true;
33732                                        col_names.push(cd.name.name.clone());
33733                                        col_defs.push(*cd.clone());
33734                                    } else if let Expression::Column(ref col) = expr {
33735                                        col_names.push(col.name.name.clone());
33736                                    } else if let Expression::Identifier(ref id) = expr {
33737                                        col_names.push(id.name.clone());
33738                                    } else {
33739                                        // For function expressions like MONTHS(y), serialize to SQL
33740                                        let generic = Dialect::get(DialectType::Generic);
33741                                        if let Ok(sql) = generic.generate(expr) {
33742                                            col_names.push(sql);
33743                                        }
33744                                    }
33745                                }
33746                                if has_col_defs {
33747                                    // Merge partition column defs into the main column list
33748                                    for cd in col_defs {
33749                                        ct.columns.push(cd);
33750                                    }
33751                                }
33752                                if !col_names.is_empty() {
33753                                    // Add PARTITIONED_BY property
33754                                    let array_val = format!(
33755                                        "ARRAY[{}]",
33756                                        col_names
33757                                            .iter()
33758                                            .map(|n| format!("'{}'", n))
33759                                            .collect::<Vec<_>>()
33760                                            .join(", ")
33761                                    );
33762                                    ct.with_properties
33763                                        .push(("PARTITIONED_BY".to_string(), array_val));
33764                                }
33765                            }
33766                            // Skip - don't keep in properties
33767                        }
33768                        _ => {
33769                            if !is_duckdb_target {
33770                                new_properties.push(prop);
33771                            }
33772                        }
33773                    }
33774                }
33775                ct.properties = new_properties;
33776            } else {
33777                // For Hive/Spark targets, unquote format names in STORED AS
33778                for prop in &mut ct.properties {
33779                    if let Expression::FileFormatProperty(ref mut ffp) = prop {
33780                        if let Some(ref mut fmt_expr) = ffp.this {
33781                            if let Expression::Literal(lit) = fmt_expr.as_ref() {
33782                                if let Literal::String(s) = lit.as_ref() {
33783                                    // Convert STORED AS 'PARQUET' to STORED AS PARQUET (unquote)
33784                                    let unquoted = s.clone();
33785                                    *fmt_expr =
33786                                        Box::new(Expression::Identifier(Identifier::new(unquoted)));
33787                                }
33788                            }
33789                        }
33790                    }
33791                }
33792            }
33793        }
33794    }
33795
33796    /// Apply PARTITIONED_BY conversion: move partition columns from column list to PARTITIONED BY
33797    fn apply_partitioned_by(
33798        ct: &mut crate::expressions::CreateTable,
33799        partitioned_by_value: &str,
33800        target: DialectType,
33801    ) {
33802        use crate::expressions::{Column, Expression, Identifier, PartitionedByProperty, Tuple};
33803
33804        // Parse the ARRAY['col1', 'col2'] value to extract column names
33805        let mut col_names: Vec<String> = Vec::new();
33806        // The value looks like ARRAY['y', 'z'] or ARRAY('y', 'z')
33807        let inner = partitioned_by_value
33808            .trim()
33809            .trim_start_matches("ARRAY")
33810            .trim_start_matches('[')
33811            .trim_start_matches('(')
33812            .trim_end_matches(']')
33813            .trim_end_matches(')');
33814        for part in inner.split(',') {
33815            let col = part.trim().trim_matches('\'').trim_matches('"');
33816            if !col.is_empty() {
33817                col_names.push(col.to_string());
33818            }
33819        }
33820
33821        if col_names.is_empty() {
33822            return;
33823        }
33824
33825        if matches!(target, DialectType::Hive) {
33826            // Hive: PARTITIONED BY (col_name type, ...) - move columns out of column list
33827            let mut partition_col_defs = Vec::new();
33828            for col_name in &col_names {
33829                // Find and remove from columns
33830                if let Some(pos) = ct
33831                    .columns
33832                    .iter()
33833                    .position(|c| c.name.name.eq_ignore_ascii_case(col_name))
33834                {
33835                    let col_def = ct.columns.remove(pos);
33836                    partition_col_defs.push(Expression::ColumnDef(Box::new(col_def)));
33837                }
33838            }
33839            if !partition_col_defs.is_empty() {
33840                ct.properties
33841                    .push(Expression::PartitionedByProperty(Box::new(
33842                        PartitionedByProperty {
33843                            this: Box::new(Expression::Tuple(Box::new(Tuple {
33844                                expressions: partition_col_defs,
33845                            }))),
33846                        },
33847                    )));
33848            }
33849        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
33850            // Spark: PARTITIONED BY (col1, col2) - just column names, keep in column list
33851            // Use quoted identifiers to match the quoting style of the original column definitions
33852            let partition_exprs: Vec<Expression> = col_names
33853                .iter()
33854                .map(|name| {
33855                    // Check if the column exists in the column list and use its quoting
33856                    let is_quoted = ct
33857                        .columns
33858                        .iter()
33859                        .any(|c| c.name.name.eq_ignore_ascii_case(name) && c.name.quoted);
33860                    let ident = if is_quoted {
33861                        Identifier::quoted(name.clone())
33862                    } else {
33863                        Identifier::new(name.clone())
33864                    };
33865                    Expression::boxed_column(Column {
33866                        name: ident,
33867                        table: None,
33868                        join_mark: false,
33869                        trailing_comments: Vec::new(),
33870                        span: None,
33871                        inferred_type: None,
33872                    })
33873                })
33874                .collect();
33875            ct.properties
33876                .push(Expression::PartitionedByProperty(Box::new(
33877                    PartitionedByProperty {
33878                        this: Box::new(Expression::Tuple(Box::new(Tuple {
33879                            expressions: partition_exprs,
33880                        }))),
33881                    },
33882                )));
33883        }
33884        // DuckDB: strip partitioned_by entirely (already handled)
33885    }
33886
33887    /// Convert a DataType to Spark's type string format (using angle brackets)
33888    fn data_type_to_spark_string(dt: &crate::expressions::DataType) -> String {
33889        use crate::expressions::DataType;
33890        match dt {
33891            DataType::Int { .. } => "INT".to_string(),
33892            DataType::BigInt { .. } => "BIGINT".to_string(),
33893            DataType::SmallInt { .. } => "SMALLINT".to_string(),
33894            DataType::TinyInt { .. } => "TINYINT".to_string(),
33895            DataType::Float { .. } => "FLOAT".to_string(),
33896            DataType::Double { .. } => "DOUBLE".to_string(),
33897            DataType::Decimal {
33898                precision: Some(p),
33899                scale: Some(s),
33900            } => format!("DECIMAL({}, {})", p, s),
33901            DataType::Decimal {
33902                precision: Some(p), ..
33903            } => format!("DECIMAL({})", p),
33904            DataType::Decimal { .. } => "DECIMAL".to_string(),
33905            DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
33906                "STRING".to_string()
33907            }
33908            DataType::Char { .. } => "STRING".to_string(),
33909            DataType::Boolean => "BOOLEAN".to_string(),
33910            DataType::Date => "DATE".to_string(),
33911            DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
33912            DataType::Json | DataType::JsonB => "STRING".to_string(),
33913            DataType::Binary { .. } => "BINARY".to_string(),
33914            DataType::Array { element_type, .. } => {
33915                format!("ARRAY<{}>", Self::data_type_to_spark_string(element_type))
33916            }
33917            DataType::Map {
33918                key_type,
33919                value_type,
33920            } => format!(
33921                "MAP<{}, {}>",
33922                Self::data_type_to_spark_string(key_type),
33923                Self::data_type_to_spark_string(value_type)
33924            ),
33925            DataType::Struct { fields, .. } => {
33926                let field_strs: Vec<String> = fields
33927                    .iter()
33928                    .map(|f| {
33929                        if f.name.is_empty() {
33930                            Self::data_type_to_spark_string(&f.data_type)
33931                        } else {
33932                            format!(
33933                                "{}: {}",
33934                                f.name,
33935                                Self::data_type_to_spark_string(&f.data_type)
33936                            )
33937                        }
33938                    })
33939                    .collect();
33940                format!("STRUCT<{}>", field_strs.join(", "))
33941            }
33942            DataType::Custom { name } => name.clone(),
33943            _ => format!("{:?}", dt),
33944        }
33945    }
33946
33947    /// Extract value and unit from an Interval expression
33948    /// Returns (value_expression, IntervalUnit)
33949    fn extract_interval_parts(
33950        interval_expr: &Expression,
33951    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
33952        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
33953
33954        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
33955            match unit.trim().to_ascii_uppercase().as_str() {
33956                "YEAR" | "YEARS" => Some(IntervalUnit::Year),
33957                "QUARTER" | "QUARTERS" => Some(IntervalUnit::Quarter),
33958                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
33959                "WEEK" | "WEEKS" | "ISOWEEK" => Some(IntervalUnit::Week),
33960                "DAY" | "DAYS" => Some(IntervalUnit::Day),
33961                "HOUR" | "HOURS" => Some(IntervalUnit::Hour),
33962                "MINUTE" | "MINUTES" => Some(IntervalUnit::Minute),
33963                "SECOND" | "SECONDS" => Some(IntervalUnit::Second),
33964                "MILLISECOND" | "MILLISECONDS" => Some(IntervalUnit::Millisecond),
33965                "MICROSECOND" | "MICROSECONDS" => Some(IntervalUnit::Microsecond),
33966                "NANOSECOND" | "NANOSECONDS" => Some(IntervalUnit::Nanosecond),
33967                _ => None,
33968            }
33969        }
33970
33971        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
33972            let mut parts = s.split_whitespace();
33973            let value = parts.next()?;
33974            let unit = unit_from_str(parts.next()?)?;
33975            Some((
33976                Expression::Literal(Box::new(Literal::String(value.to_string()))),
33977                unit,
33978            ))
33979        }
33980
33981        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
33982            match unit {
33983                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
33984                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
33985                    Expression::Day(_) => Some(IntervalUnit::Day),
33986                    Expression::Month(_) => Some(IntervalUnit::Month),
33987                    Expression::Year(_) => Some(IntervalUnit::Year),
33988                    Expression::Identifier(id) => unit_from_str(&id.name),
33989                    Expression::Var(v) => unit_from_str(&v.this),
33990                    Expression::Column(col) => unit_from_str(&col.name.name),
33991                    _ => None,
33992                },
33993                _ => None,
33994            }
33995        }
33996
33997        match interval_expr {
33998            Expression::Interval(iv) => {
33999                let val = iv.this.clone().unwrap_or(Expression::number(0));
34000                if let Expression::Literal(lit) = &val {
34001                    if let Literal::String(s) = lit.as_ref() {
34002                        if let Some(parts) = parts_from_literal_string(s) {
34003                            return Some(parts);
34004                        }
34005                    }
34006                }
34007                let unit = iv
34008                    .unit
34009                    .as_ref()
34010                    .and_then(unit_from_spec)
34011                    .unwrap_or(IntervalUnit::Day);
34012                Some((val, unit))
34013            }
34014            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
34015                if let Expression::Literal(lit) = &cast.this {
34016                    if let Literal::String(s) = lit.as_ref() {
34017                        if let Some(parts) = parts_from_literal_string(s) {
34018                            return Some(parts);
34019                        }
34020                    }
34021                }
34022                let unit = match &cast.to {
34023                    DataType::Interval {
34024                        unit: Some(unit), ..
34025                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
34026                    _ => IntervalUnit::Day,
34027                };
34028                Some((cast.this.clone(), unit))
34029            }
34030            _ => None,
34031        }
34032    }
34033
34034    fn rewrite_tsql_interval_arithmetic(expr: &Expression) -> Option<Expression> {
34035        match expr {
34036            Expression::Add(op) => {
34037                Self::extract_interval_parts(&op.right)?;
34038                Some(Self::build_tsql_dateadd_from_interval(
34039                    op.left.clone(),
34040                    &op.right,
34041                    false,
34042                ))
34043            }
34044            Expression::Sub(op) => {
34045                Self::extract_interval_parts(&op.right)?;
34046                Some(Self::build_tsql_dateadd_from_interval(
34047                    op.left.clone(),
34048                    &op.right,
34049                    true,
34050                ))
34051            }
34052            _ => None,
34053        }
34054    }
34055
34056    fn build_tsql_dateadd_from_interval(
34057        date: Expression,
34058        interval: &Expression,
34059        subtract: bool,
34060    ) -> Expression {
34061        let (value, unit) = Self::extract_interval_parts(interval)
34062            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
34063        let unit = Self::interval_unit_to_string(&unit);
34064        let amount = Self::tsql_dateadd_amount(value, subtract);
34065
34066        Expression::Function(Box::new(Function::new(
34067            "DATEADD".to_string(),
34068            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
34069        )))
34070    }
34071
34072    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
34073        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
34074
34075        fn numeric_literal_value(value: &Expression) -> Option<&str> {
34076            match value {
34077                Expression::Literal(lit) => match lit.as_ref() {
34078                    crate::expressions::Literal::Number(n)
34079                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
34080                    _ => None,
34081                },
34082                _ => None,
34083            }
34084        }
34085
34086        fn colon_parameter(value: &Expression) -> Option<Expression> {
34087            let Expression::Literal(lit) = value else {
34088                return None;
34089            };
34090            let crate::expressions::Literal::String(s) = lit.as_ref() else {
34091                return None;
34092            };
34093            let name = s.strip_prefix(':')?;
34094            if name.is_empty()
34095                || !name
34096                    .chars()
34097                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
34098            {
34099                return None;
34100            }
34101
34102            Some(Expression::Parameter(Box::new(Parameter {
34103                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
34104                    None
34105                } else {
34106                    Some(name.to_string())
34107                },
34108                index: name.parse::<u32>().ok(),
34109                style: ParameterStyle::Colon,
34110                quoted: false,
34111                string_quoted: false,
34112                expression: None,
34113            })))
34114        }
34115
34116        let value = colon_parameter(&value).unwrap_or(value);
34117
34118        if let Some(n) = numeric_literal_value(&value) {
34119            if let Ok(parsed) = n.parse::<f64>() {
34120                let normalized = if negate { -parsed } else { parsed };
34121                let rendered = if normalized.fract() == 0.0 {
34122                    format!("{}", normalized as i64)
34123                } else {
34124                    normalized.to_string()
34125                };
34126                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
34127                    rendered,
34128                )));
34129            }
34130        }
34131
34132        if !negate {
34133            return value;
34134        }
34135
34136        match value {
34137            Expression::Neg(op) => op.this,
34138            other => Expression::Neg(Box::new(UnaryOp {
34139                this: other,
34140                inferred_type: None,
34141            })),
34142        }
34143    }
34144
34145    /// Normalize BigQuery-specific functions to standard forms that target dialects can handle
34146    fn normalize_bigquery_function(
34147        e: Expression,
34148        source: DialectType,
34149        target: DialectType,
34150    ) -> Result<Expression> {
34151        use crate::expressions::{BinaryOp, Cast, DataType, Function, Identifier, Literal, Paren};
34152
34153        let f = if let Expression::Function(f) = e {
34154            *f
34155        } else {
34156            return Ok(e);
34157        };
34158        let name = f.name.to_ascii_uppercase();
34159        let mut args = f.args;
34160
34161        /// Helper to extract unit string from an identifier, column, or literal expression
34162        fn get_unit_str(expr: &Expression) -> String {
34163            match expr {
34164                Expression::Identifier(id) => id.name.to_ascii_uppercase(),
34165                Expression::Var(v) => v.this.to_ascii_uppercase(),
34166                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
34167                    let Literal::String(s) = lit.as_ref() else {
34168                        unreachable!()
34169                    };
34170                    s.to_ascii_uppercase()
34171                }
34172                Expression::Column(col) => col.name.name.to_ascii_uppercase(),
34173                // Handle WEEK(MONDAY), WEEK(SUNDAY) etc. which are parsed as Function("WEEK", [Column("MONDAY")])
34174                Expression::Function(f) => {
34175                    let base = f.name.to_ascii_uppercase();
34176                    if !f.args.is_empty() {
34177                        // e.g., WEEK(MONDAY) -> "WEEK(MONDAY)"
34178                        let inner = get_unit_str(&f.args[0]);
34179                        format!("{}({})", base, inner)
34180                    } else {
34181                        base
34182                    }
34183                }
34184                _ => "DAY".to_string(),
34185            }
34186        }
34187
34188        /// Parse unit string to IntervalUnit
34189        fn parse_interval_unit(s: &str) -> crate::expressions::IntervalUnit {
34190            match s {
34191                "YEAR" => crate::expressions::IntervalUnit::Year,
34192                "QUARTER" => crate::expressions::IntervalUnit::Quarter,
34193                "MONTH" => crate::expressions::IntervalUnit::Month,
34194                "WEEK" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
34195                "DAY" => crate::expressions::IntervalUnit::Day,
34196                "HOUR" => crate::expressions::IntervalUnit::Hour,
34197                "MINUTE" => crate::expressions::IntervalUnit::Minute,
34198                "SECOND" => crate::expressions::IntervalUnit::Second,
34199                "MILLISECOND" => crate::expressions::IntervalUnit::Millisecond,
34200                "MICROSECOND" => crate::expressions::IntervalUnit::Microsecond,
34201                _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
34202                _ => crate::expressions::IntervalUnit::Day,
34203            }
34204        }
34205
34206        match name.as_str() {
34207            // TIMESTAMP_DIFF(date1, date2, unit) -> TIMESTAMPDIFF(unit, date2, date1)
34208            // (BigQuery: result = date1 - date2, Standard: result = end - start)
34209            "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF" if args.len() == 3 => {
34210                let date1 = args.remove(0);
34211                let date2 = args.remove(0);
34212                let unit_expr = args.remove(0);
34213                let unit_str = get_unit_str(&unit_expr);
34214
34215                if matches!(target, DialectType::BigQuery) {
34216                    // BigQuery -> BigQuery: just uppercase the unit
34217                    let unit = Expression::Identifier(Identifier::new(unit_str.clone()));
34218                    return Ok(Expression::Function(Box::new(Function::new(
34219                        f.name,
34220                        vec![date1, date2, unit],
34221                    ))));
34222                }
34223
34224                // For Snowflake: use TimestampDiff expression so it generates TIMESTAMPDIFF
34225                // (Function("TIMESTAMPDIFF") would be converted to DATEDIFF by Snowflake's function normalization)
34226                if matches!(target, DialectType::Snowflake) {
34227                    return Ok(Expression::TimestampDiff(Box::new(
34228                        crate::expressions::TimestampDiff {
34229                            this: Box::new(date2),
34230                            expression: Box::new(date1),
34231                            unit: Some(unit_str),
34232                        },
34233                    )));
34234                }
34235
34236                // For DuckDB: DATE_DIFF('UNIT', start, end) with proper CAST
34237                if matches!(target, DialectType::DuckDB) {
34238                    let (cast_d1, cast_d2) = if name == "TIME_DIFF" {
34239                        // CAST to TIME
34240                        let cast_fn = |e: Expression| -> Expression {
34241                            match e {
34242                                Expression::Literal(lit)
34243                                    if matches!(lit.as_ref(), Literal::String(_)) =>
34244                                {
34245                                    let Literal::String(s) = lit.as_ref() else {
34246                                        unreachable!()
34247                                    };
34248                                    Expression::Cast(Box::new(Cast {
34249                                        this: Expression::Literal(Box::new(Literal::String(
34250                                            s.clone(),
34251                                        ))),
34252                                        to: DataType::Custom {
34253                                            name: "TIME".to_string(),
34254                                        },
34255                                        trailing_comments: vec![],
34256                                        double_colon_syntax: false,
34257                                        format: None,
34258                                        default: None,
34259                                        inferred_type: None,
34260                                    }))
34261                                }
34262                                other => other,
34263                            }
34264                        };
34265                        (cast_fn(date1), cast_fn(date2))
34266                    } else if name == "DATETIME_DIFF" {
34267                        // CAST to TIMESTAMP
34268                        (
34269                            Self::ensure_cast_timestamp(date1),
34270                            Self::ensure_cast_timestamp(date2),
34271                        )
34272                    } else {
34273                        // TIMESTAMP_DIFF: CAST to TIMESTAMPTZ
34274                        (
34275                            Self::ensure_cast_timestamptz(date1),
34276                            Self::ensure_cast_timestamptz(date2),
34277                        )
34278                    };
34279                    return Ok(Expression::Function(Box::new(Function::new(
34280                        "DATE_DIFF".to_string(),
34281                        vec![
34282                            Expression::Literal(Box::new(Literal::String(unit_str))),
34283                            cast_d2,
34284                            cast_d1,
34285                        ],
34286                    ))));
34287                }
34288
34289                // Convert to standard TIMESTAMPDIFF(unit, start, end)
34290                let unit = Expression::Identifier(Identifier::new(unit_str));
34291                Ok(Expression::Function(Box::new(Function::new(
34292                    "TIMESTAMPDIFF".to_string(),
34293                    vec![unit, date2, date1],
34294                ))))
34295            }
34296
34297            // DATEDIFF(unit, start, end) -> target-specific form
34298            // Used by: Redshift, Snowflake, TSQL, Databricks, Spark
34299            "DATEDIFF" if args.len() == 3 => {
34300                let arg0 = args.remove(0);
34301                let arg1 = args.remove(0);
34302                let arg2 = args.remove(0);
34303                let unit_str = get_unit_str(&arg0);
34304
34305                // Redshift DATEDIFF(unit, start, end) order: result = end - start
34306                // Snowflake DATEDIFF(unit, start, end) order: result = end - start
34307                // TSQL DATEDIFF(unit, start, end) order: result = end - start
34308
34309                if matches!(target, DialectType::Snowflake) {
34310                    // Snowflake: DATEDIFF(UNIT, start, end) - uppercase unit
34311                    let unit = Expression::Identifier(Identifier::new(unit_str));
34312                    return Ok(Expression::Function(Box::new(Function::new(
34313                        "DATEDIFF".to_string(),
34314                        vec![unit, arg1, arg2],
34315                    ))));
34316                }
34317
34318                if matches!(target, DialectType::DuckDB) {
34319                    // DuckDB: DATE_DIFF('UNIT', start, end) with CAST
34320                    let cast_d1 = Self::ensure_cast_timestamp(arg1);
34321                    let cast_d2 = Self::ensure_cast_timestamp(arg2);
34322                    return Ok(Expression::Function(Box::new(Function::new(
34323                        "DATE_DIFF".to_string(),
34324                        vec![
34325                            Expression::Literal(Box::new(Literal::String(unit_str))),
34326                            cast_d1,
34327                            cast_d2,
34328                        ],
34329                    ))));
34330                }
34331
34332                if matches!(target, DialectType::BigQuery) {
34333                    // BigQuery: DATE_DIFF(end_date, start_date, UNIT) - reversed args, CAST to DATETIME
34334                    let cast_d1 = Self::ensure_cast_datetime(arg1);
34335                    let cast_d2 = Self::ensure_cast_datetime(arg2);
34336                    let unit = Expression::Identifier(Identifier::new(unit_str));
34337                    return Ok(Expression::Function(Box::new(Function::new(
34338                        "DATE_DIFF".to_string(),
34339                        vec![cast_d2, cast_d1, unit],
34340                    ))));
34341                }
34342
34343                if matches!(target, DialectType::Spark | DialectType::Databricks) {
34344                    // Spark/Databricks: DATEDIFF(UNIT, start, end) - uppercase unit
34345                    let unit = Expression::Identifier(Identifier::new(unit_str));
34346                    return Ok(Expression::Function(Box::new(Function::new(
34347                        "DATEDIFF".to_string(),
34348                        vec![unit, arg1, arg2],
34349                    ))));
34350                }
34351
34352                if matches!(target, DialectType::Hive) {
34353                    // Hive: DATEDIFF(end, start) for DAY only, use MONTHS_BETWEEN for MONTH
34354                    match unit_str.as_str() {
34355                        "MONTH" => {
34356                            return Ok(Expression::Function(Box::new(Function::new(
34357                                "CAST".to_string(),
34358                                vec![Expression::Function(Box::new(Function::new(
34359                                    "MONTHS_BETWEEN".to_string(),
34360                                    vec![arg2, arg1],
34361                                )))],
34362                            ))));
34363                        }
34364                        "WEEK" => {
34365                            return Ok(Expression::Cast(Box::new(Cast {
34366                                this: Expression::Div(Box::new(crate::expressions::BinaryOp::new(
34367                                    Expression::Function(Box::new(Function::new(
34368                                        "DATEDIFF".to_string(),
34369                                        vec![arg2, arg1],
34370                                    ))),
34371                                    Expression::Literal(Box::new(Literal::Number("7".to_string()))),
34372                                ))),
34373                                to: DataType::Int {
34374                                    length: None,
34375                                    integer_spelling: false,
34376                                },
34377                                trailing_comments: vec![],
34378                                double_colon_syntax: false,
34379                                format: None,
34380                                default: None,
34381                                inferred_type: None,
34382                            })));
34383                        }
34384                        _ => {
34385                            // Default: DATEDIFF(end, start) for DAY
34386                            return Ok(Expression::Function(Box::new(Function::new(
34387                                "DATEDIFF".to_string(),
34388                                vec![arg2, arg1],
34389                            ))));
34390                        }
34391                    }
34392                }
34393
34394                if matches!(
34395                    target,
34396                    DialectType::Presto | DialectType::Trino | DialectType::Athena
34397                ) {
34398                    // Presto/Trino: DATE_DIFF('UNIT', start, end)
34399                    return Ok(Expression::Function(Box::new(Function::new(
34400                        "DATE_DIFF".to_string(),
34401                        vec![
34402                            Expression::Literal(Box::new(Literal::String(unit_str))),
34403                            arg1,
34404                            arg2,
34405                        ],
34406                    ))));
34407                }
34408
34409                if matches!(target, DialectType::TSQL) {
34410                    // TSQL: DATEDIFF(UNIT, start, CAST(end AS DATETIME2))
34411                    let cast_d2 = Self::ensure_cast_datetime2(arg2);
34412                    let unit = Expression::Identifier(Identifier::new(unit_str));
34413                    return Ok(Expression::Function(Box::new(Function::new(
34414                        "DATEDIFF".to_string(),
34415                        vec![unit, arg1, cast_d2],
34416                    ))));
34417                }
34418
34419                if matches!(target, DialectType::PostgreSQL) {
34420                    // PostgreSQL doesn't have DATEDIFF - use date subtraction or EXTRACT
34421                    // For now, use DATEDIFF (passthrough) with uppercased unit
34422                    let unit = Expression::Identifier(Identifier::new(unit_str));
34423                    return Ok(Expression::Function(Box::new(Function::new(
34424                        "DATEDIFF".to_string(),
34425                        vec![unit, arg1, arg2],
34426                    ))));
34427                }
34428
34429                // Default: DATEDIFF(UNIT, start, end) with uppercase unit
34430                let unit = Expression::Identifier(Identifier::new(unit_str));
34431                Ok(Expression::Function(Box::new(Function::new(
34432                    "DATEDIFF".to_string(),
34433                    vec![unit, arg1, arg2],
34434                ))))
34435            }
34436
34437            // DATE_DIFF(date1, date2, unit) -> standard form
34438            "DATE_DIFF" if args.len() == 3 => {
34439                let date1 = args.remove(0);
34440                let date2 = args.remove(0);
34441                let unit_expr = args.remove(0);
34442                let unit_str = get_unit_str(&unit_expr);
34443
34444                if matches!(target, DialectType::BigQuery) {
34445                    // BigQuery -> BigQuery: just uppercase the unit, normalize WEEK(SUNDAY) -> WEEK
34446                    let norm_unit = if unit_str == "WEEK(SUNDAY)" {
34447                        "WEEK".to_string()
34448                    } else {
34449                        unit_str
34450                    };
34451                    let norm_d1 = Self::date_literal_to_cast(date1);
34452                    let norm_d2 = Self::date_literal_to_cast(date2);
34453                    let unit = Expression::Identifier(Identifier::new(norm_unit));
34454                    return Ok(Expression::Function(Box::new(Function::new(
34455                        f.name,
34456                        vec![norm_d1, norm_d2, unit],
34457                    ))));
34458                }
34459
34460                if matches!(target, DialectType::MySQL) {
34461                    // MySQL DATEDIFF only takes 2 args (date1, date2), returns day difference
34462                    let norm_d1 = Self::date_literal_to_cast(date1);
34463                    let norm_d2 = Self::date_literal_to_cast(date2);
34464                    return Ok(Expression::Function(Box::new(Function::new(
34465                        "DATEDIFF".to_string(),
34466                        vec![norm_d1, norm_d2],
34467                    ))));
34468                }
34469
34470                if matches!(target, DialectType::StarRocks) {
34471                    // StarRocks: DATE_DIFF('UNIT', date1, date2) - unit as string, args NOT swapped
34472                    let norm_d1 = Self::date_literal_to_cast(date1);
34473                    let norm_d2 = Self::date_literal_to_cast(date2);
34474                    return Ok(Expression::Function(Box::new(Function::new(
34475                        "DATE_DIFF".to_string(),
34476                        vec![
34477                            Expression::Literal(Box::new(Literal::String(unit_str))),
34478                            norm_d1,
34479                            norm_d2,
34480                        ],
34481                    ))));
34482                }
34483
34484                if matches!(target, DialectType::DuckDB) {
34485                    // DuckDB: DATE_DIFF('UNIT', date2, date1) with proper CAST for dates
34486                    let norm_d1 = Self::ensure_cast_date(date1);
34487                    let norm_d2 = Self::ensure_cast_date(date2);
34488
34489                    // Handle WEEK variants: WEEK(MONDAY)/WEEK(SUNDAY)/ISOWEEK/WEEK
34490                    let is_week_variant = unit_str == "WEEK"
34491                        || unit_str.starts_with("WEEK(")
34492                        || unit_str == "ISOWEEK";
34493                    if is_week_variant {
34494                        // For DuckDB, WEEK-based diffs use DATE_TRUNC approach
34495                        // WEEK(MONDAY) / ISOWEEK: DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2), DATE_TRUNC('WEEK', d1))
34496                        // WEEK / WEEK(SUNDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '1' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '1' DAY))
34497                        // WEEK(SATURDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '-5' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '-5' DAY))
34498                        let day_offset = if unit_str == "WEEK(MONDAY)" || unit_str == "ISOWEEK" {
34499                            None // ISO weeks start on Monday, aligned with DATE_TRUNC('WEEK')
34500                        } else if unit_str == "WEEK" || unit_str == "WEEK(SUNDAY)" {
34501                            Some("1") // Shift Sunday to Monday alignment
34502                        } else if unit_str == "WEEK(SATURDAY)" {
34503                            Some("-5")
34504                        } else if unit_str == "WEEK(TUESDAY)" {
34505                            Some("-1")
34506                        } else if unit_str == "WEEK(WEDNESDAY)" {
34507                            Some("-2")
34508                        } else if unit_str == "WEEK(THURSDAY)" {
34509                            Some("-3")
34510                        } else if unit_str == "WEEK(FRIDAY)" {
34511                            Some("-4")
34512                        } else {
34513                            Some("1") // default to Sunday
34514                        };
34515
34516                        let make_trunc = |date: Expression, offset: Option<&str>| -> Expression {
34517                            let shifted = if let Some(off) = offset {
34518                                let interval =
34519                                    Expression::Interval(Box::new(crate::expressions::Interval {
34520                                        this: Some(Expression::Literal(Box::new(Literal::String(
34521                                            off.to_string(),
34522                                        )))),
34523                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34524                                            unit: crate::expressions::IntervalUnit::Day,
34525                                            use_plural: false,
34526                                        }),
34527                                    }));
34528                                Expression::Add(Box::new(crate::expressions::BinaryOp::new(
34529                                    date, interval,
34530                                )))
34531                            } else {
34532                                date
34533                            };
34534                            Expression::Function(Box::new(Function::new(
34535                                "DATE_TRUNC".to_string(),
34536                                vec![
34537                                    Expression::Literal(Box::new(Literal::String(
34538                                        "WEEK".to_string(),
34539                                    ))),
34540                                    shifted,
34541                                ],
34542                            )))
34543                        };
34544
34545                        let trunc_d2 = make_trunc(norm_d2, day_offset);
34546                        let trunc_d1 = make_trunc(norm_d1, day_offset);
34547                        return Ok(Expression::Function(Box::new(Function::new(
34548                            "DATE_DIFF".to_string(),
34549                            vec![
34550                                Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
34551                                trunc_d2,
34552                                trunc_d1,
34553                            ],
34554                        ))));
34555                    }
34556
34557                    return Ok(Expression::Function(Box::new(Function::new(
34558                        "DATE_DIFF".to_string(),
34559                        vec![
34560                            Expression::Literal(Box::new(Literal::String(unit_str))),
34561                            norm_d2,
34562                            norm_d1,
34563                        ],
34564                    ))));
34565                }
34566
34567                // Default: DATEDIFF(unit, date2, date1)
34568                let unit = Expression::Identifier(Identifier::new(unit_str));
34569                Ok(Expression::Function(Box::new(Function::new(
34570                    "DATEDIFF".to_string(),
34571                    vec![unit, date2, date1],
34572                ))))
34573            }
34574
34575            // TIMESTAMP_ADD(ts, INTERVAL n UNIT) -> target-specific
34576            "TIMESTAMP_ADD" | "DATETIME_ADD" | "TIME_ADD" if args.len() == 2 => {
34577                let ts = args.remove(0);
34578                let interval_expr = args.remove(0);
34579                let (val, unit) =
34580                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
34581                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
34582                    });
34583
34584                match target {
34585                    DialectType::Snowflake => {
34586                        // TIMESTAMPADD(UNIT, val, CAST(ts AS TIMESTAMPTZ))
34587                        // Use TimestampAdd expression so Snowflake generates TIMESTAMPADD
34588                        // (Function("TIMESTAMPADD") would be converted to DATEADD by Snowflake's function normalization)
34589                        let unit_str = Self::interval_unit_to_string(&unit);
34590                        let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
34591                        Ok(Expression::TimestampAdd(Box::new(
34592                            crate::expressions::TimestampAdd {
34593                                this: Box::new(val),
34594                                expression: Box::new(cast_ts),
34595                                unit: Some(unit_str.to_string()),
34596                            },
34597                        )))
34598                    }
34599                    DialectType::Spark | DialectType::Databricks => {
34600                        if name == "DATETIME_ADD" && matches!(target, DialectType::Spark) {
34601                            // Spark DATETIME_ADD: ts + INTERVAL val UNIT
34602                            let interval =
34603                                Expression::Interval(Box::new(crate::expressions::Interval {
34604                                    this: Some(val),
34605                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34606                                        unit,
34607                                        use_plural: false,
34608                                    }),
34609                                }));
34610                            Ok(Expression::Add(Box::new(
34611                                crate::expressions::BinaryOp::new(ts, interval),
34612                            )))
34613                        } else if name == "DATETIME_ADD"
34614                            && matches!(target, DialectType::Databricks)
34615                        {
34616                            // Databricks DATETIME_ADD: TIMESTAMPADD(UNIT, val, ts)
34617                            let unit_str = Self::interval_unit_to_string(&unit);
34618                            Ok(Expression::Function(Box::new(Function::new(
34619                                "TIMESTAMPADD".to_string(),
34620                                vec![Expression::Identifier(Identifier::new(unit_str)), val, ts],
34621                            ))))
34622                        } else {
34623                            // Presto-style: DATE_ADD('unit', val, CAST(ts AS TIMESTAMP))
34624                            let unit_str = Self::interval_unit_to_string(&unit);
34625                            let cast_ts =
34626                                if name.starts_with("TIMESTAMP") || name.starts_with("DATETIME") {
34627                                    Self::maybe_cast_ts(ts)
34628                                } else {
34629                                    ts
34630                                };
34631                            Ok(Expression::Function(Box::new(Function::new(
34632                                "DATE_ADD".to_string(),
34633                                vec![
34634                                    Expression::Identifier(Identifier::new(unit_str)),
34635                                    val,
34636                                    cast_ts,
34637                                ],
34638                            ))))
34639                        }
34640                    }
34641                    DialectType::MySQL => {
34642                        // DATE_ADD(TIMESTAMP(ts), INTERVAL val UNIT) for MySQL
34643                        let mysql_ts = if name.starts_with("TIMESTAMP") {
34644                            // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
34645                            match &ts {
34646                                Expression::Function(ref inner_f)
34647                                    if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
34648                                {
34649                                    // Already wrapped, keep as-is
34650                                    ts
34651                                }
34652                                _ => {
34653                                    // Unwrap typed literals: TIMESTAMP '...' -> '...' for TIMESTAMP() wrapper
34654                                    let unwrapped = match ts {
34655                                        Expression::Literal(lit)
34656                                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
34657                                        {
34658                                            let Literal::Timestamp(s) = lit.as_ref() else {
34659                                                unreachable!()
34660                                            };
34661                                            Expression::Literal(Box::new(Literal::String(
34662                                                s.clone(),
34663                                            )))
34664                                        }
34665                                        other => other,
34666                                    };
34667                                    Expression::Function(Box::new(Function::new(
34668                                        "TIMESTAMP".to_string(),
34669                                        vec![unwrapped],
34670                                    )))
34671                                }
34672                            }
34673                        } else {
34674                            ts
34675                        };
34676                        Ok(Expression::DateAdd(Box::new(
34677                            crate::expressions::DateAddFunc {
34678                                this: mysql_ts,
34679                                interval: val,
34680                                unit,
34681                            },
34682                        )))
34683                    }
34684                    _ => {
34685                        // DuckDB and others use DateAdd expression (DuckDB converts to + INTERVAL)
34686                        let cast_ts = if matches!(target, DialectType::DuckDB) {
34687                            if name == "DATETIME_ADD" {
34688                                Self::ensure_cast_timestamp(ts)
34689                            } else if name.starts_with("TIMESTAMP") {
34690                                Self::maybe_cast_ts_to_tz(ts, &name)
34691                            } else {
34692                                ts
34693                            }
34694                        } else {
34695                            ts
34696                        };
34697                        Ok(Expression::DateAdd(Box::new(
34698                            crate::expressions::DateAddFunc {
34699                                this: cast_ts,
34700                                interval: val,
34701                                unit,
34702                            },
34703                        )))
34704                    }
34705                }
34706            }
34707
34708            // TIMESTAMP_SUB(ts, INTERVAL n UNIT) -> target-specific
34709            "TIMESTAMP_SUB" | "DATETIME_SUB" | "TIME_SUB" if args.len() == 2 => {
34710                let ts = args.remove(0);
34711                let interval_expr = args.remove(0);
34712                let (val, unit) =
34713                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
34714                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
34715                    });
34716
34717                match target {
34718                    DialectType::Snowflake => {
34719                        // TIMESTAMPADD(UNIT, val * -1, CAST(ts AS TIMESTAMPTZ))
34720                        let unit_str = Self::interval_unit_to_string(&unit);
34721                        let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
34722                        let neg_val = Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
34723                            val,
34724                            Expression::Neg(Box::new(crate::expressions::UnaryOp {
34725                                this: Expression::number(1),
34726                                inferred_type: None,
34727                            })),
34728                        )));
34729                        Ok(Expression::TimestampAdd(Box::new(
34730                            crate::expressions::TimestampAdd {
34731                                this: Box::new(neg_val),
34732                                expression: Box::new(cast_ts),
34733                                unit: Some(unit_str.to_string()),
34734                            },
34735                        )))
34736                    }
34737                    DialectType::Spark | DialectType::Databricks => {
34738                        if (name == "DATETIME_SUB" && matches!(target, DialectType::Spark))
34739                            || (name == "TIMESTAMP_SUB" && matches!(target, DialectType::Spark))
34740                        {
34741                            // Spark: ts - INTERVAL val UNIT
34742                            let cast_ts = if name.starts_with("TIMESTAMP") {
34743                                Self::maybe_cast_ts(ts)
34744                            } else {
34745                                ts
34746                            };
34747                            let interval =
34748                                Expression::Interval(Box::new(crate::expressions::Interval {
34749                                    this: Some(val),
34750                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34751                                        unit,
34752                                        use_plural: false,
34753                                    }),
34754                                }));
34755                            Ok(Expression::Sub(Box::new(
34756                                crate::expressions::BinaryOp::new(cast_ts, interval),
34757                            )))
34758                        } else {
34759                            // Databricks: TIMESTAMPADD(UNIT, val * -1, ts)
34760                            let unit_str = Self::interval_unit_to_string(&unit);
34761                            let neg_val =
34762                                Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
34763                                    val,
34764                                    Expression::Neg(Box::new(crate::expressions::UnaryOp {
34765                                        this: Expression::number(1),
34766                                        inferred_type: None,
34767                                    })),
34768                                )));
34769                            Ok(Expression::Function(Box::new(Function::new(
34770                                "TIMESTAMPADD".to_string(),
34771                                vec![
34772                                    Expression::Identifier(Identifier::new(unit_str)),
34773                                    neg_val,
34774                                    ts,
34775                                ],
34776                            ))))
34777                        }
34778                    }
34779                    DialectType::MySQL => {
34780                        let mysql_ts = if name.starts_with("TIMESTAMP") {
34781                            // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
34782                            match &ts {
34783                                Expression::Function(ref inner_f)
34784                                    if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
34785                                {
34786                                    // Already wrapped, keep as-is
34787                                    ts
34788                                }
34789                                _ => {
34790                                    let unwrapped = match ts {
34791                                        Expression::Literal(lit)
34792                                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
34793                                        {
34794                                            let Literal::Timestamp(s) = lit.as_ref() else {
34795                                                unreachable!()
34796                                            };
34797                                            Expression::Literal(Box::new(Literal::String(
34798                                                s.clone(),
34799                                            )))
34800                                        }
34801                                        other => other,
34802                                    };
34803                                    Expression::Function(Box::new(Function::new(
34804                                        "TIMESTAMP".to_string(),
34805                                        vec![unwrapped],
34806                                    )))
34807                                }
34808                            }
34809                        } else {
34810                            ts
34811                        };
34812                        Ok(Expression::DateSub(Box::new(
34813                            crate::expressions::DateAddFunc {
34814                                this: mysql_ts,
34815                                interval: val,
34816                                unit,
34817                            },
34818                        )))
34819                    }
34820                    _ => {
34821                        let cast_ts = if matches!(target, DialectType::DuckDB) {
34822                            if name == "DATETIME_SUB" {
34823                                Self::ensure_cast_timestamp(ts)
34824                            } else if name.starts_with("TIMESTAMP") {
34825                                Self::maybe_cast_ts_to_tz(ts, &name)
34826                            } else {
34827                                ts
34828                            }
34829                        } else {
34830                            ts
34831                        };
34832                        Ok(Expression::DateSub(Box::new(
34833                            crate::expressions::DateAddFunc {
34834                                this: cast_ts,
34835                                interval: val,
34836                                unit,
34837                            },
34838                        )))
34839                    }
34840                }
34841            }
34842
34843            // DATE_SUB(date, INTERVAL n UNIT) -> target-specific
34844            "DATE_SUB" if args.len() == 2 => {
34845                let date = args.remove(0);
34846                let interval_expr = args.remove(0);
34847                let (val, unit) =
34848                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
34849                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
34850                    });
34851
34852                match target {
34853                    DialectType::Databricks | DialectType::Spark => {
34854                        // Databricks/Spark: DATE_ADD(date, -val)
34855                        // Use DateAdd expression with negative val so it generates correctly
34856                        // The generator will output DATE_ADD(date, INTERVAL -val DAY)
34857                        // Then Databricks transform converts 2-arg DATE_ADD(date, interval) to DATEADD(DAY, interval, date)
34858                        // Instead, we directly output as a simple negated DateSub
34859                        Ok(Expression::DateSub(Box::new(
34860                            crate::expressions::DateAddFunc {
34861                                this: date,
34862                                interval: val,
34863                                unit,
34864                            },
34865                        )))
34866                    }
34867                    DialectType::DuckDB => {
34868                        // DuckDB: CAST(date AS DATE) - INTERVAL 'val' UNIT
34869                        let cast_date = Self::ensure_cast_date(date);
34870                        let interval =
34871                            Expression::Interval(Box::new(crate::expressions::Interval {
34872                                this: Some(val),
34873                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34874                                    unit,
34875                                    use_plural: false,
34876                                }),
34877                            }));
34878                        Ok(Expression::Sub(Box::new(
34879                            crate::expressions::BinaryOp::new(cast_date, interval),
34880                        )))
34881                    }
34882                    DialectType::Snowflake => {
34883                        // Snowflake: Let Snowflake's own DateSub -> DATEADD(UNIT, val * -1, date) handler work
34884                        // Just ensure the date is cast properly
34885                        let cast_date = Self::ensure_cast_date(date);
34886                        Ok(Expression::DateSub(Box::new(
34887                            crate::expressions::DateAddFunc {
34888                                this: cast_date,
34889                                interval: val,
34890                                unit,
34891                            },
34892                        )))
34893                    }
34894                    DialectType::PostgreSQL => {
34895                        // PostgreSQL: date - INTERVAL 'val UNIT'
34896                        let unit_str = Self::interval_unit_to_string(&unit);
34897                        let interval =
34898                            Expression::Interval(Box::new(crate::expressions::Interval {
34899                                this: Some(Expression::Literal(Box::new(Literal::String(
34900                                    format!("{} {}", Self::expr_to_string(&val), unit_str),
34901                                )))),
34902                                unit: None,
34903                            }));
34904                        Ok(Expression::Sub(Box::new(
34905                            crate::expressions::BinaryOp::new(date, interval),
34906                        )))
34907                    }
34908                    _ => Ok(Expression::DateSub(Box::new(
34909                        crate::expressions::DateAddFunc {
34910                            this: date,
34911                            interval: val,
34912                            unit,
34913                        },
34914                    ))),
34915                }
34916            }
34917
34918            // DATEADD(unit, val, date) -> target-specific form
34919            // Used by: Redshift, Snowflake, TSQL, ClickHouse
34920            "DATEADD" if args.len() == 3 => {
34921                let arg0 = args.remove(0);
34922                let arg1 = args.remove(0);
34923                let arg2 = args.remove(0);
34924                let unit_str = get_unit_str(&arg0);
34925
34926                if matches!(target, DialectType::Snowflake | DialectType::TSQL) {
34927                    // Keep DATEADD(UNIT, val, date) with uppercased unit
34928                    let unit = Expression::Identifier(Identifier::new(unit_str));
34929                    // Only CAST to DATETIME2 for TSQL target when source is NOT Spark/Databricks family
34930                    let date = if matches!(target, DialectType::TSQL)
34931                        && !matches!(
34932                            source,
34933                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
34934                        ) {
34935                        Self::ensure_cast_datetime2(arg2)
34936                    } else {
34937                        arg2
34938                    };
34939                    return Ok(Expression::Function(Box::new(Function::new(
34940                        "DATEADD".to_string(),
34941                        vec![unit, arg1, date],
34942                    ))));
34943                }
34944
34945                if matches!(target, DialectType::DuckDB) {
34946                    // DuckDB: date + INTERVAL 'val' UNIT
34947                    let iu = parse_interval_unit(&unit_str);
34948                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
34949                        this: Some(arg1),
34950                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34951                            unit: iu,
34952                            use_plural: false,
34953                        }),
34954                    }));
34955                    let cast_date = Self::ensure_cast_timestamp(arg2);
34956                    return Ok(Expression::Add(Box::new(
34957                        crate::expressions::BinaryOp::new(cast_date, interval),
34958                    )));
34959                }
34960
34961                if matches!(target, DialectType::BigQuery) {
34962                    // BigQuery: DATE_ADD(date, INTERVAL val UNIT) or TIMESTAMP_ADD(ts, INTERVAL val UNIT)
34963                    let iu = parse_interval_unit(&unit_str);
34964                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
34965                        this: Some(arg1),
34966                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34967                            unit: iu,
34968                            use_plural: false,
34969                        }),
34970                    }));
34971                    return Ok(Expression::Function(Box::new(Function::new(
34972                        "DATE_ADD".to_string(),
34973                        vec![arg2, interval],
34974                    ))));
34975                }
34976
34977                if matches!(target, DialectType::Databricks) {
34978                    // Databricks: keep DATEADD(UNIT, val, date) format
34979                    let unit = Expression::Identifier(Identifier::new(unit_str));
34980                    return Ok(Expression::Function(Box::new(Function::new(
34981                        "DATEADD".to_string(),
34982                        vec![unit, arg1, arg2],
34983                    ))));
34984                }
34985
34986                if matches!(target, DialectType::Spark) {
34987                    // Spark: convert month-based units to ADD_MONTHS, rest to DATE_ADD
34988                    fn multiply_expr_dateadd(expr: Expression, factor: i64) -> Expression {
34989                        if let Expression::Literal(lit) = &expr {
34990                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
34991                                if let Ok(val) = n.parse::<i64>() {
34992                                    return Expression::Literal(Box::new(
34993                                        crate::expressions::Literal::Number(
34994                                            (val * factor).to_string(),
34995                                        ),
34996                                    ));
34997                                }
34998                            }
34999                        }
35000                        Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
35001                            expr,
35002                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
35003                                factor.to_string(),
35004                            ))),
35005                        )))
35006                    }
35007                    match unit_str.as_str() {
35008                        "YEAR" => {
35009                            let months = multiply_expr_dateadd(arg1, 12);
35010                            return Ok(Expression::Function(Box::new(Function::new(
35011                                "ADD_MONTHS".to_string(),
35012                                vec![arg2, months],
35013                            ))));
35014                        }
35015                        "QUARTER" => {
35016                            let months = multiply_expr_dateadd(arg1, 3);
35017                            return Ok(Expression::Function(Box::new(Function::new(
35018                                "ADD_MONTHS".to_string(),
35019                                vec![arg2, months],
35020                            ))));
35021                        }
35022                        "MONTH" => {
35023                            return Ok(Expression::Function(Box::new(Function::new(
35024                                "ADD_MONTHS".to_string(),
35025                                vec![arg2, arg1],
35026                            ))));
35027                        }
35028                        "WEEK" => {
35029                            let days = multiply_expr_dateadd(arg1, 7);
35030                            return Ok(Expression::Function(Box::new(Function::new(
35031                                "DATE_ADD".to_string(),
35032                                vec![arg2, days],
35033                            ))));
35034                        }
35035                        "DAY" => {
35036                            return Ok(Expression::Function(Box::new(Function::new(
35037                                "DATE_ADD".to_string(),
35038                                vec![arg2, arg1],
35039                            ))));
35040                        }
35041                        _ => {
35042                            let unit = Expression::Identifier(Identifier::new(unit_str));
35043                            return Ok(Expression::Function(Box::new(Function::new(
35044                                "DATE_ADD".to_string(),
35045                                vec![unit, arg1, arg2],
35046                            ))));
35047                        }
35048                    }
35049                }
35050
35051                if matches!(target, DialectType::Hive) {
35052                    // Hive: DATE_ADD(date, val) for DAY, or date + INTERVAL for others
35053                    match unit_str.as_str() {
35054                        "DAY" => {
35055                            return Ok(Expression::Function(Box::new(Function::new(
35056                                "DATE_ADD".to_string(),
35057                                vec![arg2, arg1],
35058                            ))));
35059                        }
35060                        "MONTH" => {
35061                            return Ok(Expression::Function(Box::new(Function::new(
35062                                "ADD_MONTHS".to_string(),
35063                                vec![arg2, arg1],
35064                            ))));
35065                        }
35066                        _ => {
35067                            let iu = parse_interval_unit(&unit_str);
35068                            let interval =
35069                                Expression::Interval(Box::new(crate::expressions::Interval {
35070                                    this: Some(arg1),
35071                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35072                                        unit: iu,
35073                                        use_plural: false,
35074                                    }),
35075                                }));
35076                            return Ok(Expression::Add(Box::new(
35077                                crate::expressions::BinaryOp::new(arg2, interval),
35078                            )));
35079                        }
35080                    }
35081                }
35082
35083                if matches!(target, DialectType::PostgreSQL) {
35084                    // PostgreSQL: date + INTERVAL 'val UNIT'
35085                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
35086                        this: Some(Expression::Literal(Box::new(Literal::String(format!(
35087                            "{} {}",
35088                            Self::expr_to_string(&arg1),
35089                            unit_str
35090                        ))))),
35091                        unit: None,
35092                    }));
35093                    return Ok(Expression::Add(Box::new(
35094                        crate::expressions::BinaryOp::new(arg2, interval),
35095                    )));
35096                }
35097
35098                if matches!(
35099                    target,
35100                    DialectType::Presto | DialectType::Trino | DialectType::Athena
35101                ) {
35102                    // Presto/Trino: DATE_ADD('UNIT', val, date)
35103                    return Ok(Expression::Function(Box::new(Function::new(
35104                        "DATE_ADD".to_string(),
35105                        vec![
35106                            Expression::Literal(Box::new(Literal::String(unit_str))),
35107                            arg1,
35108                            arg2,
35109                        ],
35110                    ))));
35111                }
35112
35113                if matches!(target, DialectType::ClickHouse) {
35114                    // ClickHouse: DATE_ADD(UNIT, val, date)
35115                    let unit = Expression::Identifier(Identifier::new(unit_str));
35116                    return Ok(Expression::Function(Box::new(Function::new(
35117                        "DATE_ADD".to_string(),
35118                        vec![unit, arg1, arg2],
35119                    ))));
35120                }
35121
35122                // Default: keep DATEADD with uppercased unit
35123                let unit = Expression::Identifier(Identifier::new(unit_str));
35124                Ok(Expression::Function(Box::new(Function::new(
35125                    "DATEADD".to_string(),
35126                    vec![unit, arg1, arg2],
35127                ))))
35128            }
35129
35130            // DATE_ADD(unit, val, date) - 3 arg form from ClickHouse/Presto
35131            "DATE_ADD" if args.len() == 3 => {
35132                let arg0 = args.remove(0);
35133                let arg1 = args.remove(0);
35134                let arg2 = args.remove(0);
35135                let unit_str = get_unit_str(&arg0);
35136
35137                if matches!(
35138                    target,
35139                    DialectType::Presto | DialectType::Trino | DialectType::Athena
35140                ) {
35141                    // Presto/Trino: DATE_ADD('UNIT', val, date)
35142                    return Ok(Expression::Function(Box::new(Function::new(
35143                        "DATE_ADD".to_string(),
35144                        vec![
35145                            Expression::Literal(Box::new(Literal::String(unit_str))),
35146                            arg1,
35147                            arg2,
35148                        ],
35149                    ))));
35150                }
35151
35152                if matches!(
35153                    target,
35154                    DialectType::Snowflake | DialectType::TSQL | DialectType::Redshift
35155                ) {
35156                    // DATEADD(UNIT, val, date)
35157                    let unit = Expression::Identifier(Identifier::new(unit_str));
35158                    let date = if matches!(target, DialectType::TSQL) {
35159                        Self::ensure_cast_datetime2(arg2)
35160                    } else {
35161                        arg2
35162                    };
35163                    return Ok(Expression::Function(Box::new(Function::new(
35164                        "DATEADD".to_string(),
35165                        vec![unit, arg1, date],
35166                    ))));
35167                }
35168
35169                if matches!(target, DialectType::DuckDB) {
35170                    // DuckDB: date + INTERVAL val UNIT
35171                    let iu = parse_interval_unit(&unit_str);
35172                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
35173                        this: Some(arg1),
35174                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35175                            unit: iu,
35176                            use_plural: false,
35177                        }),
35178                    }));
35179                    return Ok(Expression::Add(Box::new(
35180                        crate::expressions::BinaryOp::new(arg2, interval),
35181                    )));
35182                }
35183
35184                if matches!(target, DialectType::Spark | DialectType::Databricks) {
35185                    // Spark: DATE_ADD(UNIT, val, date) with uppercased unit
35186                    let unit = Expression::Identifier(Identifier::new(unit_str));
35187                    return Ok(Expression::Function(Box::new(Function::new(
35188                        "DATE_ADD".to_string(),
35189                        vec![unit, arg1, arg2],
35190                    ))));
35191                }
35192
35193                // Default: DATE_ADD(UNIT, val, date)
35194                let unit = Expression::Identifier(Identifier::new(unit_str));
35195                Ok(Expression::Function(Box::new(Function::new(
35196                    "DATE_ADD".to_string(),
35197                    vec![unit, arg1, arg2],
35198                ))))
35199            }
35200
35201            // DATE_ADD(date, INTERVAL val UNIT) - 2 arg BigQuery form
35202            "DATE_ADD" if args.len() == 2 => {
35203                let date = args.remove(0);
35204                let interval_expr = args.remove(0);
35205                let (val, unit) =
35206                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
35207                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
35208                    });
35209                let unit_str = Self::interval_unit_to_string(&unit);
35210
35211                match target {
35212                    DialectType::DuckDB => {
35213                        // DuckDB: CAST(date AS DATE) + INTERVAL 'val' UNIT
35214                        let cast_date = Self::ensure_cast_date(date);
35215                        let quoted_val = Self::quote_interval_val(&val);
35216                        let interval =
35217                            Expression::Interval(Box::new(crate::expressions::Interval {
35218                                this: Some(quoted_val),
35219                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35220                                    unit,
35221                                    use_plural: false,
35222                                }),
35223                            }));
35224                        Ok(Expression::Add(Box::new(
35225                            crate::expressions::BinaryOp::new(cast_date, interval),
35226                        )))
35227                    }
35228                    DialectType::PostgreSQL => {
35229                        // PostgreSQL: date + INTERVAL 'val UNIT'
35230                        let interval =
35231                            Expression::Interval(Box::new(crate::expressions::Interval {
35232                                this: Some(Expression::Literal(Box::new(Literal::String(
35233                                    format!("{} {}", Self::expr_to_string(&val), unit_str),
35234                                )))),
35235                                unit: None,
35236                            }));
35237                        Ok(Expression::Add(Box::new(
35238                            crate::expressions::BinaryOp::new(date, interval),
35239                        )))
35240                    }
35241                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
35242                        // Presto: DATE_ADD('UNIT', CAST('val' AS BIGINT), date)
35243                        let val_str = Self::expr_to_string(&val);
35244                        Ok(Expression::Function(Box::new(Function::new(
35245                            "DATE_ADD".to_string(),
35246                            vec![
35247                                Expression::Literal(Box::new(Literal::String(
35248                                    unit_str.to_string(),
35249                                ))),
35250                                Expression::Cast(Box::new(Cast {
35251                                    this: Expression::Literal(Box::new(Literal::String(val_str))),
35252                                    to: DataType::BigInt { length: None },
35253                                    trailing_comments: vec![],
35254                                    double_colon_syntax: false,
35255                                    format: None,
35256                                    default: None,
35257                                    inferred_type: None,
35258                                })),
35259                                date,
35260                            ],
35261                        ))))
35262                    }
35263                    DialectType::Spark | DialectType::Hive => {
35264                        // Spark/Hive: DATE_ADD(date, val) for DAY
35265                        match unit_str {
35266                            "DAY" => Ok(Expression::Function(Box::new(Function::new(
35267                                "DATE_ADD".to_string(),
35268                                vec![date, val],
35269                            )))),
35270                            "MONTH" => Ok(Expression::Function(Box::new(Function::new(
35271                                "ADD_MONTHS".to_string(),
35272                                vec![date, val],
35273                            )))),
35274                            _ => {
35275                                let iu = parse_interval_unit(&unit_str);
35276                                let interval =
35277                                    Expression::Interval(Box::new(crate::expressions::Interval {
35278                                        this: Some(val),
35279                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35280                                            unit: iu,
35281                                            use_plural: false,
35282                                        }),
35283                                    }));
35284                                Ok(Expression::Function(Box::new(Function::new(
35285                                    "DATE_ADD".to_string(),
35286                                    vec![date, interval],
35287                                ))))
35288                            }
35289                        }
35290                    }
35291                    DialectType::Snowflake => {
35292                        // Snowflake: DATEADD(UNIT, 'val', CAST(date AS DATE))
35293                        let cast_date = Self::ensure_cast_date(date);
35294                        let val_str = Self::expr_to_string(&val);
35295                        Ok(Expression::Function(Box::new(Function::new(
35296                            "DATEADD".to_string(),
35297                            vec![
35298                                Expression::Identifier(Identifier::new(unit_str)),
35299                                Expression::Literal(Box::new(Literal::String(val_str))),
35300                                cast_date,
35301                            ],
35302                        ))))
35303                    }
35304                    DialectType::TSQL | DialectType::Fabric => {
35305                        let cast_date = Self::ensure_cast_datetime2(date);
35306                        Ok(Expression::Function(Box::new(Function::new(
35307                            "DATEADD".to_string(),
35308                            vec![
35309                                Expression::Identifier(Identifier::new(unit_str)),
35310                                val,
35311                                cast_date,
35312                            ],
35313                        ))))
35314                    }
35315                    DialectType::Redshift => Ok(Expression::Function(Box::new(Function::new(
35316                        "DATEADD".to_string(),
35317                        vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
35318                    )))),
35319                    DialectType::MySQL => {
35320                        // MySQL: DATE_ADD(date, INTERVAL 'val' UNIT)
35321                        let quoted_val = Self::quote_interval_val(&val);
35322                        let iu = parse_interval_unit(&unit_str);
35323                        let interval =
35324                            Expression::Interval(Box::new(crate::expressions::Interval {
35325                                this: Some(quoted_val),
35326                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35327                                    unit: iu,
35328                                    use_plural: false,
35329                                }),
35330                            }));
35331                        Ok(Expression::Function(Box::new(Function::new(
35332                            "DATE_ADD".to_string(),
35333                            vec![date, interval],
35334                        ))))
35335                    }
35336                    DialectType::BigQuery => {
35337                        // BigQuery: DATE_ADD(date, INTERVAL 'val' UNIT)
35338                        let quoted_val = Self::quote_interval_val(&val);
35339                        let iu = parse_interval_unit(&unit_str);
35340                        let interval =
35341                            Expression::Interval(Box::new(crate::expressions::Interval {
35342                                this: Some(quoted_val),
35343                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35344                                    unit: iu,
35345                                    use_plural: false,
35346                                }),
35347                            }));
35348                        Ok(Expression::Function(Box::new(Function::new(
35349                            "DATE_ADD".to_string(),
35350                            vec![date, interval],
35351                        ))))
35352                    }
35353                    DialectType::Databricks => Ok(Expression::Function(Box::new(Function::new(
35354                        "DATEADD".to_string(),
35355                        vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
35356                    )))),
35357                    _ => {
35358                        // Default: keep as DATE_ADD with decomposed interval
35359                        Ok(Expression::DateAdd(Box::new(
35360                            crate::expressions::DateAddFunc {
35361                                this: date,
35362                                interval: val,
35363                                unit,
35364                            },
35365                        )))
35366                    }
35367                }
35368            }
35369
35370            // ADD_MONTHS(date, val) -> target-specific form
35371            "ADD_MONTHS" if args.len() == 2 => {
35372                let date = args.remove(0);
35373                let val = args.remove(0);
35374
35375                if matches!(target, DialectType::TSQL) {
35376                    // TSQL: DATEADD(MONTH, val, CAST(date AS DATETIME2))
35377                    let cast_date = Self::ensure_cast_datetime2(date);
35378                    return Ok(Expression::Function(Box::new(Function::new(
35379                        "DATEADD".to_string(),
35380                        vec![
35381                            Expression::Identifier(Identifier::new("MONTH")),
35382                            val,
35383                            cast_date,
35384                        ],
35385                    ))));
35386                }
35387
35388                if matches!(target, DialectType::DuckDB) {
35389                    // DuckDB: date + INTERVAL val MONTH
35390                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
35391                        this: Some(val),
35392                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
35393                            unit: crate::expressions::IntervalUnit::Month,
35394                            use_plural: false,
35395                        }),
35396                    }));
35397                    return Ok(Expression::Add(Box::new(
35398                        crate::expressions::BinaryOp::new(date, interval),
35399                    )));
35400                }
35401
35402                if matches!(target, DialectType::Snowflake) {
35403                    // Snowflake: keep ADD_MONTHS when source is also Snowflake, else DATEADD
35404                    if matches!(source, DialectType::Snowflake) {
35405                        return Ok(Expression::Function(Box::new(Function::new(
35406                            "ADD_MONTHS".to_string(),
35407                            vec![date, val],
35408                        ))));
35409                    }
35410                    return Ok(Expression::Function(Box::new(Function::new(
35411                        "DATEADD".to_string(),
35412                        vec![Expression::Identifier(Identifier::new("MONTH")), val, date],
35413                    ))));
35414                }
35415
35416                if matches!(target, DialectType::Spark | DialectType::Databricks) {
35417                    // Spark: ADD_MONTHS(date, val) - keep as is
35418                    return Ok(Expression::Function(Box::new(Function::new(
35419                        "ADD_MONTHS".to_string(),
35420                        vec![date, val],
35421                    ))));
35422                }
35423
35424                if matches!(target, DialectType::Hive) {
35425                    return Ok(Expression::Function(Box::new(Function::new(
35426                        "ADD_MONTHS".to_string(),
35427                        vec![date, val],
35428                    ))));
35429                }
35430
35431                if matches!(
35432                    target,
35433                    DialectType::Presto | DialectType::Trino | DialectType::Athena
35434                ) {
35435                    // Presto: DATE_ADD('MONTH', val, date)
35436                    return Ok(Expression::Function(Box::new(Function::new(
35437                        "DATE_ADD".to_string(),
35438                        vec![
35439                            Expression::Literal(Box::new(Literal::String("MONTH".to_string()))),
35440                            val,
35441                            date,
35442                        ],
35443                    ))));
35444                }
35445
35446                // Default: keep ADD_MONTHS
35447                Ok(Expression::Function(Box::new(Function::new(
35448                    "ADD_MONTHS".to_string(),
35449                    vec![date, val],
35450                ))))
35451            }
35452
35453            // SAFE_DIVIDE(x, y) -> target-specific form directly
35454            "SAFE_DIVIDE" if args.len() == 2 => {
35455                let x = args.remove(0);
35456                let y = args.remove(0);
35457                // Wrap x and y in parens if they're complex expressions
35458                let y_ref = match &y {
35459                    Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
35460                        y.clone()
35461                    }
35462                    _ => Expression::Paren(Box::new(Paren {
35463                        this: y.clone(),
35464                        trailing_comments: vec![],
35465                    })),
35466                };
35467                let x_ref = match &x {
35468                    Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
35469                        x.clone()
35470                    }
35471                    _ => Expression::Paren(Box::new(Paren {
35472                        this: x.clone(),
35473                        trailing_comments: vec![],
35474                    })),
35475                };
35476                let condition = Expression::Neq(Box::new(crate::expressions::BinaryOp::new(
35477                    y_ref.clone(),
35478                    Expression::number(0),
35479                )));
35480                let div_expr = Expression::Div(Box::new(crate::expressions::BinaryOp::new(
35481                    x_ref.clone(),
35482                    y_ref.clone(),
35483                )));
35484
35485                match target {
35486                    DialectType::Spark | DialectType::Databricks => Ok(Expression::Function(
35487                        Box::new(Function::new("TRY_DIVIDE".to_string(), vec![x, y])),
35488                    )),
35489                    DialectType::DuckDB | DialectType::PostgreSQL => {
35490                        // CASE WHEN y <> 0 THEN x / y ELSE NULL END
35491                        let result_div = if matches!(target, DialectType::PostgreSQL) {
35492                            let cast_x = Expression::Cast(Box::new(Cast {
35493                                this: x_ref,
35494                                to: DataType::Custom {
35495                                    name: "DOUBLE PRECISION".to_string(),
35496                                },
35497                                trailing_comments: vec![],
35498                                double_colon_syntax: false,
35499                                format: None,
35500                                default: None,
35501                                inferred_type: None,
35502                            }));
35503                            Expression::Div(Box::new(crate::expressions::BinaryOp::new(
35504                                cast_x, y_ref,
35505                            )))
35506                        } else {
35507                            div_expr
35508                        };
35509                        Ok(Expression::Case(Box::new(crate::expressions::Case {
35510                            operand: None,
35511                            whens: vec![(condition, result_div)],
35512                            else_: Some(Expression::Null(crate::expressions::Null)),
35513                            comments: Vec::new(),
35514                            inferred_type: None,
35515                        })))
35516                    }
35517                    DialectType::Snowflake => {
35518                        // IFF(y <> 0, x / y, NULL)
35519                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
35520                            condition,
35521                            true_value: div_expr,
35522                            false_value: Some(Expression::Null(crate::expressions::Null)),
35523                            original_name: Some("IFF".to_string()),
35524                            inferred_type: None,
35525                        })))
35526                    }
35527                    DialectType::Presto | DialectType::Trino => {
35528                        // IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
35529                        let cast_x = Expression::Cast(Box::new(Cast {
35530                            this: x_ref,
35531                            to: DataType::Double {
35532                                precision: None,
35533                                scale: None,
35534                            },
35535                            trailing_comments: vec![],
35536                            double_colon_syntax: false,
35537                            format: None,
35538                            default: None,
35539                            inferred_type: None,
35540                        }));
35541                        let cast_div = Expression::Div(Box::new(
35542                            crate::expressions::BinaryOp::new(cast_x, y_ref),
35543                        ));
35544                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
35545                            condition,
35546                            true_value: cast_div,
35547                            false_value: Some(Expression::Null(crate::expressions::Null)),
35548                            original_name: None,
35549                            inferred_type: None,
35550                        })))
35551                    }
35552                    _ => {
35553                        // IF(y <> 0, x / y, NULL)
35554                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
35555                            condition,
35556                            true_value: div_expr,
35557                            false_value: Some(Expression::Null(crate::expressions::Null)),
35558                            original_name: None,
35559                            inferred_type: None,
35560                        })))
35561                    }
35562                }
35563            }
35564
35565            // GENERATE_UUID() -> UUID() with CAST to string
35566            "GENERATE_UUID" => {
35567                let uuid_expr = Expression::Uuid(Box::new(crate::expressions::Uuid {
35568                    this: None,
35569                    name: None,
35570                    is_string: None,
35571                }));
35572                // Most targets need CAST(UUID() AS TEXT/VARCHAR/STRING)
35573                let cast_type = match target {
35574                    DialectType::DuckDB => Some(DataType::Text),
35575                    DialectType::Presto | DialectType::Trino => Some(DataType::VarChar {
35576                        length: None,
35577                        parenthesized_length: false,
35578                    }),
35579                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
35580                        Some(DataType::String { length: None })
35581                    }
35582                    _ => None,
35583                };
35584                if let Some(dt) = cast_type {
35585                    Ok(Expression::Cast(Box::new(Cast {
35586                        this: uuid_expr,
35587                        to: dt,
35588                        trailing_comments: vec![],
35589                        double_colon_syntax: false,
35590                        format: None,
35591                        default: None,
35592                        inferred_type: None,
35593                    })))
35594                } else {
35595                    Ok(uuid_expr)
35596                }
35597            }
35598
35599            // COUNTIF(x) -> CountIf expression
35600            "COUNTIF" if args.len() == 1 => {
35601                let arg = args.remove(0);
35602                Ok(Expression::CountIf(Box::new(crate::expressions::AggFunc {
35603                    this: arg,
35604                    distinct: false,
35605                    filter: None,
35606                    order_by: vec![],
35607                    name: None,
35608                    ignore_nulls: None,
35609                    having_max: None,
35610                    limit: None,
35611                    inferred_type: None,
35612                })))
35613            }
35614
35615            // EDIT_DISTANCE(col1, col2, ...) -> Levenshtein expression
35616            "EDIT_DISTANCE" => {
35617                // Strip named arguments (max_distance => N) and pass as positional
35618                let mut positional_args: Vec<Expression> = vec![];
35619                for arg in args {
35620                    match arg {
35621                        Expression::NamedArgument(na) => {
35622                            positional_args.push(na.value);
35623                        }
35624                        other => positional_args.push(other),
35625                    }
35626                }
35627                if positional_args.len() >= 2 {
35628                    let col1 = positional_args.remove(0);
35629                    let col2 = positional_args.remove(0);
35630                    let levenshtein = crate::expressions::BinaryFunc {
35631                        this: col1,
35632                        expression: col2,
35633                        original_name: None,
35634                        inferred_type: None,
35635                    };
35636                    // Pass extra args through a function wrapper with all args
35637                    if !positional_args.is_empty() {
35638                        let max_dist = positional_args.remove(0);
35639                        // DuckDB: CASE WHEN LEVENSHTEIN(a, b) IS NULL OR max IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(a, b), max) END
35640                        if matches!(target, DialectType::DuckDB) {
35641                            let lev = Expression::Function(Box::new(Function::new(
35642                                "LEVENSHTEIN".to_string(),
35643                                vec![levenshtein.this, levenshtein.expression],
35644                            )));
35645                            let lev_is_null =
35646                                Expression::IsNull(Box::new(crate::expressions::IsNull {
35647                                    this: lev.clone(),
35648                                    not: false,
35649                                    postfix_form: false,
35650                                }));
35651                            let max_is_null =
35652                                Expression::IsNull(Box::new(crate::expressions::IsNull {
35653                                    this: max_dist.clone(),
35654                                    not: false,
35655                                    postfix_form: false,
35656                                }));
35657                            let null_check =
35658                                Expression::Or(Box::new(crate::expressions::BinaryOp {
35659                                    left: lev_is_null,
35660                                    right: max_is_null,
35661                                    left_comments: Vec::new(),
35662                                    operator_comments: Vec::new(),
35663                                    trailing_comments: Vec::new(),
35664                                    inferred_type: None,
35665                                }));
35666                            let least =
35667                                Expression::Least(Box::new(crate::expressions::VarArgFunc {
35668                                    expressions: vec![lev, max_dist],
35669                                    original_name: None,
35670                                    inferred_type: None,
35671                                }));
35672                            return Ok(Expression::Case(Box::new(crate::expressions::Case {
35673                                operand: None,
35674                                whens: vec![(
35675                                    null_check,
35676                                    Expression::Null(crate::expressions::Null),
35677                                )],
35678                                else_: Some(least),
35679                                comments: Vec::new(),
35680                                inferred_type: None,
35681                            })));
35682                        }
35683                        let mut all_args = vec![levenshtein.this, levenshtein.expression, max_dist];
35684                        all_args.extend(positional_args);
35685                        // PostgreSQL: use LEVENSHTEIN_LESS_EQUAL when max_distance is provided
35686                        let func_name = if matches!(target, DialectType::PostgreSQL) {
35687                            "LEVENSHTEIN_LESS_EQUAL"
35688                        } else {
35689                            "LEVENSHTEIN"
35690                        };
35691                        return Ok(Expression::Function(Box::new(Function::new(
35692                            func_name.to_string(),
35693                            all_args,
35694                        ))));
35695                    }
35696                    Ok(Expression::Levenshtein(Box::new(levenshtein)))
35697                } else {
35698                    Ok(Expression::Function(Box::new(Function::new(
35699                        "EDIT_DISTANCE".to_string(),
35700                        positional_args,
35701                    ))))
35702                }
35703            }
35704
35705            // TIMESTAMP_SECONDS(x) -> UnixToTime with scale 0
35706            "TIMESTAMP_SECONDS" if args.len() == 1 => {
35707                let arg = args.remove(0);
35708                Ok(Expression::UnixToTime(Box::new(
35709                    crate::expressions::UnixToTime {
35710                        this: Box::new(arg),
35711                        scale: Some(0),
35712                        zone: None,
35713                        hours: None,
35714                        minutes: None,
35715                        format: None,
35716                        target_type: None,
35717                    },
35718                )))
35719            }
35720
35721            // TIMESTAMP_MILLIS(x) -> UnixToTime with scale 3
35722            "TIMESTAMP_MILLIS" if args.len() == 1 => {
35723                let arg = args.remove(0);
35724                Ok(Expression::UnixToTime(Box::new(
35725                    crate::expressions::UnixToTime {
35726                        this: Box::new(arg),
35727                        scale: Some(3),
35728                        zone: None,
35729                        hours: None,
35730                        minutes: None,
35731                        format: None,
35732                        target_type: None,
35733                    },
35734                )))
35735            }
35736
35737            // TIMESTAMP_MICROS(x) -> UnixToTime with scale 6
35738            "TIMESTAMP_MICROS" if args.len() == 1 => {
35739                let arg = args.remove(0);
35740                Ok(Expression::UnixToTime(Box::new(
35741                    crate::expressions::UnixToTime {
35742                        this: Box::new(arg),
35743                        scale: Some(6),
35744                        zone: None,
35745                        hours: None,
35746                        minutes: None,
35747                        format: None,
35748                        target_type: None,
35749                    },
35750                )))
35751            }
35752
35753            // DIV(x, y) -> IntDiv expression
35754            "DIV" if args.len() == 2 => {
35755                let x = args.remove(0);
35756                let y = args.remove(0);
35757                Ok(Expression::IntDiv(Box::new(
35758                    crate::expressions::BinaryFunc {
35759                        this: x,
35760                        expression: y,
35761                        original_name: None,
35762                        inferred_type: None,
35763                    },
35764                )))
35765            }
35766
35767            // TO_HEX(x) -> target-specific form
35768            "TO_HEX" if args.len() == 1 => {
35769                let arg = args.remove(0);
35770                // Check if inner function already returns hex string in certain targets
35771                let inner_returns_hex = matches!(&arg, Expression::Function(f) if matches!(f.name.as_str(), "MD5" | "SHA1" | "SHA256" | "SHA512"));
35772                if matches!(target, DialectType::BigQuery) {
35773                    // BQ->BQ: keep as TO_HEX
35774                    Ok(Expression::Function(Box::new(Function::new(
35775                        "TO_HEX".to_string(),
35776                        vec![arg],
35777                    ))))
35778                } else if matches!(target, DialectType::DuckDB) && inner_returns_hex {
35779                    // DuckDB: MD5/SHA already return hex strings, so TO_HEX is redundant
35780                    Ok(arg)
35781                } else if matches!(target, DialectType::Snowflake) && inner_returns_hex {
35782                    // Snowflake: TO_HEX(SHA1(x)) -> TO_CHAR(SHA1_BINARY(x))
35783                    // TO_HEX(MD5(x)) -> TO_CHAR(MD5_BINARY(x))
35784                    // TO_HEX(SHA256(x)) -> TO_CHAR(SHA2_BINARY(x, 256))
35785                    // TO_HEX(SHA512(x)) -> TO_CHAR(SHA2_BINARY(x, 512))
35786                    if let Expression::Function(ref inner_f) = arg {
35787                        let inner_args = inner_f.args.clone();
35788                        let binary_func = match inner_f.name.to_ascii_uppercase().as_str() {
35789                            "SHA1" => Expression::Function(Box::new(Function::new(
35790                                "SHA1_BINARY".to_string(),
35791                                inner_args,
35792                            ))),
35793                            "MD5" => Expression::Function(Box::new(Function::new(
35794                                "MD5_BINARY".to_string(),
35795                                inner_args,
35796                            ))),
35797                            "SHA256" => {
35798                                let mut a = inner_args;
35799                                a.push(Expression::number(256));
35800                                Expression::Function(Box::new(Function::new(
35801                                    "SHA2_BINARY".to_string(),
35802                                    a,
35803                                )))
35804                            }
35805                            "SHA512" => {
35806                                let mut a = inner_args;
35807                                a.push(Expression::number(512));
35808                                Expression::Function(Box::new(Function::new(
35809                                    "SHA2_BINARY".to_string(),
35810                                    a,
35811                                )))
35812                            }
35813                            _ => arg.clone(),
35814                        };
35815                        Ok(Expression::Function(Box::new(Function::new(
35816                            "TO_CHAR".to_string(),
35817                            vec![binary_func],
35818                        ))))
35819                    } else {
35820                        let inner = Expression::Function(Box::new(Function::new(
35821                            "HEX".to_string(),
35822                            vec![arg],
35823                        )));
35824                        Ok(Expression::Lower(Box::new(
35825                            crate::expressions::UnaryFunc::new(inner),
35826                        )))
35827                    }
35828                } else if matches!(target, DialectType::Presto | DialectType::Trino) {
35829                    let inner = Expression::Function(Box::new(Function::new(
35830                        "TO_HEX".to_string(),
35831                        vec![arg],
35832                    )));
35833                    Ok(Expression::Lower(Box::new(
35834                        crate::expressions::UnaryFunc::new(inner),
35835                    )))
35836                } else {
35837                    let inner =
35838                        Expression::Function(Box::new(Function::new("HEX".to_string(), vec![arg])));
35839                    Ok(Expression::Lower(Box::new(
35840                        crate::expressions::UnaryFunc::new(inner),
35841                    )))
35842                }
35843            }
35844
35845            // LAST_DAY(date, unit) -> strip unit for most targets, or transform for PostgreSQL
35846            "LAST_DAY" if args.len() == 2 => {
35847                let date = args.remove(0);
35848                let _unit = args.remove(0); // Strip the unit (MONTH is default)
35849                Ok(Expression::Function(Box::new(Function::new(
35850                    "LAST_DAY".to_string(),
35851                    vec![date],
35852                ))))
35853            }
35854
35855            // GENERATE_ARRAY(start, end, step?) -> GenerateSeries expression
35856            "GENERATE_ARRAY" => {
35857                let start = args.get(0).cloned();
35858                let end = args.get(1).cloned();
35859                let step = args.get(2).cloned();
35860                Ok(Expression::GenerateSeries(Box::new(
35861                    crate::expressions::GenerateSeries {
35862                        start: start.map(Box::new),
35863                        end: end.map(Box::new),
35864                        step: step.map(Box::new),
35865                        is_end_exclusive: None,
35866                    },
35867                )))
35868            }
35869
35870            // GENERATE_TIMESTAMP_ARRAY(start, end, step) -> GenerateSeries expression
35871            "GENERATE_TIMESTAMP_ARRAY" => {
35872                let start = args.get(0).cloned();
35873                let end = args.get(1).cloned();
35874                let step = args.get(2).cloned();
35875
35876                if matches!(target, DialectType::DuckDB) {
35877                    // DuckDB: GENERATE_SERIES(CAST(start AS TIMESTAMP), CAST(end AS TIMESTAMP), step)
35878                    // Only cast string literals - leave columns/expressions as-is
35879                    let maybe_cast_ts = |expr: Expression| -> Expression {
35880                        if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
35881                        {
35882                            Expression::Cast(Box::new(Cast {
35883                                this: expr,
35884                                to: DataType::Timestamp {
35885                                    precision: None,
35886                                    timezone: false,
35887                                },
35888                                trailing_comments: vec![],
35889                                double_colon_syntax: false,
35890                                format: None,
35891                                default: None,
35892                                inferred_type: None,
35893                            }))
35894                        } else {
35895                            expr
35896                        }
35897                    };
35898                    let cast_start = start.map(maybe_cast_ts);
35899                    let cast_end = end.map(maybe_cast_ts);
35900                    Ok(Expression::GenerateSeries(Box::new(
35901                        crate::expressions::GenerateSeries {
35902                            start: cast_start.map(Box::new),
35903                            end: cast_end.map(Box::new),
35904                            step: step.map(Box::new),
35905                            is_end_exclusive: None,
35906                        },
35907                    )))
35908                } else {
35909                    Ok(Expression::GenerateSeries(Box::new(
35910                        crate::expressions::GenerateSeries {
35911                            start: start.map(Box::new),
35912                            end: end.map(Box::new),
35913                            step: step.map(Box::new),
35914                            is_end_exclusive: None,
35915                        },
35916                    )))
35917                }
35918            }
35919
35920            // TO_JSON(x) -> target-specific (from Spark/Hive)
35921            "TO_JSON" => {
35922                match target {
35923                    DialectType::Presto | DialectType::Trino => {
35924                        // JSON_FORMAT(CAST(x AS JSON))
35925                        let arg = args
35926                            .into_iter()
35927                            .next()
35928                            .unwrap_or(Expression::Null(crate::expressions::Null));
35929                        let cast_json = Expression::Cast(Box::new(Cast {
35930                            this: arg,
35931                            to: DataType::Custom {
35932                                name: "JSON".to_string(),
35933                            },
35934                            trailing_comments: vec![],
35935                            double_colon_syntax: false,
35936                            format: None,
35937                            default: None,
35938                            inferred_type: None,
35939                        }));
35940                        Ok(Expression::Function(Box::new(Function::new(
35941                            "JSON_FORMAT".to_string(),
35942                            vec![cast_json],
35943                        ))))
35944                    }
35945                    DialectType::BigQuery => Ok(Expression::Function(Box::new(Function::new(
35946                        "TO_JSON_STRING".to_string(),
35947                        args,
35948                    )))),
35949                    DialectType::DuckDB => {
35950                        // CAST(TO_JSON(x) AS TEXT)
35951                        let arg = args
35952                            .into_iter()
35953                            .next()
35954                            .unwrap_or(Expression::Null(crate::expressions::Null));
35955                        let to_json = Expression::Function(Box::new(Function::new(
35956                            "TO_JSON".to_string(),
35957                            vec![arg],
35958                        )));
35959                        Ok(Expression::Cast(Box::new(Cast {
35960                            this: to_json,
35961                            to: DataType::Text,
35962                            trailing_comments: vec![],
35963                            double_colon_syntax: false,
35964                            format: None,
35965                            default: None,
35966                            inferred_type: None,
35967                        })))
35968                    }
35969                    _ => Ok(Expression::Function(Box::new(Function::new(
35970                        "TO_JSON".to_string(),
35971                        args,
35972                    )))),
35973                }
35974            }
35975
35976            // TO_JSON_STRING(x) -> target-specific
35977            "TO_JSON_STRING" => {
35978                match target {
35979                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => Ok(
35980                        Expression::Function(Box::new(Function::new("TO_JSON".to_string(), args))),
35981                    ),
35982                    DialectType::Presto | DialectType::Trino => {
35983                        // JSON_FORMAT(CAST(x AS JSON))
35984                        let arg = args
35985                            .into_iter()
35986                            .next()
35987                            .unwrap_or(Expression::Null(crate::expressions::Null));
35988                        let cast_json = Expression::Cast(Box::new(Cast {
35989                            this: arg,
35990                            to: DataType::Custom {
35991                                name: "JSON".to_string(),
35992                            },
35993                            trailing_comments: vec![],
35994                            double_colon_syntax: false,
35995                            format: None,
35996                            default: None,
35997                            inferred_type: None,
35998                        }));
35999                        Ok(Expression::Function(Box::new(Function::new(
36000                            "JSON_FORMAT".to_string(),
36001                            vec![cast_json],
36002                        ))))
36003                    }
36004                    DialectType::DuckDB => {
36005                        // CAST(TO_JSON(x) AS TEXT)
36006                        let arg = args
36007                            .into_iter()
36008                            .next()
36009                            .unwrap_or(Expression::Null(crate::expressions::Null));
36010                        let to_json = Expression::Function(Box::new(Function::new(
36011                            "TO_JSON".to_string(),
36012                            vec![arg],
36013                        )));
36014                        Ok(Expression::Cast(Box::new(Cast {
36015                            this: to_json,
36016                            to: DataType::Text,
36017                            trailing_comments: vec![],
36018                            double_colon_syntax: false,
36019                            format: None,
36020                            default: None,
36021                            inferred_type: None,
36022                        })))
36023                    }
36024                    DialectType::Snowflake => {
36025                        // TO_JSON(x)
36026                        Ok(Expression::Function(Box::new(Function::new(
36027                            "TO_JSON".to_string(),
36028                            args,
36029                        ))))
36030                    }
36031                    _ => Ok(Expression::Function(Box::new(Function::new(
36032                        "TO_JSON_STRING".to_string(),
36033                        args,
36034                    )))),
36035                }
36036            }
36037
36038            // SAFE_ADD(x, y) -> SafeAdd expression
36039            "SAFE_ADD" if args.len() == 2 => {
36040                let x = args.remove(0);
36041                let y = args.remove(0);
36042                Ok(Expression::SafeAdd(Box::new(crate::expressions::SafeAdd {
36043                    this: Box::new(x),
36044                    expression: Box::new(y),
36045                })))
36046            }
36047
36048            // SAFE_SUBTRACT(x, y) -> SafeSubtract expression
36049            "SAFE_SUBTRACT" if args.len() == 2 => {
36050                let x = args.remove(0);
36051                let y = args.remove(0);
36052                Ok(Expression::SafeSubtract(Box::new(
36053                    crate::expressions::SafeSubtract {
36054                        this: Box::new(x),
36055                        expression: Box::new(y),
36056                    },
36057                )))
36058            }
36059
36060            // SAFE_MULTIPLY(x, y) -> SafeMultiply expression
36061            "SAFE_MULTIPLY" if args.len() == 2 => {
36062                let x = args.remove(0);
36063                let y = args.remove(0);
36064                Ok(Expression::SafeMultiply(Box::new(
36065                    crate::expressions::SafeMultiply {
36066                        this: Box::new(x),
36067                        expression: Box::new(y),
36068                    },
36069                )))
36070            }
36071
36072            // REGEXP_CONTAINS(str, pattern) -> RegexpLike expression
36073            "REGEXP_CONTAINS" if args.len() == 2 => {
36074                let str_expr = args.remove(0);
36075                let pattern = args.remove(0);
36076                Ok(Expression::RegexpLike(Box::new(
36077                    crate::expressions::RegexpFunc {
36078                        this: str_expr,
36079                        pattern,
36080                        flags: None,
36081                    },
36082                )))
36083            }
36084
36085            // CONTAINS_SUBSTR(a, b) -> CONTAINS(LOWER(a), LOWER(b))
36086            "CONTAINS_SUBSTR" if args.len() == 2 => {
36087                let a = args.remove(0);
36088                let b = args.remove(0);
36089                let lower_a = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(a)));
36090                let lower_b = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(b)));
36091                Ok(Expression::Function(Box::new(Function::new(
36092                    "CONTAINS".to_string(),
36093                    vec![lower_a, lower_b],
36094                ))))
36095            }
36096
36097            // INT64(x) -> CAST(x AS BIGINT)
36098            "INT64" if args.len() == 1 => {
36099                let arg = args.remove(0);
36100                Ok(Expression::Cast(Box::new(Cast {
36101                    this: arg,
36102                    to: DataType::BigInt { length: None },
36103                    trailing_comments: vec![],
36104                    double_colon_syntax: false,
36105                    format: None,
36106                    default: None,
36107                    inferred_type: None,
36108                })))
36109            }
36110
36111            // INSTR(str, substr) -> target-specific
36112            "INSTR" if args.len() >= 2 => {
36113                let str_expr = args.remove(0);
36114                let substr = args.remove(0);
36115                if matches!(target, DialectType::Snowflake) {
36116                    // CHARINDEX(substr, str)
36117                    Ok(Expression::Function(Box::new(Function::new(
36118                        "CHARINDEX".to_string(),
36119                        vec![substr, str_expr],
36120                    ))))
36121                } else if matches!(target, DialectType::BigQuery) {
36122                    // Keep as INSTR
36123                    Ok(Expression::Function(Box::new(Function::new(
36124                        "INSTR".to_string(),
36125                        vec![str_expr, substr],
36126                    ))))
36127                } else {
36128                    // Default: keep as INSTR
36129                    Ok(Expression::Function(Box::new(Function::new(
36130                        "INSTR".to_string(),
36131                        vec![str_expr, substr],
36132                    ))))
36133                }
36134            }
36135
36136            // BigQuery DATE_TRUNC(expr, unit) -> DATE_TRUNC('unit', expr) for standard SQL
36137            "DATE_TRUNC" if args.len() == 2 => {
36138                let expr = args.remove(0);
36139                let unit_expr = args.remove(0);
36140                let unit_str = get_unit_str(&unit_expr);
36141
36142                match target {
36143                    DialectType::DuckDB
36144                    | DialectType::Snowflake
36145                    | DialectType::PostgreSQL
36146                    | DialectType::Presto
36147                    | DialectType::Trino
36148                    | DialectType::Databricks
36149                    | DialectType::Spark
36150                    | DialectType::Redshift
36151                    | DialectType::ClickHouse
36152                    | DialectType::TSQL => {
36153                        // Standard: DATE_TRUNC('UNIT', expr)
36154                        Ok(Expression::Function(Box::new(Function::new(
36155                            "DATE_TRUNC".to_string(),
36156                            vec![
36157                                Expression::Literal(Box::new(Literal::String(unit_str))),
36158                                expr,
36159                            ],
36160                        ))))
36161                    }
36162                    _ => {
36163                        // Keep BigQuery arg order: DATE_TRUNC(expr, unit)
36164                        Ok(Expression::Function(Box::new(Function::new(
36165                            "DATE_TRUNC".to_string(),
36166                            vec![expr, unit_expr],
36167                        ))))
36168                    }
36169                }
36170            }
36171
36172            // TIMESTAMP_TRUNC / DATETIME_TRUNC -> target-specific
36173            "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" if args.len() >= 2 => {
36174                // TIMESTAMP_TRUNC(ts, unit) or TIMESTAMP_TRUNC(ts, unit, timezone)
36175                let ts = args.remove(0);
36176                let unit_expr = args.remove(0);
36177                let tz = if !args.is_empty() {
36178                    Some(args.remove(0))
36179                } else {
36180                    None
36181                };
36182                let unit_str = get_unit_str(&unit_expr);
36183
36184                match target {
36185                    DialectType::DuckDB => {
36186                        // DuckDB: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
36187                        // With timezone: DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz' (for DAY granularity)
36188                        // Without timezone for MINUTE+ granularity: just DATE_TRUNC
36189                        let is_coarse = matches!(
36190                            unit_str.as_str(),
36191                            "DAY" | "WEEK" | "MONTH" | "QUARTER" | "YEAR"
36192                        );
36193                        // For DATETIME_TRUNC, cast string args to TIMESTAMP
36194                        let cast_ts = if name == "DATETIME_TRUNC" {
36195                            match ts {
36196                                Expression::Literal(ref lit)
36197                                    if matches!(lit.as_ref(), Literal::String(ref _s)) =>
36198                                {
36199                                    Expression::Cast(Box::new(Cast {
36200                                        this: ts,
36201                                        to: DataType::Timestamp {
36202                                            precision: None,
36203                                            timezone: false,
36204                                        },
36205                                        trailing_comments: vec![],
36206                                        double_colon_syntax: false,
36207                                        format: None,
36208                                        default: None,
36209                                        inferred_type: None,
36210                                    }))
36211                                }
36212                                _ => Self::maybe_cast_ts_to_tz(ts, &name),
36213                            }
36214                        } else {
36215                            Self::maybe_cast_ts_to_tz(ts, &name)
36216                        };
36217
36218                        if let Some(tz_arg) = tz {
36219                            if is_coarse {
36220                                // DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz'
36221                                let at_tz = Expression::AtTimeZone(Box::new(
36222                                    crate::expressions::AtTimeZone {
36223                                        this: cast_ts,
36224                                        zone: tz_arg.clone(),
36225                                    },
36226                                ));
36227                                let date_trunc = Expression::Function(Box::new(Function::new(
36228                                    "DATE_TRUNC".to_string(),
36229                                    vec![
36230                                        Expression::Literal(Box::new(Literal::String(unit_str))),
36231                                        at_tz,
36232                                    ],
36233                                )));
36234                                Ok(Expression::AtTimeZone(Box::new(
36235                                    crate::expressions::AtTimeZone {
36236                                        this: date_trunc,
36237                                        zone: tz_arg,
36238                                    },
36239                                )))
36240                            } else {
36241                                // For MINUTE/HOUR: no AT TIME ZONE wrapper, just DATE_TRUNC('UNIT', ts)
36242                                Ok(Expression::Function(Box::new(Function::new(
36243                                    "DATE_TRUNC".to_string(),
36244                                    vec![
36245                                        Expression::Literal(Box::new(Literal::String(unit_str))),
36246                                        cast_ts,
36247                                    ],
36248                                ))))
36249                            }
36250                        } else {
36251                            // No timezone: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
36252                            Ok(Expression::Function(Box::new(Function::new(
36253                                "DATE_TRUNC".to_string(),
36254                                vec![
36255                                    Expression::Literal(Box::new(Literal::String(unit_str))),
36256                                    cast_ts,
36257                                ],
36258                            ))))
36259                        }
36260                    }
36261                    DialectType::Databricks | DialectType::Spark => {
36262                        // Databricks/Spark: DATE_TRUNC('UNIT', ts)
36263                        Ok(Expression::Function(Box::new(Function::new(
36264                            "DATE_TRUNC".to_string(),
36265                            vec![Expression::Literal(Box::new(Literal::String(unit_str))), ts],
36266                        ))))
36267                    }
36268                    _ => {
36269                        // Default: keep as TIMESTAMP_TRUNC('UNIT', ts, [tz])
36270                        let unit = Expression::Literal(Box::new(Literal::String(unit_str)));
36271                        let mut date_trunc_args = vec![unit, ts];
36272                        if let Some(tz_arg) = tz {
36273                            date_trunc_args.push(tz_arg);
36274                        }
36275                        Ok(Expression::Function(Box::new(Function::new(
36276                            "TIMESTAMP_TRUNC".to_string(),
36277                            date_trunc_args,
36278                        ))))
36279                    }
36280                }
36281            }
36282
36283            // TIME(h, m, s) -> target-specific, TIME('string') -> CAST('string' AS TIME)
36284            "TIME" => {
36285                if args.len() == 3 {
36286                    // TIME(h, m, s) constructor
36287                    match target {
36288                        DialectType::TSQL => {
36289                            // TIMEFROMPARTS(h, m, s, 0, 0)
36290                            args.push(Expression::number(0));
36291                            args.push(Expression::number(0));
36292                            Ok(Expression::Function(Box::new(Function::new(
36293                                "TIMEFROMPARTS".to_string(),
36294                                args,
36295                            ))))
36296                        }
36297                        DialectType::MySQL => Ok(Expression::Function(Box::new(Function::new(
36298                            "MAKETIME".to_string(),
36299                            args,
36300                        )))),
36301                        DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
36302                            Function::new("MAKE_TIME".to_string(), args),
36303                        ))),
36304                        _ => Ok(Expression::Function(Box::new(Function::new(
36305                            "TIME".to_string(),
36306                            args,
36307                        )))),
36308                    }
36309                } else if args.len() == 1 {
36310                    let arg = args.remove(0);
36311                    if matches!(target, DialectType::Spark) {
36312                        // Spark: CAST(x AS TIMESTAMP) (yes, TIMESTAMP not TIME)
36313                        Ok(Expression::Cast(Box::new(Cast {
36314                            this: arg,
36315                            to: DataType::Timestamp {
36316                                timezone: false,
36317                                precision: None,
36318                            },
36319                            trailing_comments: vec![],
36320                            double_colon_syntax: false,
36321                            format: None,
36322                            default: None,
36323                            inferred_type: None,
36324                        })))
36325                    } else {
36326                        // Most targets: CAST(x AS TIME)
36327                        Ok(Expression::Cast(Box::new(Cast {
36328                            this: arg,
36329                            to: DataType::Time {
36330                                precision: None,
36331                                timezone: false,
36332                            },
36333                            trailing_comments: vec![],
36334                            double_colon_syntax: false,
36335                            format: None,
36336                            default: None,
36337                            inferred_type: None,
36338                        })))
36339                    }
36340                } else if args.len() == 2 {
36341                    // TIME(expr, timezone) -> CAST(CAST(expr AS TIMESTAMPTZ) AT TIME ZONE tz AS TIME)
36342                    let expr = args.remove(0);
36343                    let tz = args.remove(0);
36344                    let cast_tstz = Expression::Cast(Box::new(Cast {
36345                        this: expr,
36346                        to: DataType::Timestamp {
36347                            timezone: true,
36348                            precision: None,
36349                        },
36350                        trailing_comments: vec![],
36351                        double_colon_syntax: false,
36352                        format: None,
36353                        default: None,
36354                        inferred_type: None,
36355                    }));
36356                    let at_tz = Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
36357                        this: cast_tstz,
36358                        zone: tz,
36359                    }));
36360                    Ok(Expression::Cast(Box::new(Cast {
36361                        this: at_tz,
36362                        to: DataType::Time {
36363                            precision: None,
36364                            timezone: false,
36365                        },
36366                        trailing_comments: vec![],
36367                        double_colon_syntax: false,
36368                        format: None,
36369                        default: None,
36370                        inferred_type: None,
36371                    })))
36372                } else {
36373                    Ok(Expression::Function(Box::new(Function::new(
36374                        "TIME".to_string(),
36375                        args,
36376                    ))))
36377                }
36378            }
36379
36380            // DATETIME('string') -> CAST('string' AS TIMESTAMP)
36381            // DATETIME('date', TIME 'time') -> CAST(CAST('date' AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
36382            // DATETIME('string', 'timezone') -> CAST(CAST('string' AS TIMESTAMPTZ) AT TIME ZONE tz AS TIMESTAMP)
36383            // DATETIME(y, m, d, h, min, s) -> target-specific
36384            "DATETIME" => {
36385                // For BigQuery target: keep DATETIME function but convert TIME literal to CAST
36386                if matches!(target, DialectType::BigQuery) {
36387                    if args.len() == 2 {
36388                        let has_time_literal = matches!(&args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
36389                        if has_time_literal {
36390                            let first = args.remove(0);
36391                            let second = args.remove(0);
36392                            let time_as_cast = match second {
36393                                Expression::Literal(lit)
36394                                    if matches!(lit.as_ref(), Literal::Time(_)) =>
36395                                {
36396                                    let Literal::Time(s) = lit.as_ref() else {
36397                                        unreachable!()
36398                                    };
36399                                    Expression::Cast(Box::new(Cast {
36400                                        this: Expression::Literal(Box::new(Literal::String(
36401                                            s.clone(),
36402                                        ))),
36403                                        to: DataType::Time {
36404                                            precision: None,
36405                                            timezone: false,
36406                                        },
36407                                        trailing_comments: vec![],
36408                                        double_colon_syntax: false,
36409                                        format: None,
36410                                        default: None,
36411                                        inferred_type: None,
36412                                    }))
36413                                }
36414                                other => other,
36415                            };
36416                            return Ok(Expression::Function(Box::new(Function::new(
36417                                "DATETIME".to_string(),
36418                                vec![first, time_as_cast],
36419                            ))));
36420                        }
36421                    }
36422                    return Ok(Expression::Function(Box::new(Function::new(
36423                        "DATETIME".to_string(),
36424                        args,
36425                    ))));
36426                }
36427
36428                if args.len() == 1 {
36429                    let arg = args.remove(0);
36430                    Ok(Expression::Cast(Box::new(Cast {
36431                        this: arg,
36432                        to: DataType::Timestamp {
36433                            timezone: false,
36434                            precision: None,
36435                        },
36436                        trailing_comments: vec![],
36437                        double_colon_syntax: false,
36438                        format: None,
36439                        default: None,
36440                        inferred_type: None,
36441                    })))
36442                } else if args.len() == 2 {
36443                    let first = args.remove(0);
36444                    let second = args.remove(0);
36445                    // Check if second arg is a TIME literal
36446                    let is_time_literal = matches!(&second, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
36447                    if is_time_literal {
36448                        // DATETIME('date', TIME 'time') -> CAST(CAST(date AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
36449                        let cast_date = Expression::Cast(Box::new(Cast {
36450                            this: first,
36451                            to: DataType::Date,
36452                            trailing_comments: vec![],
36453                            double_colon_syntax: false,
36454                            format: None,
36455                            default: None,
36456                            inferred_type: None,
36457                        }));
36458                        // Convert TIME 'x' literal to string 'x' so CAST produces CAST('x' AS TIME) not CAST(TIME 'x' AS TIME)
36459                        let time_as_string = match second {
36460                            Expression::Literal(lit)
36461                                if matches!(lit.as_ref(), Literal::Time(_)) =>
36462                            {
36463                                let Literal::Time(s) = lit.as_ref() else {
36464                                    unreachable!()
36465                                };
36466                                Expression::Literal(Box::new(Literal::String(s.clone())))
36467                            }
36468                            other => other,
36469                        };
36470                        let cast_time = Expression::Cast(Box::new(Cast {
36471                            this: time_as_string,
36472                            to: DataType::Time {
36473                                precision: None,
36474                                timezone: false,
36475                            },
36476                            trailing_comments: vec![],
36477                            double_colon_syntax: false,
36478                            format: None,
36479                            default: None,
36480                            inferred_type: None,
36481                        }));
36482                        let add_expr =
36483                            Expression::Add(Box::new(BinaryOp::new(cast_date, cast_time)));
36484                        Ok(Expression::Cast(Box::new(Cast {
36485                            this: add_expr,
36486                            to: DataType::Timestamp {
36487                                timezone: false,
36488                                precision: None,
36489                            },
36490                            trailing_comments: vec![],
36491                            double_colon_syntax: false,
36492                            format: None,
36493                            default: None,
36494                            inferred_type: None,
36495                        })))
36496                    } else {
36497                        // DATETIME('string', 'timezone')
36498                        let cast_tstz = Expression::Cast(Box::new(Cast {
36499                            this: first,
36500                            to: DataType::Timestamp {
36501                                timezone: true,
36502                                precision: None,
36503                            },
36504                            trailing_comments: vec![],
36505                            double_colon_syntax: false,
36506                            format: None,
36507                            default: None,
36508                            inferred_type: None,
36509                        }));
36510                        let at_tz =
36511                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
36512                                this: cast_tstz,
36513                                zone: second,
36514                            }));
36515                        Ok(Expression::Cast(Box::new(Cast {
36516                            this: at_tz,
36517                            to: DataType::Timestamp {
36518                                timezone: false,
36519                                precision: None,
36520                            },
36521                            trailing_comments: vec![],
36522                            double_colon_syntax: false,
36523                            format: None,
36524                            default: None,
36525                            inferred_type: None,
36526                        })))
36527                    }
36528                } else if args.len() >= 3 {
36529                    // DATETIME(y, m, d, h, min, s) -> TIMESTAMP_FROM_PARTS for Snowflake
36530                    // For other targets, use MAKE_TIMESTAMP or similar
36531                    if matches!(target, DialectType::Snowflake) {
36532                        Ok(Expression::Function(Box::new(Function::new(
36533                            "TIMESTAMP_FROM_PARTS".to_string(),
36534                            args,
36535                        ))))
36536                    } else {
36537                        Ok(Expression::Function(Box::new(Function::new(
36538                            "DATETIME".to_string(),
36539                            args,
36540                        ))))
36541                    }
36542                } else {
36543                    Ok(Expression::Function(Box::new(Function::new(
36544                        "DATETIME".to_string(),
36545                        args,
36546                    ))))
36547                }
36548            }
36549
36550            // TIMESTAMP(x) -> CAST(x AS TIMESTAMP WITH TIME ZONE) for Presto
36551            // TIMESTAMP(x, tz) -> CAST(x AS TIMESTAMP) AT TIME ZONE tz for DuckDB
36552            "TIMESTAMP" => {
36553                if args.len() == 1 {
36554                    let arg = args.remove(0);
36555                    Ok(Expression::Cast(Box::new(Cast {
36556                        this: arg,
36557                        to: DataType::Timestamp {
36558                            timezone: true,
36559                            precision: None,
36560                        },
36561                        trailing_comments: vec![],
36562                        double_colon_syntax: false,
36563                        format: None,
36564                        default: None,
36565                        inferred_type: None,
36566                    })))
36567                } else if args.len() == 2 {
36568                    let arg = args.remove(0);
36569                    let tz = args.remove(0);
36570                    let cast_ts = Expression::Cast(Box::new(Cast {
36571                        this: arg,
36572                        to: DataType::Timestamp {
36573                            timezone: false,
36574                            precision: None,
36575                        },
36576                        trailing_comments: vec![],
36577                        double_colon_syntax: false,
36578                        format: None,
36579                        default: None,
36580                        inferred_type: None,
36581                    }));
36582                    if matches!(target, DialectType::Snowflake) {
36583                        // CONVERT_TIMEZONE('tz', CAST(x AS TIMESTAMP))
36584                        Ok(Expression::Function(Box::new(Function::new(
36585                            "CONVERT_TIMEZONE".to_string(),
36586                            vec![tz, cast_ts],
36587                        ))))
36588                    } else {
36589                        Ok(Expression::AtTimeZone(Box::new(
36590                            crate::expressions::AtTimeZone {
36591                                this: cast_ts,
36592                                zone: tz,
36593                            },
36594                        )))
36595                    }
36596                } else {
36597                    Ok(Expression::Function(Box::new(Function::new(
36598                        "TIMESTAMP".to_string(),
36599                        args,
36600                    ))))
36601                }
36602            }
36603
36604            // STRING(x) -> CAST(x AS VARCHAR/TEXT)
36605            // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS VARCHAR/TEXT)
36606            "STRING" => {
36607                if args.len() == 1 {
36608                    let arg = args.remove(0);
36609                    let cast_type = match target {
36610                        DialectType::DuckDB => DataType::Text,
36611                        _ => DataType::VarChar {
36612                            length: None,
36613                            parenthesized_length: false,
36614                        },
36615                    };
36616                    Ok(Expression::Cast(Box::new(Cast {
36617                        this: arg,
36618                        to: cast_type,
36619                        trailing_comments: vec![],
36620                        double_colon_syntax: false,
36621                        format: None,
36622                        default: None,
36623                        inferred_type: None,
36624                    })))
36625                } else if args.len() == 2 {
36626                    let arg = args.remove(0);
36627                    let tz = args.remove(0);
36628                    let cast_type = match target {
36629                        DialectType::DuckDB => DataType::Text,
36630                        _ => DataType::VarChar {
36631                            length: None,
36632                            parenthesized_length: false,
36633                        },
36634                    };
36635                    if matches!(target, DialectType::Snowflake) {
36636                        // STRING(x, tz) -> CAST(CONVERT_TIMEZONE('UTC', tz, x) AS VARCHAR)
36637                        let convert_tz = Expression::Function(Box::new(Function::new(
36638                            "CONVERT_TIMEZONE".to_string(),
36639                            vec![
36640                                Expression::Literal(Box::new(Literal::String("UTC".to_string()))),
36641                                tz,
36642                                arg,
36643                            ],
36644                        )));
36645                        Ok(Expression::Cast(Box::new(Cast {
36646                            this: convert_tz,
36647                            to: cast_type,
36648                            trailing_comments: vec![],
36649                            double_colon_syntax: false,
36650                            format: None,
36651                            default: None,
36652                            inferred_type: None,
36653                        })))
36654                    } else {
36655                        // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS TEXT/VARCHAR)
36656                        let cast_ts = Expression::Cast(Box::new(Cast {
36657                            this: arg,
36658                            to: DataType::Timestamp {
36659                                timezone: false,
36660                                precision: None,
36661                            },
36662                            trailing_comments: vec![],
36663                            double_colon_syntax: false,
36664                            format: None,
36665                            default: None,
36666                            inferred_type: None,
36667                        }));
36668                        let at_utc =
36669                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
36670                                this: cast_ts,
36671                                zone: Expression::Literal(Box::new(Literal::String(
36672                                    "UTC".to_string(),
36673                                ))),
36674                            }));
36675                        let at_tz =
36676                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
36677                                this: at_utc,
36678                                zone: tz,
36679                            }));
36680                        Ok(Expression::Cast(Box::new(Cast {
36681                            this: at_tz,
36682                            to: cast_type,
36683                            trailing_comments: vec![],
36684                            double_colon_syntax: false,
36685                            format: None,
36686                            default: None,
36687                            inferred_type: None,
36688                        })))
36689                    }
36690                } else {
36691                    Ok(Expression::Function(Box::new(Function::new(
36692                        "STRING".to_string(),
36693                        args,
36694                    ))))
36695                }
36696            }
36697
36698            // UNIX_SECONDS, UNIX_MILLIS, UNIX_MICROS as functions (not expressions)
36699            "UNIX_SECONDS" if args.len() == 1 => {
36700                let ts = args.remove(0);
36701                match target {
36702                    DialectType::DuckDB => {
36703                        // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
36704                        let cast_ts = Self::ensure_cast_timestamptz(ts);
36705                        let epoch = Expression::Function(Box::new(Function::new(
36706                            "EPOCH".to_string(),
36707                            vec![cast_ts],
36708                        )));
36709                        Ok(Expression::Cast(Box::new(Cast {
36710                            this: epoch,
36711                            to: DataType::BigInt { length: None },
36712                            trailing_comments: vec![],
36713                            double_colon_syntax: false,
36714                            format: None,
36715                            default: None,
36716                            inferred_type: None,
36717                        })))
36718                    }
36719                    DialectType::Snowflake => {
36720                        // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
36721                        let epoch = Expression::Cast(Box::new(Cast {
36722                            this: Expression::Literal(Box::new(Literal::String(
36723                                "1970-01-01 00:00:00+00".to_string(),
36724                            ))),
36725                            to: DataType::Timestamp {
36726                                timezone: true,
36727                                precision: None,
36728                            },
36729                            trailing_comments: vec![],
36730                            double_colon_syntax: false,
36731                            format: None,
36732                            default: None,
36733                            inferred_type: None,
36734                        }));
36735                        Ok(Expression::TimestampDiff(Box::new(
36736                            crate::expressions::TimestampDiff {
36737                                this: Box::new(epoch),
36738                                expression: Box::new(ts),
36739                                unit: Some("SECONDS".to_string()),
36740                            },
36741                        )))
36742                    }
36743                    _ => Ok(Expression::Function(Box::new(Function::new(
36744                        "UNIX_SECONDS".to_string(),
36745                        vec![ts],
36746                    )))),
36747                }
36748            }
36749
36750            "UNIX_MILLIS" if args.len() == 1 => {
36751                let ts = args.remove(0);
36752                match target {
36753                    DialectType::DuckDB => {
36754                        // EPOCH_MS(CAST(ts AS TIMESTAMPTZ))
36755                        let cast_ts = Self::ensure_cast_timestamptz(ts);
36756                        Ok(Expression::Function(Box::new(Function::new(
36757                            "EPOCH_MS".to_string(),
36758                            vec![cast_ts],
36759                        ))))
36760                    }
36761                    _ => Ok(Expression::Function(Box::new(Function::new(
36762                        "UNIX_MILLIS".to_string(),
36763                        vec![ts],
36764                    )))),
36765                }
36766            }
36767
36768            "UNIX_MICROS" if args.len() == 1 => {
36769                let ts = args.remove(0);
36770                match target {
36771                    DialectType::DuckDB => {
36772                        // EPOCH_US(CAST(ts AS TIMESTAMPTZ))
36773                        let cast_ts = Self::ensure_cast_timestamptz(ts);
36774                        Ok(Expression::Function(Box::new(Function::new(
36775                            "EPOCH_US".to_string(),
36776                            vec![cast_ts],
36777                        ))))
36778                    }
36779                    _ => Ok(Expression::Function(Box::new(Function::new(
36780                        "UNIX_MICROS".to_string(),
36781                        vec![ts],
36782                    )))),
36783                }
36784            }
36785
36786            // ARRAY_CONCAT / LIST_CONCAT -> target-specific
36787            "ARRAY_CONCAT" | "LIST_CONCAT" => {
36788                match target {
36789                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
36790                        // CONCAT(arr1, arr2, ...)
36791                        Ok(Expression::Function(Box::new(Function::new(
36792                            "CONCAT".to_string(),
36793                            args,
36794                        ))))
36795                    }
36796                    DialectType::Presto | DialectType::Trino => {
36797                        // CONCAT(arr1, arr2, ...)
36798                        Ok(Expression::Function(Box::new(Function::new(
36799                            "CONCAT".to_string(),
36800                            args,
36801                        ))))
36802                    }
36803                    DialectType::Snowflake => {
36804                        // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
36805                        if args.len() == 1 {
36806                            // ARRAY_CAT requires 2 args, add empty array as []
36807                            let empty_arr = Expression::ArrayFunc(Box::new(
36808                                crate::expressions::ArrayConstructor {
36809                                    expressions: vec![],
36810                                    bracket_notation: true,
36811                                    use_list_keyword: false,
36812                                },
36813                            ));
36814                            let mut new_args = args;
36815                            new_args.push(empty_arr);
36816                            Ok(Expression::Function(Box::new(Function::new(
36817                                "ARRAY_CAT".to_string(),
36818                                new_args,
36819                            ))))
36820                        } else if args.is_empty() {
36821                            Ok(Expression::Function(Box::new(Function::new(
36822                                "ARRAY_CAT".to_string(),
36823                                args,
36824                            ))))
36825                        } else {
36826                            let mut it = args.into_iter().rev();
36827                            let mut result = it.next().unwrap();
36828                            for arr in it {
36829                                result = Expression::Function(Box::new(Function::new(
36830                                    "ARRAY_CAT".to_string(),
36831                                    vec![arr, result],
36832                                )));
36833                            }
36834                            Ok(result)
36835                        }
36836                    }
36837                    DialectType::PostgreSQL => {
36838                        // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
36839                        if args.len() <= 1 {
36840                            Ok(Expression::Function(Box::new(Function::new(
36841                                "ARRAY_CAT".to_string(),
36842                                args,
36843                            ))))
36844                        } else {
36845                            let mut it = args.into_iter().rev();
36846                            let mut result = it.next().unwrap();
36847                            for arr in it {
36848                                result = Expression::Function(Box::new(Function::new(
36849                                    "ARRAY_CAT".to_string(),
36850                                    vec![arr, result],
36851                                )));
36852                            }
36853                            Ok(result)
36854                        }
36855                    }
36856                    DialectType::Redshift => {
36857                        // ARRAY_CONCAT(arr1, ARRAY_CONCAT(arr2, arr3))
36858                        if args.len() <= 2 {
36859                            Ok(Expression::Function(Box::new(Function::new(
36860                                "ARRAY_CONCAT".to_string(),
36861                                args,
36862                            ))))
36863                        } else {
36864                            let mut it = args.into_iter().rev();
36865                            let mut result = it.next().unwrap();
36866                            for arr in it {
36867                                result = Expression::Function(Box::new(Function::new(
36868                                    "ARRAY_CONCAT".to_string(),
36869                                    vec![arr, result],
36870                                )));
36871                            }
36872                            Ok(result)
36873                        }
36874                    }
36875                    DialectType::DuckDB => {
36876                        // LIST_CONCAT supports multiple args natively in DuckDB
36877                        Ok(Expression::Function(Box::new(Function::new(
36878                            "LIST_CONCAT".to_string(),
36879                            args,
36880                        ))))
36881                    }
36882                    _ => Ok(Expression::Function(Box::new(Function::new(
36883                        "ARRAY_CONCAT".to_string(),
36884                        args,
36885                    )))),
36886                }
36887            }
36888
36889            // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(x))
36890            "ARRAY_CONCAT_AGG" if args.len() == 1 => {
36891                let arg = args.remove(0);
36892                match target {
36893                    DialectType::Snowflake => {
36894                        let array_agg =
36895                            Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
36896                                this: arg,
36897                                distinct: false,
36898                                filter: None,
36899                                order_by: vec![],
36900                                name: None,
36901                                ignore_nulls: None,
36902                                having_max: None,
36903                                limit: None,
36904                                inferred_type: None,
36905                            }));
36906                        Ok(Expression::Function(Box::new(Function::new(
36907                            "ARRAY_FLATTEN".to_string(),
36908                            vec![array_agg],
36909                        ))))
36910                    }
36911                    _ => Ok(Expression::Function(Box::new(Function::new(
36912                        "ARRAY_CONCAT_AGG".to_string(),
36913                        vec![arg],
36914                    )))),
36915                }
36916            }
36917
36918            // MD5/SHA1/SHA256/SHA512 -> target-specific hash functions
36919            "MD5" if args.len() == 1 => {
36920                let arg = args.remove(0);
36921                match target {
36922                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
36923                        // UNHEX(MD5(x))
36924                        let md5 = Expression::Function(Box::new(Function::new(
36925                            "MD5".to_string(),
36926                            vec![arg],
36927                        )));
36928                        Ok(Expression::Function(Box::new(Function::new(
36929                            "UNHEX".to_string(),
36930                            vec![md5],
36931                        ))))
36932                    }
36933                    DialectType::Snowflake => {
36934                        // MD5_BINARY(x)
36935                        Ok(Expression::Function(Box::new(Function::new(
36936                            "MD5_BINARY".to_string(),
36937                            vec![arg],
36938                        ))))
36939                    }
36940                    _ => Ok(Expression::Function(Box::new(Function::new(
36941                        "MD5".to_string(),
36942                        vec![arg],
36943                    )))),
36944                }
36945            }
36946
36947            "SHA1" if args.len() == 1 => {
36948                let arg = args.remove(0);
36949                match target {
36950                    DialectType::DuckDB => {
36951                        // UNHEX(SHA1(x))
36952                        let sha1 = Expression::Function(Box::new(Function::new(
36953                            "SHA1".to_string(),
36954                            vec![arg],
36955                        )));
36956                        Ok(Expression::Function(Box::new(Function::new(
36957                            "UNHEX".to_string(),
36958                            vec![sha1],
36959                        ))))
36960                    }
36961                    _ => Ok(Expression::Function(Box::new(Function::new(
36962                        "SHA1".to_string(),
36963                        vec![arg],
36964                    )))),
36965                }
36966            }
36967
36968            "SHA256" if args.len() == 1 => {
36969                let arg = args.remove(0);
36970                match target {
36971                    DialectType::DuckDB => {
36972                        // UNHEX(SHA256(x))
36973                        let sha = Expression::Function(Box::new(Function::new(
36974                            "SHA256".to_string(),
36975                            vec![arg],
36976                        )));
36977                        Ok(Expression::Function(Box::new(Function::new(
36978                            "UNHEX".to_string(),
36979                            vec![sha],
36980                        ))))
36981                    }
36982                    DialectType::Snowflake => {
36983                        // SHA2_BINARY(x, 256)
36984                        Ok(Expression::Function(Box::new(Function::new(
36985                            "SHA2_BINARY".to_string(),
36986                            vec![arg, Expression::number(256)],
36987                        ))))
36988                    }
36989                    DialectType::Redshift | DialectType::Spark => {
36990                        // SHA2(x, 256)
36991                        Ok(Expression::Function(Box::new(Function::new(
36992                            "SHA2".to_string(),
36993                            vec![arg, Expression::number(256)],
36994                        ))))
36995                    }
36996                    _ => Ok(Expression::Function(Box::new(Function::new(
36997                        "SHA256".to_string(),
36998                        vec![arg],
36999                    )))),
37000                }
37001            }
37002
37003            "SHA512" if args.len() == 1 => {
37004                let arg = args.remove(0);
37005                match target {
37006                    DialectType::Snowflake => {
37007                        // SHA2_BINARY(x, 512)
37008                        Ok(Expression::Function(Box::new(Function::new(
37009                            "SHA2_BINARY".to_string(),
37010                            vec![arg, Expression::number(512)],
37011                        ))))
37012                    }
37013                    DialectType::Redshift | DialectType::Spark => {
37014                        // SHA2(x, 512)
37015                        Ok(Expression::Function(Box::new(Function::new(
37016                            "SHA2".to_string(),
37017                            vec![arg, Expression::number(512)],
37018                        ))))
37019                    }
37020                    _ => Ok(Expression::Function(Box::new(Function::new(
37021                        "SHA512".to_string(),
37022                        vec![arg],
37023                    )))),
37024                }
37025            }
37026
37027            // REGEXP_EXTRACT_ALL(str, pattern) -> add default group arg
37028            "REGEXP_EXTRACT_ALL" if args.len() == 2 => {
37029                let str_expr = args.remove(0);
37030                let pattern = args.remove(0);
37031
37032                // Check if pattern contains capturing groups (parentheses)
37033                let has_groups = match &pattern {
37034                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
37035                        let Literal::String(s) = lit.as_ref() else {
37036                            unreachable!()
37037                        };
37038                        s.contains('(') && s.contains(')')
37039                    }
37040                    _ => false,
37041                };
37042
37043                match target {
37044                    DialectType::DuckDB => {
37045                        let group = if has_groups {
37046                            Expression::number(1)
37047                        } else {
37048                            Expression::number(0)
37049                        };
37050                        Ok(Expression::Function(Box::new(Function::new(
37051                            "REGEXP_EXTRACT_ALL".to_string(),
37052                            vec![str_expr, pattern, group],
37053                        ))))
37054                    }
37055                    DialectType::Spark | DialectType::Databricks => {
37056                        // Spark's default group_index is 1 (same as BigQuery), so omit for capturing groups
37057                        if has_groups {
37058                            Ok(Expression::Function(Box::new(Function::new(
37059                                "REGEXP_EXTRACT_ALL".to_string(),
37060                                vec![str_expr, pattern],
37061                            ))))
37062                        } else {
37063                            Ok(Expression::Function(Box::new(Function::new(
37064                                "REGEXP_EXTRACT_ALL".to_string(),
37065                                vec![str_expr, pattern, Expression::number(0)],
37066                            ))))
37067                        }
37068                    }
37069                    DialectType::Presto | DialectType::Trino => {
37070                        if has_groups {
37071                            Ok(Expression::Function(Box::new(Function::new(
37072                                "REGEXP_EXTRACT_ALL".to_string(),
37073                                vec![str_expr, pattern, Expression::number(1)],
37074                            ))))
37075                        } else {
37076                            Ok(Expression::Function(Box::new(Function::new(
37077                                "REGEXP_EXTRACT_ALL".to_string(),
37078                                vec![str_expr, pattern],
37079                            ))))
37080                        }
37081                    }
37082                    DialectType::Snowflake => {
37083                        if has_groups {
37084                            // REGEXP_EXTRACT_ALL(str, pattern, 1, 1, 'c', 1)
37085                            Ok(Expression::Function(Box::new(Function::new(
37086                                "REGEXP_EXTRACT_ALL".to_string(),
37087                                vec![
37088                                    str_expr,
37089                                    pattern,
37090                                    Expression::number(1),
37091                                    Expression::number(1),
37092                                    Expression::Literal(Box::new(Literal::String("c".to_string()))),
37093                                    Expression::number(1),
37094                                ],
37095                            ))))
37096                        } else {
37097                            Ok(Expression::Function(Box::new(Function::new(
37098                                "REGEXP_EXTRACT_ALL".to_string(),
37099                                vec![str_expr, pattern],
37100                            ))))
37101                        }
37102                    }
37103                    _ => Ok(Expression::Function(Box::new(Function::new(
37104                        "REGEXP_EXTRACT_ALL".to_string(),
37105                        vec![str_expr, pattern],
37106                    )))),
37107                }
37108            }
37109
37110            // MOD(x, y) -> x % y for dialects that prefer or require the infix operator.
37111            "MOD" if args.len() == 2 => {
37112                match target {
37113                    DialectType::PostgreSQL
37114                    | DialectType::DuckDB
37115                    | DialectType::Presto
37116                    | DialectType::Trino
37117                    | DialectType::Athena
37118                    | DialectType::Snowflake
37119                    | DialectType::TSQL
37120                    | DialectType::Fabric => {
37121                        let x = args.remove(0);
37122                        let y = args.remove(0);
37123                        // Wrap complex expressions in parens to preserve precedence
37124                        let needs_paren = |e: &Expression| {
37125                            matches!(
37126                                e,
37127                                Expression::Add(_)
37128                                    | Expression::Sub(_)
37129                                    | Expression::Mul(_)
37130                                    | Expression::Div(_)
37131                                    | Expression::Mod(_)
37132                                    | Expression::ModFunc(_)
37133                            )
37134                        };
37135                        let x = if needs_paren(&x) {
37136                            Expression::Paren(Box::new(crate::expressions::Paren {
37137                                this: x,
37138                                trailing_comments: vec![],
37139                            }))
37140                        } else {
37141                            x
37142                        };
37143                        let y = if needs_paren(&y) {
37144                            Expression::Paren(Box::new(crate::expressions::Paren {
37145                                this: y,
37146                                trailing_comments: vec![],
37147                            }))
37148                        } else {
37149                            y
37150                        };
37151                        Ok(Expression::Mod(Box::new(
37152                            crate::expressions::BinaryOp::new(x, y),
37153                        )))
37154                    }
37155                    DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
37156                        // Hive/Spark: a % b
37157                        let x = args.remove(0);
37158                        let y = args.remove(0);
37159                        let needs_paren = |e: &Expression| {
37160                            matches!(
37161                                e,
37162                                Expression::Add(_)
37163                                    | Expression::Sub(_)
37164                                    | Expression::Mul(_)
37165                                    | Expression::Div(_)
37166                                    | Expression::Mod(_)
37167                                    | Expression::ModFunc(_)
37168                            )
37169                        };
37170                        let x = if needs_paren(&x) {
37171                            Expression::Paren(Box::new(crate::expressions::Paren {
37172                                this: x,
37173                                trailing_comments: vec![],
37174                            }))
37175                        } else {
37176                            x
37177                        };
37178                        let y = if needs_paren(&y) {
37179                            Expression::Paren(Box::new(crate::expressions::Paren {
37180                                this: y,
37181                                trailing_comments: vec![],
37182                            }))
37183                        } else {
37184                            y
37185                        };
37186                        Ok(Expression::Mod(Box::new(
37187                            crate::expressions::BinaryOp::new(x, y),
37188                        )))
37189                    }
37190                    _ => Ok(Expression::Function(Box::new(Function::new(
37191                        "MOD".to_string(),
37192                        args,
37193                    )))),
37194                }
37195            }
37196
37197            // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, ARRAY_FILTER for StarRocks
37198            "ARRAY_FILTER" if args.len() == 2 => {
37199                let name = match target {
37200                    DialectType::DuckDB => "LIST_FILTER",
37201                    DialectType::StarRocks => "ARRAY_FILTER",
37202                    _ => "FILTER",
37203                };
37204                Ok(Expression::Function(Box::new(Function::new(
37205                    name.to_string(),
37206                    args,
37207                ))))
37208            }
37209            // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
37210            "FILTER" if args.len() == 2 => {
37211                let name = match target {
37212                    DialectType::DuckDB => "LIST_FILTER",
37213                    DialectType::StarRocks => "ARRAY_FILTER",
37214                    _ => "FILTER",
37215                };
37216                Ok(Expression::Function(Box::new(Function::new(
37217                    name.to_string(),
37218                    args,
37219                ))))
37220            }
37221            // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
37222            "REDUCE" if args.len() >= 3 => {
37223                let name = match target {
37224                    DialectType::Spark | DialectType::Databricks => "AGGREGATE",
37225                    _ => "REDUCE",
37226                };
37227                Ok(Expression::Function(Box::new(Function::new(
37228                    name.to_string(),
37229                    args,
37230                ))))
37231            }
37232            // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse (handled by generator)
37233            "ARRAY_REVERSE" if args.len() == 1 => Ok(Expression::Function(Box::new(
37234                Function::new("ARRAY_REVERSE".to_string(), args),
37235            ))),
37236
37237            // CONCAT(a, b, ...) -> a || b || ... for DuckDB with 3+ args
37238            "CONCAT" if args.len() > 2 => match target {
37239                DialectType::DuckDB => {
37240                    let mut it = args.into_iter();
37241                    let mut result = it.next().unwrap();
37242                    for arg in it {
37243                        result = Expression::DPipe(Box::new(crate::expressions::DPipe {
37244                            this: Box::new(result),
37245                            expression: Box::new(arg),
37246                            safe: None,
37247                        }));
37248                    }
37249                    Ok(result)
37250                }
37251                _ => Ok(Expression::Function(Box::new(Function::new(
37252                    "CONCAT".to_string(),
37253                    args,
37254                )))),
37255            },
37256
37257            // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
37258            "GENERATE_DATE_ARRAY" => {
37259                if matches!(target, DialectType::BigQuery) {
37260                    // BQ->BQ: add default interval if not present
37261                    if args.len() == 2 {
37262                        let start = args.remove(0);
37263                        let end = args.remove(0);
37264                        let default_interval =
37265                            Expression::Interval(Box::new(crate::expressions::Interval {
37266                                this: Some(Expression::Literal(Box::new(Literal::String(
37267                                    "1".to_string(),
37268                                )))),
37269                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37270                                    unit: crate::expressions::IntervalUnit::Day,
37271                                    use_plural: false,
37272                                }),
37273                            }));
37274                        Ok(Expression::Function(Box::new(Function::new(
37275                            "GENERATE_DATE_ARRAY".to_string(),
37276                            vec![start, end, default_interval],
37277                        ))))
37278                    } else {
37279                        Ok(Expression::Function(Box::new(Function::new(
37280                            "GENERATE_DATE_ARRAY".to_string(),
37281                            args,
37282                        ))))
37283                    }
37284                } else if matches!(target, DialectType::DuckDB) {
37285                    // DuckDB: CAST(GENERATE_SERIES(CAST(start AS DATE), CAST(end AS DATE), step) AS DATE[])
37286                    let start = args.get(0).cloned();
37287                    let end = args.get(1).cloned();
37288                    let step = args.get(2).cloned().or_else(|| {
37289                        Some(Expression::Interval(Box::new(
37290                            crate::expressions::Interval {
37291                                this: Some(Expression::Literal(Box::new(Literal::String(
37292                                    "1".to_string(),
37293                                )))),
37294                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37295                                    unit: crate::expressions::IntervalUnit::Day,
37296                                    use_plural: false,
37297                                }),
37298                            },
37299                        )))
37300                    });
37301
37302                    // Wrap start/end in CAST(... AS DATE) only for string literals
37303                    let maybe_cast_date = |expr: Expression| -> Expression {
37304                        if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37305                        {
37306                            Expression::Cast(Box::new(Cast {
37307                                this: expr,
37308                                to: DataType::Date,
37309                                trailing_comments: vec![],
37310                                double_colon_syntax: false,
37311                                format: None,
37312                                default: None,
37313                                inferred_type: None,
37314                            }))
37315                        } else {
37316                            expr
37317                        }
37318                    };
37319                    let cast_start = start.map(maybe_cast_date);
37320                    let cast_end = end.map(maybe_cast_date);
37321
37322                    let gen_series =
37323                        Expression::GenerateSeries(Box::new(crate::expressions::GenerateSeries {
37324                            start: cast_start.map(Box::new),
37325                            end: cast_end.map(Box::new),
37326                            step: step.map(Box::new),
37327                            is_end_exclusive: None,
37328                        }));
37329
37330                    // Wrap in CAST(... AS DATE[])
37331                    Ok(Expression::Cast(Box::new(Cast {
37332                        this: gen_series,
37333                        to: DataType::Array {
37334                            element_type: Box::new(DataType::Date),
37335                            dimension: None,
37336                        },
37337                        trailing_comments: vec![],
37338                        double_colon_syntax: false,
37339                        format: None,
37340                        default: None,
37341                        inferred_type: None,
37342                    })))
37343                } else if matches!(target, DialectType::Snowflake) {
37344                    // Snowflake: keep as GENERATE_DATE_ARRAY function for later transform
37345                    // (transform_generate_date_array_snowflake will convert to ARRAY_GENERATE_RANGE + DATEADD)
37346                    if args.len() == 2 {
37347                        let start = args.remove(0);
37348                        let end = args.remove(0);
37349                        let default_interval =
37350                            Expression::Interval(Box::new(crate::expressions::Interval {
37351                                this: Some(Expression::Literal(Box::new(Literal::String(
37352                                    "1".to_string(),
37353                                )))),
37354                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37355                                    unit: crate::expressions::IntervalUnit::Day,
37356                                    use_plural: false,
37357                                }),
37358                            }));
37359                        Ok(Expression::Function(Box::new(Function::new(
37360                            "GENERATE_DATE_ARRAY".to_string(),
37361                            vec![start, end, default_interval],
37362                        ))))
37363                    } else {
37364                        Ok(Expression::Function(Box::new(Function::new(
37365                            "GENERATE_DATE_ARRAY".to_string(),
37366                            args,
37367                        ))))
37368                    }
37369                } else {
37370                    // Convert to GenerateSeries for other targets
37371                    let start = args.get(0).cloned();
37372                    let end = args.get(1).cloned();
37373                    let step = args.get(2).cloned().or_else(|| {
37374                        Some(Expression::Interval(Box::new(
37375                            crate::expressions::Interval {
37376                                this: Some(Expression::Literal(Box::new(Literal::String(
37377                                    "1".to_string(),
37378                                )))),
37379                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37380                                    unit: crate::expressions::IntervalUnit::Day,
37381                                    use_plural: false,
37382                                }),
37383                            },
37384                        )))
37385                    });
37386                    Ok(Expression::GenerateSeries(Box::new(
37387                        crate::expressions::GenerateSeries {
37388                            start: start.map(Box::new),
37389                            end: end.map(Box::new),
37390                            step: step.map(Box::new),
37391                            is_end_exclusive: None,
37392                        },
37393                    )))
37394                }
37395            }
37396
37397            // PARSE_DATE(format, str) -> target-specific
37398            "PARSE_DATE" if args.len() == 2 => {
37399                let format = args.remove(0);
37400                let str_expr = args.remove(0);
37401                match target {
37402                    DialectType::DuckDB => {
37403                        // CAST(STRPTIME(str, duck_format) AS DATE)
37404                        let duck_format = Self::bq_format_to_duckdb(&format);
37405                        let strptime = Expression::Function(Box::new(Function::new(
37406                            "STRPTIME".to_string(),
37407                            vec![str_expr, duck_format],
37408                        )));
37409                        Ok(Expression::Cast(Box::new(Cast {
37410                            this: strptime,
37411                            to: DataType::Date,
37412                            trailing_comments: vec![],
37413                            double_colon_syntax: false,
37414                            format: None,
37415                            default: None,
37416                            inferred_type: None,
37417                        })))
37418                    }
37419                    DialectType::Snowflake => {
37420                        // _POLYGLOT_DATE(str, snowflake_format)
37421                        // Use marker so Snowflake target transform keeps it as DATE() instead of TO_DATE()
37422                        let sf_format = Self::bq_format_to_snowflake(&format);
37423                        Ok(Expression::Function(Box::new(Function::new(
37424                            "_POLYGLOT_DATE".to_string(),
37425                            vec![str_expr, sf_format],
37426                        ))))
37427                    }
37428                    _ => Ok(Expression::Function(Box::new(Function::new(
37429                        "PARSE_DATE".to_string(),
37430                        vec![format, str_expr],
37431                    )))),
37432                }
37433            }
37434
37435            // PARSE_DATETIME(format, str) -> target-specific
37436            "PARSE_DATETIME" if args.len() == 2 => {
37437                let format = args.remove(0);
37438                let str_expr = args.remove(0);
37439                let c_format = Self::bq_format_to_duckdb(&format);
37440                match target {
37441                    DialectType::DuckDB => {
37442                        // DuckDB STRPTIME needs a date-bearing format for DATETIME parsing.
37443                        let str_with_year = Expression::Concat(Box::new(BinaryOp::new(
37444                            Expression::string("1970 "),
37445                            str_expr,
37446                        )));
37447                        let format_with_year = Expression::Concat(Box::new(BinaryOp::new(
37448                            Expression::string("%Y "),
37449                            c_format,
37450                        )));
37451                        Ok(Expression::Function(Box::new(Function::new(
37452                            "STRPTIME".to_string(),
37453                            vec![str_with_year, format_with_year],
37454                        ))))
37455                    }
37456                    DialectType::Snowflake => {
37457                        // SQLGlot emits PARSE_DATETIME(value, format) with expanded C-style format tokens.
37458                        Ok(Expression::Function(Box::new(Function::new(
37459                            "PARSE_DATETIME".to_string(),
37460                            vec![str_expr, c_format],
37461                        ))))
37462                    }
37463                    _ => Ok(Expression::Function(Box::new(Function::new(
37464                        "PARSE_DATETIME".to_string(),
37465                        vec![format, str_expr],
37466                    )))),
37467                }
37468            }
37469
37470            // PARSE_TIMESTAMP(format, str) -> target-specific
37471            "PARSE_TIMESTAMP" if args.len() >= 2 => {
37472                let format = args.remove(0);
37473                let str_expr = args.remove(0);
37474                let tz = if !args.is_empty() {
37475                    Some(args.remove(0))
37476                } else {
37477                    None
37478                };
37479                match target {
37480                    DialectType::DuckDB => {
37481                        let duck_format = Self::bq_format_to_duckdb(&format);
37482                        let strptime = Expression::Function(Box::new(Function::new(
37483                            "STRPTIME".to_string(),
37484                            vec![str_expr, duck_format],
37485                        )));
37486                        Ok(strptime)
37487                    }
37488                    _ => {
37489                        let mut result_args = vec![format, str_expr];
37490                        if let Some(tz_arg) = tz {
37491                            result_args.push(tz_arg);
37492                        }
37493                        Ok(Expression::Function(Box::new(Function::new(
37494                            "PARSE_TIMESTAMP".to_string(),
37495                            result_args,
37496                        ))))
37497                    }
37498                }
37499            }
37500
37501            // FORMAT_DATE(format, date) -> target-specific
37502            "FORMAT_DATE" if args.len() == 2 => {
37503                let format = args.remove(0);
37504                let date_expr = args.remove(0);
37505                match target {
37506                    DialectType::DuckDB => {
37507                        // STRFTIME(CAST(date AS DATE), format)
37508                        let cast_date = Expression::Cast(Box::new(Cast {
37509                            this: date_expr,
37510                            to: DataType::Date,
37511                            trailing_comments: vec![],
37512                            double_colon_syntax: false,
37513                            format: None,
37514                            default: None,
37515                            inferred_type: None,
37516                        }));
37517                        Ok(Expression::Function(Box::new(Function::new(
37518                            "STRFTIME".to_string(),
37519                            vec![cast_date, format],
37520                        ))))
37521                    }
37522                    _ => Ok(Expression::Function(Box::new(Function::new(
37523                        "FORMAT_DATE".to_string(),
37524                        vec![format, date_expr],
37525                    )))),
37526                }
37527            }
37528
37529            // FORMAT_DATETIME(format, datetime) -> target-specific
37530            "FORMAT_DATETIME" if args.len() == 2 => {
37531                let format = args.remove(0);
37532                let dt_expr = args.remove(0);
37533
37534                if matches!(target, DialectType::BigQuery) {
37535                    // BQ->BQ: normalize %H:%M:%S to %T, %x to %D
37536                    let norm_format = Self::bq_format_normalize_bq(&format);
37537                    // Also strip DATETIME keyword from typed literals
37538                    let norm_dt = match dt_expr {
37539                        Expression::Literal(lit)
37540                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
37541                        {
37542                            let Literal::Timestamp(s) = lit.as_ref() else {
37543                                unreachable!()
37544                            };
37545                            Expression::Cast(Box::new(Cast {
37546                                this: Expression::Literal(Box::new(Literal::String(s.clone()))),
37547                                to: DataType::Custom {
37548                                    name: "DATETIME".to_string(),
37549                                },
37550                                trailing_comments: vec![],
37551                                double_colon_syntax: false,
37552                                format: None,
37553                                default: None,
37554                                inferred_type: None,
37555                            }))
37556                        }
37557                        other => other,
37558                    };
37559                    return Ok(Expression::Function(Box::new(Function::new(
37560                        "FORMAT_DATETIME".to_string(),
37561                        vec![norm_format, norm_dt],
37562                    ))));
37563                }
37564
37565                match target {
37566                    DialectType::DuckDB => {
37567                        // STRFTIME(CAST(dt AS TIMESTAMP), duckdb_format)
37568                        let cast_dt = Self::ensure_cast_timestamp(dt_expr);
37569                        let duck_format = Self::bq_format_to_duckdb(&format);
37570                        Ok(Expression::Function(Box::new(Function::new(
37571                            "STRFTIME".to_string(),
37572                            vec![cast_dt, duck_format],
37573                        ))))
37574                    }
37575                    _ => Ok(Expression::Function(Box::new(Function::new(
37576                        "FORMAT_DATETIME".to_string(),
37577                        vec![format, dt_expr],
37578                    )))),
37579                }
37580            }
37581
37582            // FORMAT_TIMESTAMP(format, ts) -> target-specific
37583            "FORMAT_TIMESTAMP" if args.len() == 2 => {
37584                let format = args.remove(0);
37585                let ts_expr = args.remove(0);
37586                match target {
37587                    DialectType::DuckDB => {
37588                        // STRFTIME(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), format)
37589                        let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
37590                        let cast_ts = Expression::Cast(Box::new(Cast {
37591                            this: cast_tstz,
37592                            to: DataType::Timestamp {
37593                                timezone: false,
37594                                precision: None,
37595                            },
37596                            trailing_comments: vec![],
37597                            double_colon_syntax: false,
37598                            format: None,
37599                            default: None,
37600                            inferred_type: None,
37601                        }));
37602                        Ok(Expression::Function(Box::new(Function::new(
37603                            "STRFTIME".to_string(),
37604                            vec![cast_ts, format],
37605                        ))))
37606                    }
37607                    DialectType::Snowflake => {
37608                        // TO_CHAR(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), snowflake_format)
37609                        let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
37610                        let cast_ts = Expression::Cast(Box::new(Cast {
37611                            this: cast_tstz,
37612                            to: DataType::Timestamp {
37613                                timezone: false,
37614                                precision: None,
37615                            },
37616                            trailing_comments: vec![],
37617                            double_colon_syntax: false,
37618                            format: None,
37619                            default: None,
37620                            inferred_type: None,
37621                        }));
37622                        let sf_format = Self::bq_format_to_snowflake(&format);
37623                        Ok(Expression::Function(Box::new(Function::new(
37624                            "TO_CHAR".to_string(),
37625                            vec![cast_ts, sf_format],
37626                        ))))
37627                    }
37628                    _ => Ok(Expression::Function(Box::new(Function::new(
37629                        "FORMAT_TIMESTAMP".to_string(),
37630                        vec![format, ts_expr],
37631                    )))),
37632                }
37633            }
37634
37635            // UNIX_DATE(date) -> DATE_DIFF('DAY', '1970-01-01', date) for DuckDB
37636            "UNIX_DATE" if args.len() == 1 => {
37637                let date = args.remove(0);
37638                match target {
37639                    DialectType::DuckDB => {
37640                        let epoch = Expression::Cast(Box::new(Cast {
37641                            this: Expression::Literal(Box::new(Literal::String(
37642                                "1970-01-01".to_string(),
37643                            ))),
37644                            to: DataType::Date,
37645                            trailing_comments: vec![],
37646                            double_colon_syntax: false,
37647                            format: None,
37648                            default: None,
37649                            inferred_type: None,
37650                        }));
37651                        // DATE_DIFF('DAY', epoch, date) but date might be DATE '...' literal
37652                        // Need to convert DATE literal to CAST
37653                        let norm_date = Self::date_literal_to_cast(date);
37654                        Ok(Expression::Function(Box::new(Function::new(
37655                            "DATE_DIFF".to_string(),
37656                            vec![
37657                                Expression::Literal(Box::new(Literal::String("DAY".to_string()))),
37658                                epoch,
37659                                norm_date,
37660                            ],
37661                        ))))
37662                    }
37663                    _ => Ok(Expression::Function(Box::new(Function::new(
37664                        "UNIX_DATE".to_string(),
37665                        vec![date],
37666                    )))),
37667                }
37668            }
37669
37670            // UNIX_SECONDS(ts) -> target-specific
37671            "UNIX_SECONDS" if args.len() == 1 => {
37672                let ts = args.remove(0);
37673                match target {
37674                    DialectType::DuckDB => {
37675                        // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
37676                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
37677                        let epoch = Expression::Function(Box::new(Function::new(
37678                            "EPOCH".to_string(),
37679                            vec![norm_ts],
37680                        )));
37681                        Ok(Expression::Cast(Box::new(Cast {
37682                            this: epoch,
37683                            to: DataType::BigInt { length: None },
37684                            trailing_comments: vec![],
37685                            double_colon_syntax: false,
37686                            format: None,
37687                            default: None,
37688                            inferred_type: None,
37689                        })))
37690                    }
37691                    DialectType::Snowflake => {
37692                        // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
37693                        let epoch = Expression::Cast(Box::new(Cast {
37694                            this: Expression::Literal(Box::new(Literal::String(
37695                                "1970-01-01 00:00:00+00".to_string(),
37696                            ))),
37697                            to: DataType::Timestamp {
37698                                timezone: true,
37699                                precision: None,
37700                            },
37701                            trailing_comments: vec![],
37702                            double_colon_syntax: false,
37703                            format: None,
37704                            default: None,
37705                            inferred_type: None,
37706                        }));
37707                        Ok(Expression::Function(Box::new(Function::new(
37708                            "TIMESTAMPDIFF".to_string(),
37709                            vec![
37710                                Expression::Identifier(Identifier::new("SECONDS".to_string())),
37711                                epoch,
37712                                ts,
37713                            ],
37714                        ))))
37715                    }
37716                    _ => Ok(Expression::Function(Box::new(Function::new(
37717                        "UNIX_SECONDS".to_string(),
37718                        vec![ts],
37719                    )))),
37720                }
37721            }
37722
37723            // UNIX_MILLIS(ts) -> target-specific
37724            "UNIX_MILLIS" if args.len() == 1 => {
37725                let ts = args.remove(0);
37726                match target {
37727                    DialectType::DuckDB => {
37728                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
37729                        Ok(Expression::Function(Box::new(Function::new(
37730                            "EPOCH_MS".to_string(),
37731                            vec![norm_ts],
37732                        ))))
37733                    }
37734                    _ => Ok(Expression::Function(Box::new(Function::new(
37735                        "UNIX_MILLIS".to_string(),
37736                        vec![ts],
37737                    )))),
37738                }
37739            }
37740
37741            // UNIX_MICROS(ts) -> target-specific
37742            "UNIX_MICROS" if args.len() == 1 => {
37743                let ts = args.remove(0);
37744                match target {
37745                    DialectType::DuckDB => {
37746                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
37747                        Ok(Expression::Function(Box::new(Function::new(
37748                            "EPOCH_US".to_string(),
37749                            vec![norm_ts],
37750                        ))))
37751                    }
37752                    _ => Ok(Expression::Function(Box::new(Function::new(
37753                        "UNIX_MICROS".to_string(),
37754                        vec![ts],
37755                    )))),
37756                }
37757            }
37758
37759            // INSTR(str, substr) -> target-specific
37760            "INSTR" => {
37761                if matches!(target, DialectType::BigQuery) {
37762                    // BQ->BQ: keep as INSTR
37763                    Ok(Expression::Function(Box::new(Function::new(
37764                        "INSTR".to_string(),
37765                        args,
37766                    ))))
37767                } else if matches!(target, DialectType::Snowflake) && args.len() == 2 {
37768                    // Snowflake: CHARINDEX(substr, str) - swap args
37769                    let str_expr = args.remove(0);
37770                    let substr = args.remove(0);
37771                    Ok(Expression::Function(Box::new(Function::new(
37772                        "CHARINDEX".to_string(),
37773                        vec![substr, str_expr],
37774                    ))))
37775                } else {
37776                    // Keep as INSTR for other targets
37777                    Ok(Expression::Function(Box::new(Function::new(
37778                        "INSTR".to_string(),
37779                        args,
37780                    ))))
37781                }
37782            }
37783
37784            // CURRENT_TIMESTAMP / CURRENT_DATE handling - parens normalization and timezone
37785            "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME" => {
37786                if matches!(target, DialectType::BigQuery) {
37787                    // BQ->BQ: always output with parens (function form), keep any timezone arg
37788                    Ok(Expression::Function(Box::new(Function::new(name, args))))
37789                } else if name == "CURRENT_DATE" && args.len() == 1 {
37790                    // CURRENT_DATE('UTC') - has timezone arg
37791                    let tz_arg = args.remove(0);
37792                    match target {
37793                        DialectType::DuckDB => {
37794                            // CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)
37795                            let ct = Expression::CurrentTimestamp(
37796                                crate::expressions::CurrentTimestamp {
37797                                    precision: None,
37798                                    sysdate: false,
37799                                },
37800                            );
37801                            let at_tz =
37802                                Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
37803                                    this: ct,
37804                                    zone: tz_arg,
37805                                }));
37806                            Ok(Expression::Cast(Box::new(Cast {
37807                                this: at_tz,
37808                                to: DataType::Date,
37809                                trailing_comments: vec![],
37810                                double_colon_syntax: false,
37811                                format: None,
37812                                default: None,
37813                                inferred_type: None,
37814                            })))
37815                        }
37816                        DialectType::Snowflake => {
37817                            // CAST(CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()) AS DATE)
37818                            let ct = Expression::Function(Box::new(Function::new(
37819                                "CURRENT_TIMESTAMP".to_string(),
37820                                vec![],
37821                            )));
37822                            let convert = Expression::Function(Box::new(Function::new(
37823                                "CONVERT_TIMEZONE".to_string(),
37824                                vec![tz_arg, ct],
37825                            )));
37826                            Ok(Expression::Cast(Box::new(Cast {
37827                                this: convert,
37828                                to: DataType::Date,
37829                                trailing_comments: vec![],
37830                                double_colon_syntax: false,
37831                                format: None,
37832                                default: None,
37833                                inferred_type: None,
37834                            })))
37835                        }
37836                        _ => {
37837                            // PostgreSQL, MySQL, etc.: CURRENT_DATE AT TIME ZONE 'UTC'
37838                            let cd = Expression::CurrentDate(crate::expressions::CurrentDate);
37839                            Ok(Expression::AtTimeZone(Box::new(
37840                                crate::expressions::AtTimeZone {
37841                                    this: cd,
37842                                    zone: tz_arg,
37843                                },
37844                            )))
37845                        }
37846                    }
37847                } else if (name == "CURRENT_TIMESTAMP"
37848                    || name == "CURRENT_TIME"
37849                    || name == "CURRENT_DATE")
37850                    && args.is_empty()
37851                    && matches!(
37852                        target,
37853                        DialectType::PostgreSQL
37854                            | DialectType::DuckDB
37855                            | DialectType::Presto
37856                            | DialectType::Trino
37857                    )
37858                {
37859                    // These targets want no-parens CURRENT_TIMESTAMP / CURRENT_DATE / CURRENT_TIME
37860                    if name == "CURRENT_TIMESTAMP" {
37861                        Ok(Expression::CurrentTimestamp(
37862                            crate::expressions::CurrentTimestamp {
37863                                precision: None,
37864                                sysdate: false,
37865                            },
37866                        ))
37867                    } else if name == "CURRENT_DATE" {
37868                        Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
37869                    } else {
37870                        // CURRENT_TIME
37871                        Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
37872                            precision: None,
37873                        }))
37874                    }
37875                } else {
37876                    // All other targets: keep as function (with parens)
37877                    Ok(Expression::Function(Box::new(Function::new(name, args))))
37878                }
37879            }
37880
37881            // JSON_QUERY(json, path) -> target-specific
37882            "JSON_QUERY" if args.len() == 2 => {
37883                match target {
37884                    DialectType::DuckDB | DialectType::SQLite => {
37885                        // json -> path syntax
37886                        let json_expr = args.remove(0);
37887                        let path = args.remove(0);
37888                        Ok(Expression::JsonExtract(Box::new(
37889                            crate::expressions::JsonExtractFunc {
37890                                this: json_expr,
37891                                path,
37892                                returning: None,
37893                                arrow_syntax: true,
37894                                hash_arrow_syntax: false,
37895                                wrapper_option: None,
37896                                quotes_option: None,
37897                                on_scalar_string: false,
37898                                on_error: None,
37899                            },
37900                        )))
37901                    }
37902                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
37903                        Ok(Expression::Function(Box::new(Function::new(
37904                            "GET_JSON_OBJECT".to_string(),
37905                            args,
37906                        ))))
37907                    }
37908                    DialectType::PostgreSQL | DialectType::Redshift => Ok(Expression::Function(
37909                        Box::new(Function::new("JSON_EXTRACT_PATH".to_string(), args)),
37910                    )),
37911                    _ => Ok(Expression::Function(Box::new(Function::new(
37912                        "JSON_QUERY".to_string(),
37913                        args,
37914                    )))),
37915                }
37916            }
37917
37918            // JSON_VALUE_ARRAY(json, path) -> target-specific
37919            "JSON_VALUE_ARRAY" if args.len() == 2 => {
37920                match target {
37921                    DialectType::DuckDB => {
37922                        // CAST(json -> path AS TEXT[])
37923                        let json_expr = args.remove(0);
37924                        let path = args.remove(0);
37925                        let arrow = Expression::JsonExtract(Box::new(
37926                            crate::expressions::JsonExtractFunc {
37927                                this: json_expr,
37928                                path,
37929                                returning: None,
37930                                arrow_syntax: true,
37931                                hash_arrow_syntax: false,
37932                                wrapper_option: None,
37933                                quotes_option: None,
37934                                on_scalar_string: false,
37935                                on_error: None,
37936                            },
37937                        ));
37938                        Ok(Expression::Cast(Box::new(Cast {
37939                            this: arrow,
37940                            to: DataType::Array {
37941                                element_type: Box::new(DataType::Text),
37942                                dimension: None,
37943                            },
37944                            trailing_comments: vec![],
37945                            double_colon_syntax: false,
37946                            format: None,
37947                            default: None,
37948                            inferred_type: None,
37949                        })))
37950                    }
37951                    DialectType::Snowflake => {
37952                        let json_expr = args.remove(0);
37953                        let path_expr = args.remove(0);
37954                        // Convert JSON path from $.path to just path
37955                        let sf_path = if let Expression::Literal(ref lit) = path_expr {
37956                            if let Literal::String(ref s) = lit.as_ref() {
37957                                let trimmed = s.trim_start_matches('$').trim_start_matches('.');
37958                                Expression::Literal(Box::new(Literal::String(trimmed.to_string())))
37959                            } else {
37960                                path_expr.clone()
37961                            }
37962                        } else {
37963                            path_expr
37964                        };
37965                        let parse_json = Expression::Function(Box::new(Function::new(
37966                            "PARSE_JSON".to_string(),
37967                            vec![json_expr],
37968                        )));
37969                        let get_path = Expression::Function(Box::new(Function::new(
37970                            "GET_PATH".to_string(),
37971                            vec![parse_json, sf_path],
37972                        )));
37973                        // TRANSFORM(get_path, x -> CAST(x AS VARCHAR))
37974                        let cast_expr = Expression::Cast(Box::new(Cast {
37975                            this: Expression::Identifier(Identifier::new("x")),
37976                            to: DataType::VarChar {
37977                                length: None,
37978                                parenthesized_length: false,
37979                            },
37980                            trailing_comments: vec![],
37981                            double_colon_syntax: false,
37982                            format: None,
37983                            default: None,
37984                            inferred_type: None,
37985                        }));
37986                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
37987                            parameters: vec![Identifier::new("x")],
37988                            body: cast_expr,
37989                            colon: false,
37990                            parameter_types: vec![],
37991                        }));
37992                        Ok(Expression::Function(Box::new(Function::new(
37993                            "TRANSFORM".to_string(),
37994                            vec![get_path, lambda],
37995                        ))))
37996                    }
37997                    _ => Ok(Expression::Function(Box::new(Function::new(
37998                        "JSON_VALUE_ARRAY".to_string(),
37999                        args,
38000                    )))),
38001                }
38002            }
38003
38004            // BigQuery REGEXP_EXTRACT(val, regex[, position[, occurrence]]) -> target dialects
38005            // BigQuery's 3rd arg is "position" (starting char index), 4th is "occurrence" (which match to return)
38006            // This is different from Hive/Spark where 3rd arg is "group_index"
38007            "REGEXP_EXTRACT" if matches!(source, DialectType::BigQuery) => {
38008                match target {
38009                    DialectType::DuckDB
38010                    | DialectType::Presto
38011                    | DialectType::Trino
38012                    | DialectType::Athena => {
38013                        if args.len() == 2 {
38014                            // REGEXP_EXTRACT(val, regex) -> REGEXP_EXTRACT(val, regex, 1)
38015                            args.push(Expression::number(1));
38016                            Ok(Expression::Function(Box::new(Function::new(
38017                                "REGEXP_EXTRACT".to_string(),
38018                                args,
38019                            ))))
38020                        } else if args.len() == 3 {
38021                            let val = args.remove(0);
38022                            let regex = args.remove(0);
38023                            let position = args.remove(0);
38024                            let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
38025                            if is_pos_1 {
38026                                Ok(Expression::Function(Box::new(Function::new(
38027                                    "REGEXP_EXTRACT".to_string(),
38028                                    vec![val, regex, Expression::number(1)],
38029                                ))))
38030                            } else {
38031                                let substring_expr = Expression::Function(Box::new(Function::new(
38032                                    "SUBSTRING".to_string(),
38033                                    vec![val, position],
38034                                )));
38035                                let nullif_expr = Expression::Function(Box::new(Function::new(
38036                                    "NULLIF".to_string(),
38037                                    vec![
38038                                        substring_expr,
38039                                        Expression::Literal(Box::new(Literal::String(
38040                                            String::new(),
38041                                        ))),
38042                                    ],
38043                                )));
38044                                Ok(Expression::Function(Box::new(Function::new(
38045                                    "REGEXP_EXTRACT".to_string(),
38046                                    vec![nullif_expr, regex, Expression::number(1)],
38047                                ))))
38048                            }
38049                        } else if args.len() == 4 {
38050                            let val = args.remove(0);
38051                            let regex = args.remove(0);
38052                            let position = args.remove(0);
38053                            let occurrence = args.remove(0);
38054                            let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
38055                            let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
38056                            if is_pos_1 && is_occ_1 {
38057                                Ok(Expression::Function(Box::new(Function::new(
38058                                    "REGEXP_EXTRACT".to_string(),
38059                                    vec![val, regex, Expression::number(1)],
38060                                ))))
38061                            } else {
38062                                let subject = if is_pos_1 {
38063                                    val
38064                                } else {
38065                                    let substring_expr = Expression::Function(Box::new(
38066                                        Function::new("SUBSTRING".to_string(), vec![val, position]),
38067                                    ));
38068                                    Expression::Function(Box::new(Function::new(
38069                                        "NULLIF".to_string(),
38070                                        vec![
38071                                            substring_expr,
38072                                            Expression::Literal(Box::new(Literal::String(
38073                                                String::new(),
38074                                            ))),
38075                                        ],
38076                                    )))
38077                                };
38078                                let extract_all = Expression::Function(Box::new(Function::new(
38079                                    "REGEXP_EXTRACT_ALL".to_string(),
38080                                    vec![subject, regex, Expression::number(1)],
38081                                )));
38082                                Ok(Expression::Function(Box::new(Function::new(
38083                                    "ARRAY_EXTRACT".to_string(),
38084                                    vec![extract_all, occurrence],
38085                                ))))
38086                            }
38087                        } else {
38088                            Ok(Expression::Function(Box::new(Function {
38089                                name: f.name,
38090                                args,
38091                                distinct: f.distinct,
38092                                trailing_comments: f.trailing_comments,
38093                                use_bracket_syntax: f.use_bracket_syntax,
38094                                no_parens: f.no_parens,
38095                                quoted: f.quoted,
38096                                span: None,
38097                                inferred_type: None,
38098                            })))
38099                        }
38100                    }
38101                    DialectType::Snowflake => {
38102                        // BigQuery REGEXP_EXTRACT -> Snowflake REGEXP_SUBSTR
38103                        Ok(Expression::Function(Box::new(Function::new(
38104                            "REGEXP_SUBSTR".to_string(),
38105                            args,
38106                        ))))
38107                    }
38108                    _ => {
38109                        // For other targets (Hive/Spark/BigQuery): pass through as-is
38110                        // BigQuery's default group behavior matches Hive/Spark for 2-arg case
38111                        Ok(Expression::Function(Box::new(Function {
38112                            name: f.name,
38113                            args,
38114                            distinct: f.distinct,
38115                            trailing_comments: f.trailing_comments,
38116                            use_bracket_syntax: f.use_bracket_syntax,
38117                            no_parens: f.no_parens,
38118                            quoted: f.quoted,
38119                            span: None,
38120                            inferred_type: None,
38121                        })))
38122                    }
38123                }
38124            }
38125
38126            // BigQuery STRUCT(args) -> target-specific struct expression
38127            "STRUCT" => {
38128                // Convert Function args to Struct fields
38129                let mut fields: Vec<(Option<String>, Expression)> = Vec::new();
38130                for (i, arg) in args.into_iter().enumerate() {
38131                    match arg {
38132                        Expression::Alias(a) => {
38133                            // Named field: expr AS name
38134                            fields.push((Some(a.alias.name.clone()), a.this));
38135                        }
38136                        other => {
38137                            // Unnamed field: for Spark/Hive, keep as None
38138                            // For Snowflake, auto-name as _N
38139                            // For DuckDB, use column name for column refs, _N for others
38140                            if matches!(target, DialectType::Snowflake) {
38141                                fields.push((Some(format!("_{}", i)), other));
38142                            } else if matches!(target, DialectType::DuckDB) {
38143                                let auto_name = match &other {
38144                                    Expression::Column(col) => col.name.name.clone(),
38145                                    _ => format!("_{}", i),
38146                                };
38147                                fields.push((Some(auto_name), other));
38148                            } else {
38149                                fields.push((None, other));
38150                            }
38151                        }
38152                    }
38153                }
38154
38155                match target {
38156                    DialectType::Snowflake => {
38157                        // OBJECT_CONSTRUCT('name', value, ...)
38158                        let mut oc_args = Vec::new();
38159                        for (name, val) in &fields {
38160                            if let Some(n) = name {
38161                                oc_args.push(Expression::Literal(Box::new(Literal::String(
38162                                    n.clone(),
38163                                ))));
38164                                oc_args.push(val.clone());
38165                            } else {
38166                                oc_args.push(val.clone());
38167                            }
38168                        }
38169                        Ok(Expression::Function(Box::new(Function::new(
38170                            "OBJECT_CONSTRUCT".to_string(),
38171                            oc_args,
38172                        ))))
38173                    }
38174                    DialectType::DuckDB => {
38175                        // {'name': value, ...}
38176                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
38177                            fields,
38178                        })))
38179                    }
38180                    DialectType::Hive => {
38181                        // STRUCT(val1, val2, ...) - strip aliases
38182                        let hive_fields: Vec<(Option<String>, Expression)> =
38183                            fields.into_iter().map(|(_, v)| (None, v)).collect();
38184                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
38185                            fields: hive_fields,
38186                        })))
38187                    }
38188                    DialectType::Spark | DialectType::Databricks => {
38189                        // Use Expression::Struct to bypass Spark target transform auto-naming
38190                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
38191                            fields,
38192                        })))
38193                    }
38194                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
38195                        // Check if all fields are named AND all have inferable types - if so, wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
38196                        let all_named =
38197                            !fields.is_empty() && fields.iter().all(|(name, _)| name.is_some());
38198                        let all_types_inferable = all_named
38199                            && fields
38200                                .iter()
38201                                .all(|(_, val)| Self::can_infer_presto_type(val));
38202                        let row_args: Vec<Expression> =
38203                            fields.iter().map(|(_, v)| v.clone()).collect();
38204                        let row_expr = Expression::Function(Box::new(Function::new(
38205                            "ROW".to_string(),
38206                            row_args,
38207                        )));
38208                        if all_named && all_types_inferable {
38209                            // Build ROW type with inferred types
38210                            let mut row_type_fields = Vec::new();
38211                            for (name, val) in &fields {
38212                                if let Some(n) = name {
38213                                    let type_str = Self::infer_sql_type_for_presto(val);
38214                                    row_type_fields.push(crate::expressions::StructField::new(
38215                                        n.clone(),
38216                                        crate::expressions::DataType::Custom { name: type_str },
38217                                    ));
38218                                }
38219                            }
38220                            let row_type = crate::expressions::DataType::Struct {
38221                                fields: row_type_fields,
38222                                nested: true,
38223                            };
38224                            Ok(Expression::Cast(Box::new(Cast {
38225                                this: row_expr,
38226                                to: row_type,
38227                                trailing_comments: Vec::new(),
38228                                double_colon_syntax: false,
38229                                format: None,
38230                                default: None,
38231                                inferred_type: None,
38232                            })))
38233                        } else {
38234                            Ok(row_expr)
38235                        }
38236                    }
38237                    _ => {
38238                        // Default: keep as STRUCT function with original args
38239                        let mut new_args = Vec::new();
38240                        for (name, val) in fields {
38241                            if let Some(n) = name {
38242                                new_args.push(Expression::Alias(Box::new(
38243                                    crate::expressions::Alias::new(val, Identifier::new(n)),
38244                                )));
38245                            } else {
38246                                new_args.push(val);
38247                            }
38248                        }
38249                        Ok(Expression::Function(Box::new(Function::new(
38250                            "STRUCT".to_string(),
38251                            new_args,
38252                        ))))
38253                    }
38254                }
38255            }
38256
38257            // ROUND(x, n, 'ROUND_HALF_EVEN') -> ROUND_EVEN(x, n) for DuckDB
38258            "ROUND" if args.len() == 3 => {
38259                let x = args.remove(0);
38260                let n = args.remove(0);
38261                let mode = args.remove(0);
38262                // Check if mode is 'ROUND_HALF_EVEN'
38263                let is_half_even = matches!(&mode, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.eq_ignore_ascii_case("ROUND_HALF_EVEN")));
38264                if is_half_even && matches!(target, DialectType::DuckDB) {
38265                    Ok(Expression::Function(Box::new(Function::new(
38266                        "ROUND_EVEN".to_string(),
38267                        vec![x, n],
38268                    ))))
38269                } else {
38270                    // Pass through with all args
38271                    Ok(Expression::Function(Box::new(Function::new(
38272                        "ROUND".to_string(),
38273                        vec![x, n, mode],
38274                    ))))
38275                }
38276            }
38277
38278            // MAKE_INTERVAL(year, month, named_args...) -> INTERVAL string for Snowflake/DuckDB
38279            "MAKE_INTERVAL" => {
38280                // MAKE_INTERVAL(1, 2, minute => 5, day => 3)
38281                // The positional args are: year, month
38282                // Named args are: day =>, minute =>, etc.
38283                // For Snowflake: INTERVAL '1 year, 2 month, 5 minute, 3 day'
38284                // For DuckDB: INTERVAL '1 year 2 month 5 minute 3 day'
38285                // For BigQuery->BigQuery: reorder named args (day before minute)
38286                if matches!(target, DialectType::Snowflake | DialectType::DuckDB) {
38287                    let mut parts: Vec<(String, String)> = Vec::new();
38288                    let mut pos_idx = 0;
38289                    let pos_units = ["year", "month"];
38290                    for arg in &args {
38291                        if let Expression::NamedArgument(na) = arg {
38292                            // Named arg like minute => 5
38293                            let unit = na.name.name.clone();
38294                            if let Expression::Literal(lit) = &na.value {
38295                                if let Literal::Number(n) = lit.as_ref() {
38296                                    parts.push((unit, n.clone()));
38297                                }
38298                            }
38299                        } else if pos_idx < pos_units.len() {
38300                            if let Expression::Literal(lit) = arg {
38301                                if let Literal::Number(n) = lit.as_ref() {
38302                                    parts.push((pos_units[pos_idx].to_string(), n.clone()));
38303                                }
38304                            }
38305                            pos_idx += 1;
38306                        }
38307                    }
38308                    // Don't sort - preserve original argument order
38309                    let separator = if matches!(target, DialectType::Snowflake) {
38310                        ", "
38311                    } else {
38312                        " "
38313                    };
38314                    let interval_str = parts
38315                        .iter()
38316                        .map(|(u, v)| format!("{} {}", v, u))
38317                        .collect::<Vec<_>>()
38318                        .join(separator);
38319                    Ok(Expression::Interval(Box::new(
38320                        crate::expressions::Interval {
38321                            this: Some(Expression::Literal(Box::new(Literal::String(
38322                                interval_str,
38323                            )))),
38324                            unit: None,
38325                        },
38326                    )))
38327                } else if matches!(target, DialectType::BigQuery) {
38328                    // BigQuery->BigQuery: reorder named args (day, minute, etc.)
38329                    let mut positional = Vec::new();
38330                    let mut named: Vec<(
38331                        String,
38332                        Expression,
38333                        crate::expressions::NamedArgSeparator,
38334                    )> = Vec::new();
38335                    let _pos_units = ["year", "month"];
38336                    let mut _pos_idx = 0;
38337                    for arg in args {
38338                        if let Expression::NamedArgument(na) = arg {
38339                            named.push((na.name.name.clone(), na.value, na.separator));
38340                        } else {
38341                            positional.push(arg);
38342                            _pos_idx += 1;
38343                        }
38344                    }
38345                    // Sort named args by: day, hour, minute, second
38346                    let unit_order = |u: &str| -> usize {
38347                        match u.to_ascii_lowercase().as_str() {
38348                            "day" => 0,
38349                            "hour" => 1,
38350                            "minute" => 2,
38351                            "second" => 3,
38352                            _ => 4,
38353                        }
38354                    };
38355                    named.sort_by_key(|(u, _, _)| unit_order(u));
38356                    let mut result_args = positional;
38357                    for (name, value, sep) in named {
38358                        result_args.push(Expression::NamedArgument(Box::new(
38359                            crate::expressions::NamedArgument {
38360                                name: Identifier::new(&name),
38361                                value,
38362                                separator: sep,
38363                            },
38364                        )));
38365                    }
38366                    Ok(Expression::Function(Box::new(Function::new(
38367                        "MAKE_INTERVAL".to_string(),
38368                        result_args,
38369                    ))))
38370                } else {
38371                    Ok(Expression::Function(Box::new(Function::new(
38372                        "MAKE_INTERVAL".to_string(),
38373                        args,
38374                    ))))
38375                }
38376            }
38377
38378            // ARRAY_TO_STRING(array, sep, null_text) -> ARRAY_TO_STRING(LIST_TRANSFORM(array, x -> COALESCE(x, null_text)), sep) for DuckDB
38379            "ARRAY_TO_STRING" if args.len() == 3 => {
38380                let arr = args.remove(0);
38381                let sep = args.remove(0);
38382                let null_text = args.remove(0);
38383                match target {
38384                    DialectType::DuckDB => {
38385                        // LIST_TRANSFORM(array, x -> COALESCE(x, null_text))
38386                        let _lambda_param =
38387                            Expression::Identifier(crate::expressions::Identifier::new("x"));
38388                        let coalesce =
38389                            Expression::Coalesce(Box::new(crate::expressions::VarArgFunc {
38390                                original_name: None,
38391                                expressions: vec![
38392                                    Expression::Identifier(crate::expressions::Identifier::new(
38393                                        "x",
38394                                    )),
38395                                    null_text,
38396                                ],
38397                                inferred_type: None,
38398                            }));
38399                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
38400                            parameters: vec![crate::expressions::Identifier::new("x")],
38401                            body: coalesce,
38402                            colon: false,
38403                            parameter_types: vec![],
38404                        }));
38405                        let list_transform = Expression::Function(Box::new(Function::new(
38406                            "LIST_TRANSFORM".to_string(),
38407                            vec![arr, lambda],
38408                        )));
38409                        Ok(Expression::Function(Box::new(Function::new(
38410                            "ARRAY_TO_STRING".to_string(),
38411                            vec![list_transform, sep],
38412                        ))))
38413                    }
38414                    _ => Ok(Expression::Function(Box::new(Function::new(
38415                        "ARRAY_TO_STRING".to_string(),
38416                        vec![arr, sep, null_text],
38417                    )))),
38418                }
38419            }
38420
38421            // LENGTH(x) -> CASE TYPEOF(x) ... for DuckDB
38422            "LENGTH" if args.len() == 1 => {
38423                let arg = args.remove(0);
38424                match target {
38425                    DialectType::DuckDB => {
38426                        // CASE TYPEOF(foo) WHEN 'BLOB' THEN OCTET_LENGTH(CAST(foo AS BLOB)) ELSE LENGTH(CAST(foo AS TEXT)) END
38427                        let typeof_func = Expression::Function(Box::new(Function::new(
38428                            "TYPEOF".to_string(),
38429                            vec![arg.clone()],
38430                        )));
38431                        let blob_cast = Expression::Cast(Box::new(Cast {
38432                            this: arg.clone(),
38433                            to: DataType::VarBinary { length: None },
38434                            trailing_comments: vec![],
38435                            double_colon_syntax: false,
38436                            format: None,
38437                            default: None,
38438                            inferred_type: None,
38439                        }));
38440                        let octet_length = Expression::Function(Box::new(Function::new(
38441                            "OCTET_LENGTH".to_string(),
38442                            vec![blob_cast],
38443                        )));
38444                        let text_cast = Expression::Cast(Box::new(Cast {
38445                            this: arg,
38446                            to: DataType::Text,
38447                            trailing_comments: vec![],
38448                            double_colon_syntax: false,
38449                            format: None,
38450                            default: None,
38451                            inferred_type: None,
38452                        }));
38453                        let length_text = Expression::Function(Box::new(Function::new(
38454                            "LENGTH".to_string(),
38455                            vec![text_cast],
38456                        )));
38457                        Ok(Expression::Case(Box::new(crate::expressions::Case {
38458                            operand: Some(typeof_func),
38459                            whens: vec![(
38460                                Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
38461                                octet_length,
38462                            )],
38463                            else_: Some(length_text),
38464                            comments: Vec::new(),
38465                            inferred_type: None,
38466                        })))
38467                    }
38468                    _ => Ok(Expression::Function(Box::new(Function::new(
38469                        "LENGTH".to_string(),
38470                        vec![arg],
38471                    )))),
38472                }
38473            }
38474
38475            // PERCENTILE_CONT(x, fraction RESPECT NULLS) -> QUANTILE_CONT(x, fraction) for DuckDB
38476            "PERCENTILE_CONT" if args.len() >= 2 && matches!(source, DialectType::BigQuery) => {
38477                // BigQuery PERCENTILE_CONT(x, fraction [RESPECT|IGNORE NULLS]) OVER ()
38478                // The args should be [x, fraction] with the null handling stripped
38479                // For DuckDB: QUANTILE_CONT(x, fraction)
38480                // For Spark: PERCENTILE_CONT(x, fraction) RESPECT NULLS (handled at window level)
38481                match target {
38482                    DialectType::DuckDB => {
38483                        // Strip down to just 2 args, rename to QUANTILE_CONT
38484                        let x = args[0].clone();
38485                        let frac = args[1].clone();
38486                        Ok(Expression::Function(Box::new(Function::new(
38487                            "QUANTILE_CONT".to_string(),
38488                            vec![x, frac],
38489                        ))))
38490                    }
38491                    _ => Ok(Expression::Function(Box::new(Function::new(
38492                        "PERCENTILE_CONT".to_string(),
38493                        args,
38494                    )))),
38495                }
38496            }
38497
38498            // All others: pass through
38499            _ => Ok(Expression::Function(Box::new(Function {
38500                name: f.name,
38501                args,
38502                distinct: f.distinct,
38503                trailing_comments: f.trailing_comments,
38504                use_bracket_syntax: f.use_bracket_syntax,
38505                no_parens: f.no_parens,
38506                quoted: f.quoted,
38507                span: None,
38508                inferred_type: None,
38509            }))),
38510        }
38511    }
38512
38513    /// Check if we can reliably infer the SQL type for Presto/Trino ROW CAST.
38514    /// Returns false for column references and other non-literal expressions where the type is unknown.
38515    fn can_infer_presto_type(expr: &Expression) -> bool {
38516        match expr {
38517            Expression::Literal(_) => true,
38518            Expression::Boolean(_) => true,
38519            Expression::Array(_) | Expression::ArrayFunc(_) => true,
38520            Expression::Struct(_) | Expression::StructFunc(_) => true,
38521            Expression::Function(f) => {
38522                f.name.eq_ignore_ascii_case("STRUCT")
38523                    || f.name.eq_ignore_ascii_case("ROW")
38524                    || f.name.eq_ignore_ascii_case("CURRENT_DATE")
38525                    || f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
38526                    || f.name.eq_ignore_ascii_case("NOW")
38527            }
38528            Expression::Cast(_) => true,
38529            Expression::Neg(inner) => Self::can_infer_presto_type(&inner.this),
38530            _ => false,
38531        }
38532    }
38533
38534    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
38535    fn infer_sql_type_for_presto(expr: &Expression) -> String {
38536        use crate::expressions::Literal;
38537        match expr {
38538            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38539                "VARCHAR".to_string()
38540            }
38541            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
38542                let Literal::Number(n) = lit.as_ref() else {
38543                    unreachable!()
38544                };
38545                if n.contains('.') {
38546                    "DOUBLE".to_string()
38547                } else {
38548                    "INTEGER".to_string()
38549                }
38550            }
38551            Expression::Boolean(_) => "BOOLEAN".to_string(),
38552            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
38553                "DATE".to_string()
38554            }
38555            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
38556                "TIMESTAMP".to_string()
38557            }
38558            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
38559                "TIMESTAMP".to_string()
38560            }
38561            Expression::Array(_) | Expression::ArrayFunc(_) => "ARRAY(VARCHAR)".to_string(),
38562            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
38563            Expression::Function(f) => {
38564                if f.name.eq_ignore_ascii_case("STRUCT") || f.name.eq_ignore_ascii_case("ROW") {
38565                    "ROW".to_string()
38566                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
38567                    "DATE".to_string()
38568                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
38569                    || f.name.eq_ignore_ascii_case("NOW")
38570                {
38571                    "TIMESTAMP".to_string()
38572                } else {
38573                    "VARCHAR".to_string()
38574                }
38575            }
38576            Expression::Cast(c) => {
38577                // If already cast, use the target type
38578                Self::data_type_to_presto_string(&c.to)
38579            }
38580            _ => "VARCHAR".to_string(),
38581        }
38582    }
38583
38584    /// Convert a DataType to its Presto/Trino string representation for ROW type
38585    fn data_type_to_presto_string(dt: &crate::expressions::DataType) -> String {
38586        use crate::expressions::DataType;
38587        match dt {
38588            DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
38589                "VARCHAR".to_string()
38590            }
38591            DataType::Int { .. }
38592            | DataType::BigInt { .. }
38593            | DataType::SmallInt { .. }
38594            | DataType::TinyInt { .. } => "INTEGER".to_string(),
38595            DataType::Float { .. } | DataType::Double { .. } => "DOUBLE".to_string(),
38596            DataType::Boolean => "BOOLEAN".to_string(),
38597            DataType::Date => "DATE".to_string(),
38598            DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
38599            DataType::Struct { fields, .. } => {
38600                let field_strs: Vec<String> = fields
38601                    .iter()
38602                    .map(|f| {
38603                        format!(
38604                            "{} {}",
38605                            f.name,
38606                            Self::data_type_to_presto_string(&f.data_type)
38607                        )
38608                    })
38609                    .collect();
38610                format!("ROW({})", field_strs.join(", "))
38611            }
38612            DataType::Array { element_type, .. } => {
38613                format!("ARRAY({})", Self::data_type_to_presto_string(element_type))
38614            }
38615            DataType::Custom { name } => {
38616                // Pass through custom type names (e.g., "INTEGER", "VARCHAR" from earlier inference)
38617                name.clone()
38618            }
38619            _ => "VARCHAR".to_string(),
38620        }
38621    }
38622
38623    /// Convert IntervalUnit to string
38624    fn interval_unit_to_string(unit: &crate::expressions::IntervalUnit) -> &'static str {
38625        match unit {
38626            crate::expressions::IntervalUnit::Year => "YEAR",
38627            crate::expressions::IntervalUnit::Quarter => "QUARTER",
38628            crate::expressions::IntervalUnit::Month => "MONTH",
38629            crate::expressions::IntervalUnit::Week => "WEEK",
38630            crate::expressions::IntervalUnit::Day => "DAY",
38631            crate::expressions::IntervalUnit::Hour => "HOUR",
38632            crate::expressions::IntervalUnit::Minute => "MINUTE",
38633            crate::expressions::IntervalUnit::Second => "SECOND",
38634            crate::expressions::IntervalUnit::Millisecond => "MILLISECOND",
38635            crate::expressions::IntervalUnit::Microsecond => "MICROSECOND",
38636            crate::expressions::IntervalUnit::Nanosecond => "NANOSECOND",
38637        }
38638    }
38639
38640    /// Extract unit string from an expression (uppercased)
38641    fn get_unit_str_static(expr: &Expression) -> String {
38642        use crate::expressions::Literal;
38643        match expr {
38644            Expression::Identifier(id) => id.name.to_ascii_uppercase(),
38645            Expression::Var(v) => v.this.to_ascii_uppercase(),
38646            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38647                let Literal::String(s) = lit.as_ref() else {
38648                    unreachable!()
38649                };
38650                s.to_ascii_uppercase()
38651            }
38652            Expression::Column(col) => col.name.name.to_ascii_uppercase(),
38653            Expression::Function(f) => {
38654                let base = f.name.to_ascii_uppercase();
38655                if !f.args.is_empty() {
38656                    let inner = Self::get_unit_str_static(&f.args[0]);
38657                    format!("{}({})", base, inner)
38658                } else {
38659                    base
38660                }
38661            }
38662            _ => "DAY".to_string(),
38663        }
38664    }
38665
38666    /// Parse unit string to IntervalUnit
38667    fn parse_interval_unit_static(s: &str) -> crate::expressions::IntervalUnit {
38668        match s {
38669            "YEAR" | "YY" | "YYYY" => crate::expressions::IntervalUnit::Year,
38670            "QUARTER" | "QQ" | "Q" => crate::expressions::IntervalUnit::Quarter,
38671            "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" | "M" => {
38672                crate::expressions::IntervalUnit::Month
38673            }
38674            "WEEK" | "WK" | "WW" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
38675            "DAY" | "DD" | "D" | "DY" => crate::expressions::IntervalUnit::Day,
38676            "HOUR" | "HH" => crate::expressions::IntervalUnit::Hour,
38677            "MINUTE" | "MI" | "N" => crate::expressions::IntervalUnit::Minute,
38678            "SECOND" | "SS" | "S" => crate::expressions::IntervalUnit::Second,
38679            "MILLISECOND" | "MS" => crate::expressions::IntervalUnit::Millisecond,
38680            "MICROSECOND" | "MCS" | "US" => crate::expressions::IntervalUnit::Microsecond,
38681            _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
38682            _ => crate::expressions::IntervalUnit::Day,
38683        }
38684    }
38685
38686    /// Convert expression to simple string for interval building
38687    fn expr_to_string_static(expr: &Expression) -> String {
38688        use crate::expressions::Literal;
38689        match expr {
38690            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
38691                let Literal::Number(s) = lit.as_ref() else {
38692                    unreachable!()
38693                };
38694                s.clone()
38695            }
38696            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38697                let Literal::String(s) = lit.as_ref() else {
38698                    unreachable!()
38699                };
38700                s.clone()
38701            }
38702            Expression::Identifier(id) => id.name.clone(),
38703            Expression::Neg(f) => format!("-{}", Self::expr_to_string_static(&f.this)),
38704            _ => "1".to_string(),
38705        }
38706    }
38707
38708    /// Extract a simple string representation from a literal expression
38709    fn expr_to_string(expr: &Expression) -> String {
38710        use crate::expressions::Literal;
38711        match expr {
38712            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
38713                let Literal::Number(s) = lit.as_ref() else {
38714                    unreachable!()
38715                };
38716                s.clone()
38717            }
38718            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38719                let Literal::String(s) = lit.as_ref() else {
38720                    unreachable!()
38721                };
38722                s.clone()
38723            }
38724            Expression::Neg(f) => format!("-{}", Self::expr_to_string(&f.this)),
38725            Expression::Identifier(id) => id.name.clone(),
38726            _ => "1".to_string(),
38727        }
38728    }
38729
38730    /// Quote an interval value expression as a string literal if it's a number (or negated number)
38731    fn quote_interval_val(expr: &Expression) -> Expression {
38732        use crate::expressions::Literal;
38733        match expr {
38734            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
38735                let Literal::Number(n) = lit.as_ref() else {
38736                    unreachable!()
38737                };
38738                Expression::Literal(Box::new(Literal::String(n.clone())))
38739            }
38740            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => expr.clone(),
38741            Expression::Neg(inner) => {
38742                if let Expression::Literal(lit) = &inner.this {
38743                    if let Literal::Number(n) = lit.as_ref() {
38744                        Expression::Literal(Box::new(Literal::String(format!("-{}", n))))
38745                    } else {
38746                        inner.this.clone()
38747                    }
38748                } else {
38749                    expr.clone()
38750                }
38751            }
38752            _ => expr.clone(),
38753        }
38754    }
38755
38756    /// Check if a timestamp string contains timezone info (offset like +02:00, or named timezone)
38757    fn timestamp_string_has_timezone(ts: &str) -> bool {
38758        let trimmed = ts.trim();
38759        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN at end
38760        if let Some(last_space) = trimmed.rfind(' ') {
38761            let suffix = &trimmed[last_space + 1..];
38762            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
38763                let rest = &suffix[1..];
38764                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
38765                    return true;
38766                }
38767            }
38768        }
38769        // Check for named timezone abbreviations
38770        let ts_lower = trimmed.to_ascii_lowercase();
38771        let tz_abbrevs = [" utc", " gmt", " cet", " est", " pst", " cst", " mst"];
38772        for abbrev in &tz_abbrevs {
38773            if ts_lower.ends_with(abbrev) {
38774                return true;
38775            }
38776        }
38777        false
38778    }
38779
38780    /// Maybe CAST timestamp literal to TIMESTAMPTZ for Snowflake
38781    fn maybe_cast_ts_to_tz(expr: Expression, func_name: &str) -> Expression {
38782        use crate::expressions::{Cast, DataType, Literal};
38783        match expr {
38784            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
38785                let Literal::Timestamp(s) = lit.as_ref() else {
38786                    unreachable!()
38787                };
38788                let tz = func_name.starts_with("TIMESTAMP");
38789                Expression::Cast(Box::new(Cast {
38790                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
38791                    to: if tz {
38792                        DataType::Timestamp {
38793                            timezone: true,
38794                            precision: None,
38795                        }
38796                    } else {
38797                        DataType::Timestamp {
38798                            timezone: false,
38799                            precision: None,
38800                        }
38801                    },
38802                    trailing_comments: vec![],
38803                    double_colon_syntax: false,
38804                    format: None,
38805                    default: None,
38806                    inferred_type: None,
38807                }))
38808            }
38809            other => other,
38810        }
38811    }
38812
38813    /// Maybe CAST timestamp literal to TIMESTAMP (no tz)
38814    fn maybe_cast_ts(expr: Expression) -> Expression {
38815        use crate::expressions::{Cast, DataType, Literal};
38816        match expr {
38817            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
38818                let Literal::Timestamp(s) = lit.as_ref() else {
38819                    unreachable!()
38820                };
38821                Expression::Cast(Box::new(Cast {
38822                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
38823                    to: DataType::Timestamp {
38824                        timezone: false,
38825                        precision: None,
38826                    },
38827                    trailing_comments: vec![],
38828                    double_colon_syntax: false,
38829                    format: None,
38830                    default: None,
38831                    inferred_type: None,
38832                }))
38833            }
38834            other => other,
38835        }
38836    }
38837
38838    /// Convert DATE 'x' literal to CAST('x' AS DATE)
38839    fn date_literal_to_cast(expr: Expression) -> Expression {
38840        use crate::expressions::{Cast, DataType, Literal};
38841        match expr {
38842            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
38843                let Literal::Date(s) = lit.as_ref() else {
38844                    unreachable!()
38845                };
38846                Expression::Cast(Box::new(Cast {
38847                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
38848                    to: DataType::Date,
38849                    trailing_comments: vec![],
38850                    double_colon_syntax: false,
38851                    format: None,
38852                    default: None,
38853                    inferred_type: None,
38854                }))
38855            }
38856            other => other,
38857        }
38858    }
38859
38860    /// Ensure an expression that should be a date is CAST(... AS DATE).
38861    /// Handles both DATE literals and string literals that look like dates.
38862    fn ensure_cast_date(expr: Expression) -> Expression {
38863        use crate::expressions::{Cast, DataType, Literal};
38864        match expr {
38865            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
38866                let Literal::Date(s) = lit.as_ref() else {
38867                    unreachable!()
38868                };
38869                Expression::Cast(Box::new(Cast {
38870                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
38871                    to: DataType::Date,
38872                    trailing_comments: vec![],
38873                    double_colon_syntax: false,
38874                    format: None,
38875                    default: None,
38876                    inferred_type: None,
38877                }))
38878            }
38879            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
38880                // String literal that should be a date -> CAST('s' AS DATE)
38881                Expression::Cast(Box::new(Cast {
38882                    this: expr,
38883                    to: DataType::Date,
38884                    trailing_comments: vec![],
38885                    double_colon_syntax: false,
38886                    format: None,
38887                    default: None,
38888                    inferred_type: None,
38889                }))
38890            }
38891            // Already a CAST or other expression -> leave as-is
38892            other => other,
38893        }
38894    }
38895
38896    /// Force CAST(expr AS DATE) for any expression (not just literals)
38897    /// Skips if the expression is already a CAST to DATE
38898    fn force_cast_date(expr: Expression) -> Expression {
38899        use crate::expressions::{Cast, DataType};
38900        // If it's already a CAST to DATE, don't double-wrap
38901        if let Expression::Cast(ref c) = expr {
38902            if matches!(c.to, DataType::Date) {
38903                return expr;
38904            }
38905        }
38906        Expression::Cast(Box::new(Cast {
38907            this: expr,
38908            to: DataType::Date,
38909            trailing_comments: vec![],
38910            double_colon_syntax: false,
38911            format: None,
38912            default: None,
38913            inferred_type: None,
38914        }))
38915    }
38916
38917    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
38918    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
38919    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
38920    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
38921
38922    fn ensure_to_date_preserved(expr: Expression) -> Expression {
38923        use crate::expressions::{Function, Literal};
38924        if matches!(expr, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)))
38925        {
38926            Expression::Function(Box::new(Function::new(
38927                Self::PRESERVED_TO_DATE.to_string(),
38928                vec![expr],
38929            )))
38930        } else {
38931            expr
38932        }
38933    }
38934
38935    /// TRY_CAST(expr AS DATE) - used for DuckDB when TO_DATE is unwrapped
38936    fn try_cast_date(expr: Expression) -> Expression {
38937        use crate::expressions::{Cast, DataType};
38938        Expression::TryCast(Box::new(Cast {
38939            this: expr,
38940            to: DataType::Date,
38941            trailing_comments: vec![],
38942            double_colon_syntax: false,
38943            format: None,
38944            default: None,
38945            inferred_type: None,
38946        }))
38947    }
38948
38949    /// CAST(CAST(expr AS TIMESTAMP) AS DATE) - used when Hive string dates need to be cast
38950    fn double_cast_timestamp_date(expr: Expression) -> Expression {
38951        use crate::expressions::{Cast, DataType};
38952        let inner = Expression::Cast(Box::new(Cast {
38953            this: expr,
38954            to: DataType::Timestamp {
38955                timezone: false,
38956                precision: None,
38957            },
38958            trailing_comments: vec![],
38959            double_colon_syntax: false,
38960            format: None,
38961            default: None,
38962            inferred_type: None,
38963        }));
38964        Expression::Cast(Box::new(Cast {
38965            this: inner,
38966            to: DataType::Date,
38967            trailing_comments: vec![],
38968            double_colon_syntax: false,
38969            format: None,
38970            default: None,
38971            inferred_type: None,
38972        }))
38973    }
38974
38975    /// CAST(CAST(expr AS DATETIME) AS DATE) - BigQuery variant
38976    fn double_cast_datetime_date(expr: Expression) -> Expression {
38977        use crate::expressions::{Cast, DataType};
38978        let inner = Expression::Cast(Box::new(Cast {
38979            this: expr,
38980            to: DataType::Custom {
38981                name: "DATETIME".to_string(),
38982            },
38983            trailing_comments: vec![],
38984            double_colon_syntax: false,
38985            format: None,
38986            default: None,
38987            inferred_type: None,
38988        }));
38989        Expression::Cast(Box::new(Cast {
38990            this: inner,
38991            to: DataType::Date,
38992            trailing_comments: vec![],
38993            double_colon_syntax: false,
38994            format: None,
38995            default: None,
38996            inferred_type: None,
38997        }))
38998    }
38999
39000    /// CAST(CAST(expr AS DATETIME2) AS DATE) - TSQL variant
39001    fn double_cast_datetime2_date(expr: Expression) -> Expression {
39002        use crate::expressions::{Cast, DataType};
39003        let inner = Expression::Cast(Box::new(Cast {
39004            this: expr,
39005            to: DataType::Custom {
39006                name: "DATETIME2".to_string(),
39007            },
39008            trailing_comments: vec![],
39009            double_colon_syntax: false,
39010            format: None,
39011            default: None,
39012            inferred_type: None,
39013        }));
39014        Expression::Cast(Box::new(Cast {
39015            this: inner,
39016            to: DataType::Date,
39017            trailing_comments: vec![],
39018            double_colon_syntax: false,
39019            format: None,
39020            default: None,
39021            inferred_type: None,
39022        }))
39023    }
39024
39025    /// Convert Hive/Java-style date format strings to C-style (strftime) format
39026    /// e.g., "yyyy-MM-dd'T'HH" -> "%Y-%m-%d'T'%H"
39027    fn hive_format_to_c_format(fmt: &str) -> String {
39028        let mut result = String::new();
39029        let chars: Vec<char> = fmt.chars().collect();
39030        let mut i = 0;
39031        while i < chars.len() {
39032            match chars[i] {
39033                'y' => {
39034                    let mut count = 0;
39035                    while i < chars.len() && chars[i] == 'y' {
39036                        count += 1;
39037                        i += 1;
39038                    }
39039                    if count >= 4 {
39040                        result.push_str("%Y");
39041                    } else if count == 2 {
39042                        result.push_str("%y");
39043                    } else {
39044                        result.push_str("%Y");
39045                    }
39046                }
39047                'M' => {
39048                    let mut count = 0;
39049                    while i < chars.len() && chars[i] == 'M' {
39050                        count += 1;
39051                        i += 1;
39052                    }
39053                    if count >= 3 {
39054                        result.push_str("%b");
39055                    } else if count == 2 {
39056                        result.push_str("%m");
39057                    } else {
39058                        result.push_str("%m");
39059                    }
39060                }
39061                'd' => {
39062                    let mut _count = 0;
39063                    while i < chars.len() && chars[i] == 'd' {
39064                        _count += 1;
39065                        i += 1;
39066                    }
39067                    result.push_str("%d");
39068                }
39069                'H' => {
39070                    let mut _count = 0;
39071                    while i < chars.len() && chars[i] == 'H' {
39072                        _count += 1;
39073                        i += 1;
39074                    }
39075                    result.push_str("%H");
39076                }
39077                'h' => {
39078                    let mut _count = 0;
39079                    while i < chars.len() && chars[i] == 'h' {
39080                        _count += 1;
39081                        i += 1;
39082                    }
39083                    result.push_str("%I");
39084                }
39085                'm' => {
39086                    let mut _count = 0;
39087                    while i < chars.len() && chars[i] == 'm' {
39088                        _count += 1;
39089                        i += 1;
39090                    }
39091                    result.push_str("%M");
39092                }
39093                's' => {
39094                    let mut _count = 0;
39095                    while i < chars.len() && chars[i] == 's' {
39096                        _count += 1;
39097                        i += 1;
39098                    }
39099                    result.push_str("%S");
39100                }
39101                'S' => {
39102                    // Fractional seconds - skip
39103                    while i < chars.len() && chars[i] == 'S' {
39104                        i += 1;
39105                    }
39106                    result.push_str("%f");
39107                }
39108                'a' => {
39109                    // AM/PM
39110                    while i < chars.len() && chars[i] == 'a' {
39111                        i += 1;
39112                    }
39113                    result.push_str("%p");
39114                }
39115                'E' => {
39116                    let mut count = 0;
39117                    while i < chars.len() && chars[i] == 'E' {
39118                        count += 1;
39119                        i += 1;
39120                    }
39121                    if count >= 4 {
39122                        result.push_str("%A");
39123                    } else {
39124                        result.push_str("%a");
39125                    }
39126                }
39127                '\'' => {
39128                    // Quoted literal text - pass through the quotes and content
39129                    result.push('\'');
39130                    i += 1;
39131                    while i < chars.len() && chars[i] != '\'' {
39132                        result.push(chars[i]);
39133                        i += 1;
39134                    }
39135                    if i < chars.len() {
39136                        result.push('\'');
39137                        i += 1;
39138                    }
39139                }
39140                c => {
39141                    result.push(c);
39142                    i += 1;
39143                }
39144            }
39145        }
39146        result
39147    }
39148
39149    /// Convert Hive/Java format to Presto format (uses %T for HH:mm:ss)
39150    fn hive_format_to_presto_format(fmt: &str) -> String {
39151        let c_fmt = Self::hive_format_to_c_format(fmt);
39152        // Presto uses %T for HH:MM:SS
39153        c_fmt.replace("%H:%M:%S", "%T")
39154    }
39155
39156    /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMP)
39157    fn ensure_cast_timestamp(expr: Expression) -> Expression {
39158        use crate::expressions::{Cast, DataType, Literal};
39159        match expr {
39160            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
39161                let Literal::Timestamp(s) = lit.as_ref() else {
39162                    unreachable!()
39163                };
39164                Expression::Cast(Box::new(Cast {
39165                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39166                    to: DataType::Timestamp {
39167                        timezone: false,
39168                        precision: None,
39169                    },
39170                    trailing_comments: vec![],
39171                    double_colon_syntax: false,
39172                    format: None,
39173                    default: None,
39174                    inferred_type: None,
39175                }))
39176            }
39177            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
39178                Expression::Cast(Box::new(Cast {
39179                    this: expr,
39180                    to: DataType::Timestamp {
39181                        timezone: false,
39182                        precision: None,
39183                    },
39184                    trailing_comments: vec![],
39185                    double_colon_syntax: false,
39186                    format: None,
39187                    default: None,
39188                    inferred_type: None,
39189                }))
39190            }
39191            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
39192                let Literal::Datetime(s) = lit.as_ref() else {
39193                    unreachable!()
39194                };
39195                Expression::Cast(Box::new(Cast {
39196                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39197                    to: DataType::Timestamp {
39198                        timezone: false,
39199                        precision: None,
39200                    },
39201                    trailing_comments: vec![],
39202                    double_colon_syntax: false,
39203                    format: None,
39204                    default: None,
39205                    inferred_type: None,
39206                }))
39207            }
39208            other => other,
39209        }
39210    }
39211
39212    /// Force CAST to TIMESTAMP for any expression (not just literals)
39213    /// Used when transpiling from Redshift/TSQL where DATEDIFF/DATEADD args need explicit timestamp cast
39214    fn force_cast_timestamp(expr: Expression) -> Expression {
39215        use crate::expressions::{Cast, DataType};
39216        // Don't double-wrap if already a CAST to TIMESTAMP
39217        if let Expression::Cast(ref c) = expr {
39218            if matches!(c.to, DataType::Timestamp { .. }) {
39219                return expr;
39220            }
39221        }
39222        Expression::Cast(Box::new(Cast {
39223            this: expr,
39224            to: DataType::Timestamp {
39225                timezone: false,
39226                precision: None,
39227            },
39228            trailing_comments: vec![],
39229            double_colon_syntax: false,
39230            format: None,
39231            default: None,
39232            inferred_type: None,
39233        }))
39234    }
39235
39236    /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMPTZ)
39237    fn ensure_cast_timestamptz(expr: Expression) -> Expression {
39238        use crate::expressions::{Cast, DataType, Literal};
39239        match expr {
39240            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
39241                let Literal::Timestamp(s) = lit.as_ref() else {
39242                    unreachable!()
39243                };
39244                Expression::Cast(Box::new(Cast {
39245                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39246                    to: DataType::Timestamp {
39247                        timezone: true,
39248                        precision: None,
39249                    },
39250                    trailing_comments: vec![],
39251                    double_colon_syntax: false,
39252                    format: None,
39253                    default: None,
39254                    inferred_type: None,
39255                }))
39256            }
39257            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
39258                Expression::Cast(Box::new(Cast {
39259                    this: expr,
39260                    to: DataType::Timestamp {
39261                        timezone: true,
39262                        precision: None,
39263                    },
39264                    trailing_comments: vec![],
39265                    double_colon_syntax: false,
39266                    format: None,
39267                    default: None,
39268                    inferred_type: None,
39269                }))
39270            }
39271            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
39272                let Literal::Datetime(s) = lit.as_ref() else {
39273                    unreachable!()
39274                };
39275                Expression::Cast(Box::new(Cast {
39276                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39277                    to: DataType::Timestamp {
39278                        timezone: true,
39279                        precision: None,
39280                    },
39281                    trailing_comments: vec![],
39282                    double_colon_syntax: false,
39283                    format: None,
39284                    default: None,
39285                    inferred_type: None,
39286                }))
39287            }
39288            other => other,
39289        }
39290    }
39291
39292    /// Ensure expression is CAST to DATETIME (for BigQuery)
39293    fn ensure_cast_datetime(expr: Expression) -> Expression {
39294        use crate::expressions::{Cast, DataType, Literal};
39295        match expr {
39296            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
39297                Expression::Cast(Box::new(Cast {
39298                    this: expr,
39299                    to: DataType::Custom {
39300                        name: "DATETIME".to_string(),
39301                    },
39302                    trailing_comments: vec![],
39303                    double_colon_syntax: false,
39304                    format: None,
39305                    default: None,
39306                    inferred_type: None,
39307                }))
39308            }
39309            other => other,
39310        }
39311    }
39312
39313    /// Force CAST expression to DATETIME (for BigQuery) - always wraps unless already DATETIME
39314    fn force_cast_datetime(expr: Expression) -> Expression {
39315        use crate::expressions::{Cast, DataType};
39316        if let Expression::Cast(ref c) = expr {
39317            if let DataType::Custom { ref name } = c.to {
39318                if name.eq_ignore_ascii_case("DATETIME") {
39319                    return expr;
39320                }
39321            }
39322        }
39323        Expression::Cast(Box::new(Cast {
39324            this: expr,
39325            to: DataType::Custom {
39326                name: "DATETIME".to_string(),
39327            },
39328            trailing_comments: vec![],
39329            double_colon_syntax: false,
39330            format: None,
39331            default: None,
39332            inferred_type: None,
39333        }))
39334    }
39335
39336    /// Ensure expression is CAST to DATETIME2 (for TSQL)
39337    fn ensure_cast_datetime2(expr: Expression) -> Expression {
39338        use crate::expressions::{Cast, DataType, Literal};
39339        match expr {
39340            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
39341                Expression::Cast(Box::new(Cast {
39342                    this: expr,
39343                    to: DataType::Custom {
39344                        name: "DATETIME2".to_string(),
39345                    },
39346                    trailing_comments: vec![],
39347                    double_colon_syntax: false,
39348                    format: None,
39349                    default: None,
39350                    inferred_type: None,
39351                }))
39352            }
39353            other => other,
39354        }
39355    }
39356
39357    /// Convert TIMESTAMP 'x' literal to CAST('x' AS TIMESTAMPTZ) for DuckDB
39358    fn ts_literal_to_cast_tz(expr: Expression) -> Expression {
39359        use crate::expressions::{Cast, DataType, Literal};
39360        match expr {
39361            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
39362                let Literal::Timestamp(s) = lit.as_ref() else {
39363                    unreachable!()
39364                };
39365                Expression::Cast(Box::new(Cast {
39366                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39367                    to: DataType::Timestamp {
39368                        timezone: true,
39369                        precision: None,
39370                    },
39371                    trailing_comments: vec![],
39372                    double_colon_syntax: false,
39373                    format: None,
39374                    default: None,
39375                    inferred_type: None,
39376                }))
39377            }
39378            other => other,
39379        }
39380    }
39381
39382    /// Convert BigQuery format string to Snowflake format string
39383    fn bq_format_to_snowflake(format_expr: &Expression) -> Expression {
39384        use crate::expressions::Literal;
39385        if let Expression::Literal(lit) = format_expr {
39386            if let Literal::String(s) = lit.as_ref() {
39387                let sf = s
39388                    .replace("%Y", "yyyy")
39389                    .replace("%m", "mm")
39390                    .replace("%d", "DD")
39391                    .replace("%H", "HH24")
39392                    .replace("%M", "MI")
39393                    .replace("%S", "SS")
39394                    .replace("%b", "mon")
39395                    .replace("%B", "Month")
39396                    .replace("%e", "FMDD");
39397                Expression::Literal(Box::new(Literal::String(sf)))
39398            } else {
39399                format_expr.clone()
39400            }
39401        } else {
39402            format_expr.clone()
39403        }
39404    }
39405
39406    /// Convert BigQuery format string to DuckDB format string
39407    fn bq_format_to_duckdb(format_expr: &Expression) -> Expression {
39408        use crate::expressions::Literal;
39409        if let Expression::Literal(lit) = format_expr {
39410            if let Literal::String(s) = lit.as_ref() {
39411                let duck = s
39412                    .replace("%T", "%H:%M:%S")
39413                    .replace("%F", "%Y-%m-%d")
39414                    .replace("%D", "%m/%d/%y")
39415                    .replace("%x", "%m/%d/%y")
39416                    .replace("%c", "%a %b %-d %H:%M:%S %Y")
39417                    .replace("%e", "%-d")
39418                    .replace("%E6S", "%S.%f");
39419                Expression::Literal(Box::new(Literal::String(duck)))
39420            } else {
39421                format_expr.clone()
39422            }
39423        } else {
39424            format_expr.clone()
39425        }
39426    }
39427
39428    /// Convert BigQuery CAST FORMAT elements (like YYYY, MM, DD) to strftime (like %Y, %m, %d)
39429    fn bq_cast_format_to_strftime(format_expr: &Expression) -> Expression {
39430        use crate::expressions::Literal;
39431        if let Expression::Literal(lit) = format_expr {
39432            if let Literal::String(s) = lit.as_ref() {
39433                // Replace format elements from longest to shortest to avoid partial matches
39434                let result = s
39435                    .replace("YYYYMMDD", "%Y%m%d")
39436                    .replace("YYYY", "%Y")
39437                    .replace("YY", "%y")
39438                    .replace("MONTH", "%B")
39439                    .replace("MON", "%b")
39440                    .replace("MM", "%m")
39441                    .replace("DD", "%d")
39442                    .replace("HH24", "%H")
39443                    .replace("HH12", "%I")
39444                    .replace("HH", "%I")
39445                    .replace("MI", "%M")
39446                    .replace("SSTZH", "%S%z")
39447                    .replace("SS", "%S")
39448                    .replace("TZH", "%z");
39449                Expression::Literal(Box::new(Literal::String(result)))
39450            } else {
39451                format_expr.clone()
39452            }
39453        } else {
39454            format_expr.clone()
39455        }
39456    }
39457
39458    /// Normalize BigQuery format strings for BQ->BQ output
39459    fn bq_format_normalize_bq(format_expr: &Expression) -> Expression {
39460        use crate::expressions::Literal;
39461        if let Expression::Literal(lit) = format_expr {
39462            if let Literal::String(s) = lit.as_ref() {
39463                let norm = s.replace("%H:%M:%S", "%T").replace("%x", "%D");
39464                Expression::Literal(Box::new(Literal::String(norm)))
39465            } else {
39466                format_expr.clone()
39467            }
39468        } else {
39469            format_expr.clone()
39470        }
39471    }
39472}
39473
39474#[cfg(test)]
39475mod tests {
39476    use super::*;
39477
39478    #[test]
39479    fn test_dialect_type_from_str() {
39480        assert_eq!(
39481            "postgres".parse::<DialectType>().unwrap(),
39482            DialectType::PostgreSQL
39483        );
39484        assert_eq!(
39485            "postgresql".parse::<DialectType>().unwrap(),
39486            DialectType::PostgreSQL
39487        );
39488        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
39489        assert_eq!(
39490            "bigquery".parse::<DialectType>().unwrap(),
39491            DialectType::BigQuery
39492        );
39493    }
39494
39495    #[test]
39496    fn test_basic_transpile() {
39497        let dialect = Dialect::get(DialectType::Generic);
39498        let result = dialect
39499            .transpile("SELECT 1", DialectType::PostgreSQL)
39500            .unwrap();
39501        assert_eq!(result.len(), 1);
39502        assert_eq!(result[0], "SELECT 1");
39503    }
39504
39505    #[test]
39506    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
39507        let sqlite = Dialect::get(DialectType::SQLite);
39508        let result = sqlite
39509            .transpile(
39510                r#"CREATE TABLE "_collections" (
39511                    "type" TEXT DEFAULT "base" NOT NULL,
39512                    "fields" JSON DEFAULT "[]" NOT NULL,
39513                    "options" JSON DEFAULT "{}" NOT NULL
39514                )"#,
39515                DialectType::PostgreSQL,
39516            )
39517            .unwrap();
39518
39519        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
39520        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
39521        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
39522    }
39523
39524    #[test]
39525    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
39526        let sqlite = Dialect::get(DialectType::SQLite);
39527        let result = sqlite
39528            .transpile(
39529                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
39530                DialectType::SQLite,
39531            )
39532            .unwrap();
39533
39534        assert_eq!(
39535            result[0],
39536            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
39537        );
39538    }
39539
39540    #[test]
39541    fn test_function_transformation_mysql() {
39542        // NVL should be transformed to IFNULL in MySQL
39543        let dialect = Dialect::get(DialectType::Generic);
39544        let result = dialect
39545            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
39546            .unwrap();
39547        assert_eq!(result[0], "SELECT IFNULL(a, b)");
39548    }
39549
39550    #[test]
39551    fn test_get_path_duckdb() {
39552        // Test: step by step
39553        let snowflake = Dialect::get(DialectType::Snowflake);
39554
39555        // Step 1: Parse and check what Snowflake produces as intermediate
39556        let result_sf_sf = snowflake
39557            .transpile(
39558                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
39559                DialectType::Snowflake,
39560            )
39561            .unwrap();
39562        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
39563
39564        // Step 2: DuckDB target
39565        let result_sf_dk = snowflake
39566            .transpile(
39567                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
39568                DialectType::DuckDB,
39569            )
39570            .unwrap();
39571        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
39572
39573        // Step 3: GET_PATH directly
39574        let result_gp = snowflake
39575            .transpile(
39576                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
39577                DialectType::DuckDB,
39578            )
39579            .unwrap();
39580        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
39581    }
39582
39583    #[test]
39584    fn test_function_transformation_postgres() {
39585        // IFNULL should be transformed to COALESCE in PostgreSQL
39586        let dialect = Dialect::get(DialectType::Generic);
39587        let result = dialect
39588            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
39589            .unwrap();
39590        assert_eq!(result[0], "SELECT COALESCE(a, b)");
39591
39592        // NVL should also be transformed to COALESCE
39593        let result = dialect
39594            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
39595            .unwrap();
39596        assert_eq!(result[0], "SELECT COALESCE(a, b)");
39597    }
39598
39599    #[test]
39600    fn test_hive_cast_to_trycast() {
39601        // Hive CAST should become TRY_CAST for targets that support it
39602        let hive = Dialect::get(DialectType::Hive);
39603        let result = hive
39604            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
39605            .unwrap();
39606        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
39607
39608        let result = hive
39609            .transpile("CAST(1 AS INT)", DialectType::Presto)
39610            .unwrap();
39611        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
39612    }
39613
39614    #[test]
39615    fn test_hive_array_identity() {
39616        // Hive ARRAY<DATE> should preserve angle bracket syntax
39617        let sql = "CREATE EXTERNAL TABLE `my_table` (`a7` ARRAY<DATE>) ROW FORMAT SERDE 'a' STORED AS INPUTFORMAT 'b' OUTPUTFORMAT 'c' LOCATION 'd' TBLPROPERTIES ('e'='f')";
39618        let hive = Dialect::get(DialectType::Hive);
39619
39620        // Test via transpile (this works)
39621        let result = hive.transpile(sql, DialectType::Hive).unwrap();
39622        eprintln!("Hive ARRAY via transpile: {}", result[0]);
39623        assert!(
39624            result[0].contains("ARRAY<DATE>"),
39625            "transpile: Expected ARRAY<DATE>, got: {}",
39626            result[0]
39627        );
39628
39629        // Test via parse -> transform -> generate (identity test path)
39630        let ast = hive.parse(sql).unwrap();
39631        let transformed = hive.transform(ast[0].clone()).unwrap();
39632        let output = hive.generate(&transformed).unwrap();
39633        eprintln!("Hive ARRAY via identity path: {}", output);
39634        assert!(
39635            output.contains("ARRAY<DATE>"),
39636            "identity path: Expected ARRAY<DATE>, got: {}",
39637            output
39638        );
39639    }
39640
39641    #[test]
39642    fn test_starrocks_delete_between_expansion() {
39643        // StarRocks doesn't support BETWEEN in DELETE statements
39644        let dialect = Dialect::get(DialectType::Generic);
39645
39646        // BETWEEN should be expanded to >= AND <= in DELETE
39647        let result = dialect
39648            .transpile(
39649                "DELETE FROM t WHERE a BETWEEN b AND c",
39650                DialectType::StarRocks,
39651            )
39652            .unwrap();
39653        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
39654
39655        // NOT BETWEEN should be expanded to < OR > in DELETE
39656        let result = dialect
39657            .transpile(
39658                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
39659                DialectType::StarRocks,
39660            )
39661            .unwrap();
39662        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
39663
39664        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
39665        let result = dialect
39666            .transpile(
39667                "SELECT * FROM t WHERE a BETWEEN b AND c",
39668                DialectType::StarRocks,
39669            )
39670            .unwrap();
39671        assert!(
39672            result[0].contains("BETWEEN"),
39673            "BETWEEN should be preserved in SELECT"
39674        );
39675    }
39676
39677    #[test]
39678    fn test_snowflake_ltrim_rtrim_parse() {
39679        let sf = Dialect::get(DialectType::Snowflake);
39680        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
39681        let result = sf.transpile(sql, DialectType::DuckDB);
39682        match &result {
39683            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
39684            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
39685        }
39686        assert!(
39687            result.is_ok(),
39688            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
39689            result.err()
39690        );
39691    }
39692
39693    #[test]
39694    fn test_duckdb_count_if_parse() {
39695        let duck = Dialect::get(DialectType::DuckDB);
39696        let sql = "COUNT_IF(x)";
39697        let result = duck.transpile(sql, DialectType::DuckDB);
39698        match &result {
39699            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
39700            Err(e) => eprintln!("COUNT_IF error: {}", e),
39701        }
39702        assert!(
39703            result.is_ok(),
39704            "Expected successful parse of COUNT_IF(x), got error: {:?}",
39705            result.err()
39706        );
39707    }
39708
39709    #[test]
39710    fn test_tsql_cast_tinyint_parse() {
39711        let tsql = Dialect::get(DialectType::TSQL);
39712        let sql = "CAST(X AS TINYINT)";
39713        let result = tsql.transpile(sql, DialectType::DuckDB);
39714        match &result {
39715            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
39716            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
39717        }
39718        assert!(
39719            result.is_ok(),
39720            "Expected successful transpile, got error: {:?}",
39721            result.err()
39722        );
39723    }
39724
39725    #[test]
39726    fn test_pg_hash_bitwise_xor() {
39727        let dialect = Dialect::get(DialectType::PostgreSQL);
39728        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
39729        assert_eq!(result[0], "x # y");
39730    }
39731
39732    #[test]
39733    fn test_pg_array_to_duckdb() {
39734        let dialect = Dialect::get(DialectType::PostgreSQL);
39735        let result = dialect
39736            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
39737            .unwrap();
39738        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
39739    }
39740
39741    #[test]
39742    fn test_array_remove_bigquery() {
39743        let dialect = Dialect::get(DialectType::Generic);
39744        let result = dialect
39745            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
39746            .unwrap();
39747        assert_eq!(
39748            result[0],
39749            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
39750        );
39751    }
39752
39753    #[test]
39754    fn test_map_clickhouse_case() {
39755        let dialect = Dialect::get(DialectType::Generic);
39756        let parsed = dialect
39757            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
39758            .unwrap();
39759        eprintln!("MAP parsed: {:?}", parsed);
39760        let result = dialect
39761            .transpile(
39762                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
39763                DialectType::ClickHouse,
39764            )
39765            .unwrap();
39766        eprintln!("MAP result: {}", result[0]);
39767    }
39768
39769    #[test]
39770    fn test_generate_date_array_presto() {
39771        let dialect = Dialect::get(DialectType::Generic);
39772        let result = dialect.transpile(
39773            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39774            DialectType::Presto,
39775        ).unwrap();
39776        eprintln!("GDA -> Presto: {}", result[0]);
39777        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
39778    }
39779
39780    #[test]
39781    fn test_generate_date_array_postgres() {
39782        let dialect = Dialect::get(DialectType::Generic);
39783        let result = dialect.transpile(
39784            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39785            DialectType::PostgreSQL,
39786        ).unwrap();
39787        eprintln!("GDA -> PostgreSQL: {}", result[0]);
39788    }
39789
39790    #[test]
39791    fn test_generate_date_array_snowflake() {
39792        let dialect = Dialect::get(DialectType::Generic);
39793        let result = dialect
39794            .transpile(
39795                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39796                DialectType::Snowflake,
39797            )
39798            .unwrap();
39799        eprintln!("GDA -> Snowflake: {}", result[0]);
39800    }
39801
39802    #[test]
39803    fn test_array_length_generate_date_array_snowflake() {
39804        let dialect = Dialect::get(DialectType::Generic);
39805        let result = dialect.transpile(
39806            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39807            DialectType::Snowflake,
39808        ).unwrap();
39809        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
39810    }
39811
39812    #[test]
39813    fn test_generate_date_array_mysql() {
39814        let dialect = Dialect::get(DialectType::Generic);
39815        let result = dialect.transpile(
39816            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39817            DialectType::MySQL,
39818        ).unwrap();
39819        eprintln!("GDA -> MySQL: {}", result[0]);
39820    }
39821
39822    #[test]
39823    fn test_generate_date_array_redshift() {
39824        let dialect = Dialect::get(DialectType::Generic);
39825        let result = dialect.transpile(
39826            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39827            DialectType::Redshift,
39828        ).unwrap();
39829        eprintln!("GDA -> Redshift: {}", result[0]);
39830    }
39831
39832    #[test]
39833    fn test_generate_date_array_tsql() {
39834        let dialect = Dialect::get(DialectType::Generic);
39835        let result = dialect.transpile(
39836            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
39837            DialectType::TSQL,
39838        ).unwrap();
39839        eprintln!("GDA -> TSQL: {}", result[0]);
39840    }
39841
39842    #[test]
39843    fn test_struct_colon_syntax() {
39844        let dialect = Dialect::get(DialectType::Generic);
39845        // Test without colon first
39846        let result = dialect.transpile(
39847            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
39848            DialectType::ClickHouse,
39849        );
39850        match result {
39851            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
39852            Err(e) => eprintln!("STRUCT no colon error: {}", e),
39853        }
39854        // Now test with colon
39855        let result = dialect.transpile(
39856            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
39857            DialectType::ClickHouse,
39858        );
39859        match result {
39860            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
39861            Err(e) => eprintln!("STRUCT colon error: {}", e),
39862        }
39863    }
39864
39865    #[test]
39866    fn test_generate_date_array_cte_wrapped_mysql() {
39867        let dialect = Dialect::get(DialectType::Generic);
39868        let result = dialect.transpile(
39869            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
39870            DialectType::MySQL,
39871        ).unwrap();
39872        eprintln!("GDA CTE -> MySQL: {}", result[0]);
39873    }
39874
39875    #[test]
39876    fn test_generate_date_array_cte_wrapped_tsql() {
39877        let dialect = Dialect::get(DialectType::Generic);
39878        let result = dialect.transpile(
39879            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
39880            DialectType::TSQL,
39881        ).unwrap();
39882        eprintln!("GDA CTE -> TSQL: {}", result[0]);
39883    }
39884
39885    #[test]
39886    fn test_decode_literal_no_null_check() {
39887        // Oracle DECODE with all literals should produce simple equality, no IS NULL
39888        let dialect = Dialect::get(DialectType::Oracle);
39889        let result = dialect
39890            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
39891            .unwrap();
39892        assert_eq!(
39893            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
39894            "Literal DECODE should not have IS NULL checks"
39895        );
39896    }
39897
39898    #[test]
39899    fn test_decode_column_vs_literal_no_null_check() {
39900        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
39901        let dialect = Dialect::get(DialectType::Oracle);
39902        let result = dialect
39903            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
39904            .unwrap();
39905        assert_eq!(
39906            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
39907            "Column vs literal DECODE should not have IS NULL checks"
39908        );
39909    }
39910
39911    #[test]
39912    fn test_decode_column_vs_column_keeps_null_check() {
39913        // Oracle DECODE with column vs column should keep null-safe comparison
39914        let dialect = Dialect::get(DialectType::Oracle);
39915        let result = dialect
39916            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
39917            .unwrap();
39918        assert!(
39919            result[0].contains("IS NULL"),
39920            "Column vs column DECODE should have IS NULL checks, got: {}",
39921            result[0]
39922        );
39923    }
39924
39925    #[test]
39926    fn test_decode_null_search() {
39927        // Oracle DECODE with NULL search should use IS NULL
39928        let dialect = Dialect::get(DialectType::Oracle);
39929        let result = dialect
39930            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
39931            .unwrap();
39932        assert_eq!(
39933            result[0],
39934            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
39935        );
39936    }
39937
39938    // =========================================================================
39939    // REGEXP function transpilation tests
39940    // =========================================================================
39941
39942    #[test]
39943    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
39944        let dialect = Dialect::get(DialectType::Snowflake);
39945        let result = dialect
39946            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
39947            .unwrap();
39948        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
39949    }
39950
39951    #[test]
39952    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
39953        let dialect = Dialect::get(DialectType::Snowflake);
39954        let result = dialect
39955            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
39956            .unwrap();
39957        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
39958    }
39959
39960    #[test]
39961    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
39962        let dialect = Dialect::get(DialectType::Snowflake);
39963        let result = dialect
39964            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
39965            .unwrap();
39966        assert_eq!(
39967            result[0],
39968            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
39969        );
39970    }
39971
39972    #[test]
39973    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
39974        let dialect = Dialect::get(DialectType::Snowflake);
39975        let result = dialect
39976            .transpile(
39977                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
39978                DialectType::DuckDB,
39979            )
39980            .unwrap();
39981        assert_eq!(
39982            result[0],
39983            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
39984        );
39985    }
39986
39987    #[test]
39988    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
39989        let dialect = Dialect::get(DialectType::Snowflake);
39990        let result = dialect
39991            .transpile(
39992                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
39993                DialectType::DuckDB,
39994            )
39995            .unwrap();
39996        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
39997    }
39998
39999    #[test]
40000    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
40001        let dialect = Dialect::get(DialectType::Snowflake);
40002        let result = dialect
40003            .transpile(
40004                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
40005                DialectType::DuckDB,
40006            )
40007            .unwrap();
40008        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
40009    }
40010
40011    #[test]
40012    fn test_regexp_substr_snowflake_identity_strip_group0() {
40013        let dialect = Dialect::get(DialectType::Snowflake);
40014        let result = dialect
40015            .transpile(
40016                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
40017                DialectType::Snowflake,
40018            )
40019            .unwrap();
40020        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
40021    }
40022
40023    #[test]
40024    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
40025        let dialect = Dialect::get(DialectType::Snowflake);
40026        let result = dialect
40027            .transpile(
40028                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
40029                DialectType::DuckDB,
40030            )
40031            .unwrap();
40032        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
40033    }
40034
40035    #[test]
40036    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
40037        let dialect = Dialect::get(DialectType::Snowflake);
40038        let result = dialect
40039            .transpile(
40040                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
40041                DialectType::DuckDB,
40042            )
40043            .unwrap();
40044        assert_eq!(
40045            result[0],
40046            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
40047        );
40048    }
40049
40050    #[test]
40051    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
40052        let dialect = Dialect::get(DialectType::Snowflake);
40053        let result = dialect
40054            .transpile(
40055                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
40056                DialectType::DuckDB,
40057            )
40058            .unwrap();
40059        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
40060    }
40061
40062    #[test]
40063    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
40064        let dialect = Dialect::get(DialectType::Snowflake);
40065        let result = dialect
40066            .transpile(
40067                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
40068                DialectType::DuckDB,
40069            )
40070            .unwrap();
40071        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
40072    }
40073
40074    #[test]
40075    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
40076        let dialect = Dialect::get(DialectType::Snowflake);
40077        let result = dialect
40078            .transpile(
40079                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
40080                DialectType::Snowflake,
40081            )
40082            .unwrap();
40083        assert_eq!(
40084            result[0],
40085            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
40086        );
40087    }
40088
40089    #[test]
40090    fn test_regexp_count_snowflake_to_duckdb_2arg() {
40091        let dialect = Dialect::get(DialectType::Snowflake);
40092        let result = dialect
40093            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
40094            .unwrap();
40095        assert_eq!(
40096            result[0],
40097            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
40098        );
40099    }
40100
40101    #[test]
40102    fn test_regexp_count_snowflake_to_duckdb_3arg() {
40103        let dialect = Dialect::get(DialectType::Snowflake);
40104        let result = dialect
40105            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
40106            .unwrap();
40107        assert_eq!(
40108            result[0],
40109            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
40110        );
40111    }
40112
40113    #[test]
40114    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
40115        let dialect = Dialect::get(DialectType::Snowflake);
40116        let result = dialect
40117            .transpile(
40118                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
40119                DialectType::DuckDB,
40120            )
40121            .unwrap();
40122        assert_eq!(
40123            result[0],
40124            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
40125        );
40126    }
40127
40128    #[test]
40129    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
40130        let dialect = Dialect::get(DialectType::Snowflake);
40131        let result = dialect
40132            .transpile(
40133                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
40134                DialectType::DuckDB,
40135            )
40136            .unwrap();
40137        assert_eq!(
40138            result[0],
40139            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
40140        );
40141    }
40142
40143    #[test]
40144    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
40145        let dialect = Dialect::get(DialectType::Snowflake);
40146        let result = dialect
40147            .transpile(
40148                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
40149                DialectType::DuckDB,
40150            )
40151            .unwrap();
40152        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
40153    }
40154
40155    #[test]
40156    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
40157        let dialect = Dialect::get(DialectType::Snowflake);
40158        let result = dialect
40159            .transpile(
40160                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
40161                DialectType::DuckDB,
40162            )
40163            .unwrap();
40164        assert_eq!(
40165            result[0],
40166            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
40167        );
40168    }
40169
40170    #[test]
40171    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
40172        let dialect = Dialect::get(DialectType::Snowflake);
40173        let result = dialect
40174            .transpile(
40175                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
40176                DialectType::DuckDB,
40177            )
40178            .unwrap();
40179        assert_eq!(
40180            result[0],
40181            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
40182        );
40183    }
40184
40185    #[test]
40186    fn test_rlike_snowflake_to_duckdb_2arg() {
40187        let dialect = Dialect::get(DialectType::Snowflake);
40188        let result = dialect
40189            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
40190            .unwrap();
40191        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
40192    }
40193
40194    #[test]
40195    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
40196        let dialect = Dialect::get(DialectType::Snowflake);
40197        let result = dialect
40198            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
40199            .unwrap();
40200        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
40201    }
40202
40203    #[test]
40204    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
40205        let dialect = Dialect::get(DialectType::BigQuery);
40206        let result = dialect
40207            .transpile(
40208                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
40209                DialectType::Snowflake,
40210            )
40211            .unwrap();
40212        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
40213    }
40214
40215    #[test]
40216    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
40217        let dialect = Dialect::get(DialectType::BigQuery);
40218        let result = dialect
40219            .transpile(
40220                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
40221                DialectType::Snowflake,
40222            )
40223            .unwrap();
40224        assert_eq!(
40225            result[0],
40226            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
40227        );
40228    }
40229
40230    #[test]
40231    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
40232        let dialect = Dialect::get(DialectType::Snowflake);
40233        let result = dialect
40234            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
40235            .unwrap();
40236        assert!(
40237            result[0].contains("CASE WHEN"),
40238            "Expected CASE WHEN in result: {}",
40239            result[0]
40240        );
40241        assert!(
40242            result[0].contains("LIST_SUM"),
40243            "Expected LIST_SUM in result: {}",
40244            result[0]
40245        );
40246    }
40247
40248    #[test]
40249    fn test_array_except_generic_to_duckdb() {
40250        let dialect = Dialect::get(DialectType::Generic);
40251        let result = dialect
40252            .transpile(
40253                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
40254                DialectType::DuckDB,
40255            )
40256            .unwrap();
40257        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
40258        assert!(
40259            result[0].contains("CASE WHEN"),
40260            "Expected CASE WHEN: {}",
40261            result[0]
40262        );
40263        assert!(
40264            result[0].contains("LIST_FILTER"),
40265            "Expected LIST_FILTER: {}",
40266            result[0]
40267        );
40268        assert!(
40269            result[0].contains("LIST_DISTINCT"),
40270            "Expected LIST_DISTINCT: {}",
40271            result[0]
40272        );
40273        assert!(
40274            result[0].contains("IS NOT DISTINCT FROM"),
40275            "Expected IS NOT DISTINCT FROM: {}",
40276            result[0]
40277        );
40278        assert!(
40279            result[0].contains("= 0"),
40280            "Expected = 0 filter: {}",
40281            result[0]
40282        );
40283    }
40284
40285    #[test]
40286    fn test_array_except_generic_to_snowflake() {
40287        let dialect = Dialect::get(DialectType::Generic);
40288        let result = dialect
40289            .transpile(
40290                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
40291                DialectType::Snowflake,
40292            )
40293            .unwrap();
40294        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
40295        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
40296    }
40297
40298    #[test]
40299    fn test_array_except_generic_to_presto() {
40300        let dialect = Dialect::get(DialectType::Generic);
40301        let result = dialect
40302            .transpile(
40303                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
40304                DialectType::Presto,
40305            )
40306            .unwrap();
40307        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
40308        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
40309    }
40310
40311    #[test]
40312    fn test_array_except_snowflake_to_duckdb() {
40313        let dialect = Dialect::get(DialectType::Snowflake);
40314        let result = dialect
40315            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
40316            .unwrap();
40317        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
40318        assert!(
40319            result[0].contains("CASE WHEN"),
40320            "Expected CASE WHEN: {}",
40321            result[0]
40322        );
40323        assert!(
40324            result[0].contains("LIST_TRANSFORM"),
40325            "Expected LIST_TRANSFORM: {}",
40326            result[0]
40327        );
40328    }
40329
40330    #[test]
40331    fn test_array_contains_snowflake_to_snowflake() {
40332        let dialect = Dialect::get(DialectType::Snowflake);
40333        let result = dialect
40334            .transpile(
40335                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
40336                DialectType::Snowflake,
40337            )
40338            .unwrap();
40339        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
40340        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
40341    }
40342
40343    #[test]
40344    fn test_array_contains_snowflake_to_duckdb() {
40345        let dialect = Dialect::get(DialectType::Snowflake);
40346        let result = dialect
40347            .transpile(
40348                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
40349                DialectType::DuckDB,
40350            )
40351            .unwrap();
40352        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
40353        assert!(
40354            result[0].contains("CASE WHEN"),
40355            "Expected CASE WHEN: {}",
40356            result[0]
40357        );
40358        assert!(
40359            result[0].contains("NULLIF"),
40360            "Expected NULLIF: {}",
40361            result[0]
40362        );
40363        assert!(
40364            result[0].contains("ARRAY_CONTAINS"),
40365            "Expected ARRAY_CONTAINS: {}",
40366            result[0]
40367        );
40368    }
40369
40370    #[test]
40371    fn test_array_distinct_snowflake_to_duckdb() {
40372        let dialect = Dialect::get(DialectType::Snowflake);
40373        let result = dialect
40374            .transpile(
40375                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
40376                DialectType::DuckDB,
40377            )
40378            .unwrap();
40379        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
40380        assert!(
40381            result[0].contains("CASE WHEN"),
40382            "Expected CASE WHEN: {}",
40383            result[0]
40384        );
40385        assert!(
40386            result[0].contains("LIST_DISTINCT"),
40387            "Expected LIST_DISTINCT: {}",
40388            result[0]
40389        );
40390        assert!(
40391            result[0].contains("LIST_APPEND"),
40392            "Expected LIST_APPEND: {}",
40393            result[0]
40394        );
40395        assert!(
40396            result[0].contains("LIST_FILTER"),
40397            "Expected LIST_FILTER: {}",
40398            result[0]
40399        );
40400    }
40401}