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::{
163    BinaryOp, Case, Cast, ColumnConstraint, DateBin, Fetch, Function, Identifier, Interval,
164    IntervalUnit, IntervalUnitSpec, LikeOp, Literal, Top, Var,
165};
166use crate::expressions::{DataType, Expression};
167#[cfg(any(
168    feature = "transpile",
169    feature = "ast-tools",
170    feature = "generate",
171    feature = "semantic"
172))]
173use crate::expressions::{From, FunctionBody, Join, Null, OrderBy, OutputClause, TableRef, With};
174#[cfg(feature = "transpile")]
175use crate::generator::UnsupportedLevel;
176#[cfg(feature = "generate")]
177use crate::generator::{Generator, GeneratorConfig};
178#[cfg(feature = "transpile")]
179use crate::guard::enforce_generate_ast;
180use crate::guard::{enforce_input, ComplexityGuardOptions};
181use crate::parser::Parser;
182#[cfg(feature = "transpile")]
183use crate::tokens::TokenType;
184use crate::tokens::{Token, Tokenizer, TokenizerConfig};
185#[cfg(feature = "transpile")]
186use crate::traversal::ExpressionWalk;
187use serde::{Deserialize, Serialize};
188use std::collections::HashMap;
189use std::sync::{Arc, LazyLock, RwLock};
190
191/// Enumeration of all supported SQL dialects.
192///
193/// Each variant corresponds to a specific SQL database engine or query language.
194/// The `Generic` variant represents standard SQL with no dialect-specific behavior,
195/// and is used as the default when no dialect is specified.
196///
197/// Dialect names are case-insensitive when parsed from strings via [`FromStr`].
198/// Some dialects accept aliases (e.g., "mssql" and "sqlserver" both resolve to [`TSQL`](DialectType::TSQL)).
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
200#[serde(rename_all = "lowercase")]
201pub enum DialectType {
202    /// Standard SQL with no dialect-specific behavior (default).
203    Generic,
204    /// PostgreSQL -- advanced open-source relational database.
205    PostgreSQL,
206    /// MySQL -- widely-used open-source relational database (also accepts "mysql").
207    MySQL,
208    /// Google BigQuery -- serverless cloud data warehouse with unique syntax (backtick quoting, STRUCT types, QUALIFY).
209    BigQuery,
210    /// Snowflake -- cloud data platform with QUALIFY clause, FLATTEN, and variant types.
211    Snowflake,
212    /// DuckDB -- in-process analytical database with modern SQL extensions.
213    DuckDB,
214    /// SQLite -- lightweight embedded relational database.
215    SQLite,
216    /// Apache Hive -- data warehouse on Hadoop with HiveQL syntax.
217    Hive,
218    /// Apache Spark SQL -- distributed query engine (also accepts "spark2").
219    Spark,
220    /// Trino -- distributed SQL query engine (formerly PrestoSQL).
221    Trino,
222    /// PrestoDB -- distributed SQL query engine for big data.
223    Presto,
224    /// Amazon Redshift -- cloud data warehouse based on PostgreSQL.
225    Redshift,
226    /// Transact-SQL (T-SQL) -- Microsoft SQL Server and Azure SQL (also accepts "mssql", "sqlserver").
227    TSQL,
228    /// Oracle Database -- commercial relational database with PL/SQL extensions.
229    Oracle,
230    /// ClickHouse -- column-oriented OLAP database for real-time analytics.
231    ClickHouse,
232    /// Databricks SQL -- Spark-based lakehouse platform with QUALIFY support.
233    Databricks,
234    /// Amazon Athena -- serverless query service (hybrid Trino/Hive engine).
235    Athena,
236    /// Teradata -- enterprise data warehouse with proprietary SQL extensions.
237    Teradata,
238    /// Apache Doris -- real-time analytical database (MySQL-compatible).
239    Doris,
240    /// StarRocks -- sub-second OLAP database (MySQL-compatible).
241    StarRocks,
242    /// Materialize -- streaming SQL database built on differential dataflow.
243    Materialize,
244    /// RisingWave -- distributed streaming database with PostgreSQL compatibility.
245    RisingWave,
246    /// SingleStore (formerly MemSQL) -- distributed SQL database (also accepts "memsql").
247    SingleStore,
248    /// CockroachDB -- distributed SQL database with PostgreSQL compatibility (also accepts "cockroach").
249    CockroachDB,
250    /// TiDB -- distributed HTAP database with MySQL compatibility.
251    TiDB,
252    /// Apache Druid -- real-time analytics database.
253    Druid,
254    /// Apache Solr -- search platform with SQL interface.
255    Solr,
256    /// Tableau -- data visualization platform with its own SQL dialect.
257    Tableau,
258    /// Dune Analytics -- blockchain analytics SQL engine.
259    Dune,
260    /// Microsoft Fabric -- unified analytics platform (T-SQL based).
261    Fabric,
262    /// Apache Drill -- schema-free SQL query engine for big data.
263    Drill,
264    /// Dremio -- data lakehouse platform with Arrow-based query engine.
265    Dremio,
266    /// Exasol -- in-memory analytic database.
267    Exasol,
268    /// Apache DataFusion -- Arrow-based query engine with modern SQL extensions.
269    DataFusion,
270}
271
272impl Default for DialectType {
273    fn default() -> Self {
274        DialectType::Generic
275    }
276}
277
278impl std::fmt::Display for DialectType {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        match self {
281            DialectType::Generic => write!(f, "generic"),
282            DialectType::PostgreSQL => write!(f, "postgresql"),
283            DialectType::MySQL => write!(f, "mysql"),
284            DialectType::BigQuery => write!(f, "bigquery"),
285            DialectType::Snowflake => write!(f, "snowflake"),
286            DialectType::DuckDB => write!(f, "duckdb"),
287            DialectType::SQLite => write!(f, "sqlite"),
288            DialectType::Hive => write!(f, "hive"),
289            DialectType::Spark => write!(f, "spark"),
290            DialectType::Trino => write!(f, "trino"),
291            DialectType::Presto => write!(f, "presto"),
292            DialectType::Redshift => write!(f, "redshift"),
293            DialectType::TSQL => write!(f, "tsql"),
294            DialectType::Oracle => write!(f, "oracle"),
295            DialectType::ClickHouse => write!(f, "clickhouse"),
296            DialectType::Databricks => write!(f, "databricks"),
297            DialectType::Athena => write!(f, "athena"),
298            DialectType::Teradata => write!(f, "teradata"),
299            DialectType::Doris => write!(f, "doris"),
300            DialectType::StarRocks => write!(f, "starrocks"),
301            DialectType::Materialize => write!(f, "materialize"),
302            DialectType::RisingWave => write!(f, "risingwave"),
303            DialectType::SingleStore => write!(f, "singlestore"),
304            DialectType::CockroachDB => write!(f, "cockroachdb"),
305            DialectType::TiDB => write!(f, "tidb"),
306            DialectType::Druid => write!(f, "druid"),
307            DialectType::Solr => write!(f, "solr"),
308            DialectType::Tableau => write!(f, "tableau"),
309            DialectType::Dune => write!(f, "dune"),
310            DialectType::Fabric => write!(f, "fabric"),
311            DialectType::Drill => write!(f, "drill"),
312            DialectType::Dremio => write!(f, "dremio"),
313            DialectType::Exasol => write!(f, "exasol"),
314            DialectType::DataFusion => write!(f, "datafusion"),
315        }
316    }
317}
318
319impl std::str::FromStr for DialectType {
320    type Err = crate::error::Error;
321
322    fn from_str(s: &str) -> Result<Self> {
323        match s.to_ascii_lowercase().as_str() {
324            "generic" | "" => Ok(DialectType::Generic),
325            "postgres" | "postgresql" => Ok(DialectType::PostgreSQL),
326            "mysql" => Ok(DialectType::MySQL),
327            "bigquery" => Ok(DialectType::BigQuery),
328            "snowflake" => Ok(DialectType::Snowflake),
329            "duckdb" => Ok(DialectType::DuckDB),
330            "sqlite" => Ok(DialectType::SQLite),
331            "hive" => Ok(DialectType::Hive),
332            "spark" | "spark2" => Ok(DialectType::Spark),
333            "trino" => Ok(DialectType::Trino),
334            "presto" => Ok(DialectType::Presto),
335            "redshift" => Ok(DialectType::Redshift),
336            "tsql" | "mssql" | "sqlserver" => Ok(DialectType::TSQL),
337            "oracle" => Ok(DialectType::Oracle),
338            "clickhouse" => Ok(DialectType::ClickHouse),
339            "databricks" => Ok(DialectType::Databricks),
340            "athena" => Ok(DialectType::Athena),
341            "teradata" => Ok(DialectType::Teradata),
342            "doris" => Ok(DialectType::Doris),
343            "starrocks" => Ok(DialectType::StarRocks),
344            "materialize" => Ok(DialectType::Materialize),
345            "risingwave" => Ok(DialectType::RisingWave),
346            "singlestore" | "memsql" => Ok(DialectType::SingleStore),
347            "cockroachdb" | "cockroach" => Ok(DialectType::CockroachDB),
348            "tidb" => Ok(DialectType::TiDB),
349            "druid" => Ok(DialectType::Druid),
350            "solr" => Ok(DialectType::Solr),
351            "tableau" => Ok(DialectType::Tableau),
352            "dune" => Ok(DialectType::Dune),
353            "fabric" => Ok(DialectType::Fabric),
354            "drill" => Ok(DialectType::Drill),
355            "dremio" => Ok(DialectType::Dremio),
356            "exasol" => Ok(DialectType::Exasol),
357            "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Ok(DialectType::DataFusion),
358            _ => Err(crate::error::Error::parse(
359                format!("Unknown dialect: {}", s),
360                0,
361                0,
362                0,
363                0,
364            )),
365        }
366    }
367}
368
369/// Trait that each concrete SQL dialect must implement.
370///
371/// `DialectImpl` provides the configuration hooks and per-expression transform logic
372/// that distinguish one dialect from another. Implementors supply:
373///
374/// - A [`DialectType`] identifier.
375/// - Optional overrides for tokenizer and generator configuration (defaults to generic SQL).
376/// - An expression-level transform function ([`transform_expr`](DialectImpl::transform_expr))
377///   that rewrites individual AST nodes for this dialect (e.g., converting `NVL` to `COALESCE`).
378/// - An optional preprocessing step ([`preprocess`](DialectImpl::preprocess)) for whole-tree
379///   rewrites that must run before the recursive per-node transform (e.g., eliminating QUALIFY).
380///
381/// The default implementations are no-ops, so a minimal dialect only needs to provide
382/// [`dialect_type`](DialectImpl::dialect_type) and override the methods that differ from
383/// standard SQL.
384pub trait DialectImpl {
385    /// Returns the [`DialectType`] that identifies this dialect.
386    fn dialect_type(&self) -> DialectType;
387
388    /// Returns the tokenizer configuration for this dialect.
389    ///
390    /// Override to customize identifier quoting characters, string escape rules,
391    /// comment styles, and other lexing behavior.
392    fn tokenizer_config(&self) -> TokenizerConfig {
393        TokenizerConfig::default()
394    }
395
396    /// Returns the generator configuration for this dialect.
397    ///
398    /// Override to customize identifier quoting style, function name casing,
399    /// keyword casing, and other SQL generation behavior.
400    #[cfg(feature = "generate")]
401    fn generator_config(&self) -> GeneratorConfig {
402        GeneratorConfig::default()
403    }
404
405    /// Returns a generator configuration tailored to a specific expression.
406    ///
407    /// Override this for hybrid dialects like Athena that route to different SQL engines
408    /// based on expression type (e.g., Hive-style generation for DDL, Trino-style for DML).
409    /// The default delegates to [`generator_config`](DialectImpl::generator_config).
410    #[cfg(feature = "generate")]
411    fn generator_config_for_expr(&self, _expr: &Expression) -> GeneratorConfig {
412        self.generator_config()
413    }
414
415    /// Transforms a single expression node for this dialect, without recursing into children.
416    ///
417    /// This is the per-node rewrite hook invoked by [`transform_recursive`]. Return the
418    /// expression unchanged if no dialect-specific rewrite is needed. Transformations
419    /// typically include function renaming, operator substitution, and type mapping.
420    #[cfg(feature = "transpile")]
421    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
422        Ok(expr)
423    }
424
425    /// Applies whole-tree preprocessing transforms before the recursive per-node pass.
426    ///
427    /// Override this to apply structural rewrites that must see the entire tree at once,
428    /// such as `eliminate_qualify`, `eliminate_distinct_on`, `ensure_bools`, or
429    /// `explode_projection_to_unnest`. The default is a no-op pass-through.
430    #[cfg(feature = "transpile")]
431    fn preprocess(&self, expr: Expression) -> Result<Expression> {
432        Ok(expr)
433    }
434}
435
436/// Recursively transforms a [`DataType`](crate::expressions::DataType), handling nested
437/// parametric types such as `ARRAY<INT>`, `STRUCT<a INT, b TEXT>`, and `MAP<STRING, INT>`.
438///
439/// The outer type is first passed through `transform_fn` as an `Expression::DataType`,
440/// and then nested element/field types are recursed into. This ensures that dialect-level
441/// type mappings (e.g., `INT` to `INTEGER`) propagate into complex nested types.
442#[cfg(any(
443    feature = "transpile",
444    feature = "ast-tools",
445    feature = "generate",
446    feature = "semantic"
447))]
448fn transform_data_type_recursive<F>(
449    dt: crate::expressions::DataType,
450    transform_fn: &F,
451) -> Result<crate::expressions::DataType>
452where
453    F: Fn(Expression) -> Result<Expression>,
454{
455    use crate::expressions::DataType;
456    // First, transform the outermost type through the expression system
457    let dt_expr = transform_fn(Expression::DataType(dt))?;
458    let dt = match dt_expr {
459        Expression::DataType(d) => d,
460        _ => {
461            return Ok(match dt_expr {
462                _ => DataType::Custom {
463                    name: "UNKNOWN".to_string(),
464                },
465            })
466        }
467    };
468    // Then recurse into nested types
469    match dt {
470        DataType::Array {
471            element_type,
472            dimension,
473        } => {
474            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
475            Ok(DataType::Array {
476                element_type: Box::new(inner),
477                dimension,
478            })
479        }
480        DataType::List { element_type } => {
481            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
482            Ok(DataType::List {
483                element_type: Box::new(inner),
484            })
485        }
486        DataType::Struct { fields, nested } => {
487            let mut new_fields = Vec::new();
488            for mut field in fields {
489                field.data_type = transform_data_type_recursive(field.data_type, transform_fn)?;
490                new_fields.push(field);
491            }
492            Ok(DataType::Struct {
493                fields: new_fields,
494                nested,
495            })
496        }
497        DataType::Map {
498            key_type,
499            value_type,
500        } => {
501            let k = transform_data_type_recursive(*key_type, transform_fn)?;
502            let v = transform_data_type_recursive(*value_type, transform_fn)?;
503            Ok(DataType::Map {
504                key_type: Box::new(k),
505                value_type: Box::new(v),
506            })
507        }
508        other => Ok(other),
509    }
510}
511
512/// Convert DuckDB C-style format strings to Presto C-style format strings.
513/// DuckDB and Presto both use C-style % directives but with different specifiers for some cases.
514#[cfg(feature = "transpile")]
515fn duckdb_to_presto_format(fmt: &str) -> String {
516    // Order matters: handle longer patterns first to avoid partial replacements
517    let mut result = fmt.to_string();
518    // First pass: mark multi-char patterns with placeholders
519    result = result.replace("%-m", "\x01NOPADM\x01");
520    result = result.replace("%-d", "\x01NOPADD\x01");
521    result = result.replace("%-I", "\x01NOPADI\x01");
522    result = result.replace("%-H", "\x01NOPADH\x01");
523    result = result.replace("%H:%M:%S", "\x01HMS\x01");
524    result = result.replace("%Y-%m-%d", "\x01YMD\x01");
525    // Now convert individual specifiers
526    result = result.replace("%M", "%i");
527    result = result.replace("%S", "%s");
528    // Restore multi-char patterns with Presto equivalents
529    result = result.replace("\x01NOPADM\x01", "%c");
530    result = result.replace("\x01NOPADD\x01", "%e");
531    result = result.replace("\x01NOPADI\x01", "%l");
532    result = result.replace("\x01NOPADH\x01", "%k");
533    result = result.replace("\x01HMS\x01", "%T");
534    result = result.replace("\x01YMD\x01", "%Y-%m-%d");
535    result
536}
537
538/// Convert DuckDB C-style format strings to BigQuery format strings.
539/// BigQuery uses a mix of strftime-like directives.
540#[cfg(feature = "transpile")]
541fn duckdb_to_bigquery_format(fmt: &str) -> String {
542    let mut result = fmt.to_string();
543    // Handle longer patterns first
544    result = result.replace("%-d", "%e");
545    result = result.replace("%Y-%m-%d %H:%M:%S", "%F %T");
546    result = result.replace("%Y-%m-%d", "%F");
547    result = result.replace("%H:%M:%S", "%T");
548    result
549}
550
551#[cfg(feature = "transpile")]
552fn presto_to_java_format(fmt: &str) -> String {
553    fmt.replace("%Y", "yyyy")
554        .replace("%m", "MM")
555        .replace("%d", "dd")
556        .replace("%H", "HH")
557        .replace("%i", "mm")
558        .replace("%S", "ss")
559        .replace("%s", "ss")
560        .replace("%y", "yy")
561        .replace("%T", "HH:mm:ss")
562        .replace("%F", "yyyy-MM-dd")
563        .replace("%M", "MMMM")
564}
565
566#[cfg(feature = "transpile")]
567fn normalize_presto_format(fmt: &str) -> String {
568    fmt.replace("%H:%i:%S", "%T").replace("%H:%i:%s", "%T")
569}
570
571#[cfg(feature = "transpile")]
572fn presto_to_duckdb_format(fmt: &str) -> String {
573    fmt.replace("%i", "%M")
574        .replace("%s", "%S")
575        .replace("%T", "%H:%M:%S")
576}
577
578#[cfg(feature = "transpile")]
579fn presto_to_bigquery_format(fmt: &str) -> String {
580    fmt.replace("%Y-%m-%d", "%F")
581        .replace("%H:%i:%S", "%T")
582        .replace("%H:%i:%s", "%T")
583        .replace("%i", "%M")
584        .replace("%s", "%S")
585}
586
587#[cfg(feature = "transpile")]
588fn is_default_presto_timestamp_format(fmt: &str) -> bool {
589    let normalized = normalize_presto_format(fmt);
590    normalized == "%Y-%m-%d %T"
591        || normalized == "%Y-%m-%d %H:%i:%S"
592        || fmt == "%Y-%m-%d %H:%i:%S"
593        || fmt == "%Y-%m-%d %T"
594}
595
596#[cfg(feature = "transpile")]
597fn is_default_presto_date_format(fmt: &str) -> bool {
598    fmt == "%Y-%m-%d" || fmt == "%F"
599}
600
601#[cfg(any(
602    feature = "transpile",
603    feature = "ast-tools",
604    feature = "generate",
605    feature = "semantic"
606))]
607#[derive(Debug)]
608enum TransformTask {
609    Visit(Expression),
610    Finish(FinishTask),
611}
612
613#[cfg(any(
614    feature = "transpile",
615    feature = "ast-tools",
616    feature = "generate",
617    feature = "semantic"
618))]
619#[derive(Debug)]
620enum FinishTask {
621    Unary(Expression),
622    Binary(Expression),
623    CastLike(Expression),
624    List(Expression, usize),
625    From(crate::expressions::From, usize),
626    Select(SelectFrame),
627    SetOp(Expression),
628}
629
630#[cfg(any(
631    feature = "transpile",
632    feature = "ast-tools",
633    feature = "generate",
634    feature = "semantic"
635))]
636#[derive(Debug)]
637struct SelectFrame {
638    select: Box<crate::expressions::Select>,
639    expr_count: usize,
640    from_present: bool,
641    where_present: bool,
642    group_by_count: usize,
643    having_present: bool,
644    qualify_present: bool,
645}
646
647#[cfg(any(
648    feature = "transpile",
649    feature = "ast-tools",
650    feature = "generate",
651    feature = "semantic"
652))]
653fn transform_pop_result(results: &mut Vec<Expression>) -> Result<Expression> {
654    results
655        .pop()
656        .ok_or_else(|| crate::error::Error::Internal("transform stack underflow".to_string()))
657}
658
659#[cfg(any(
660    feature = "transpile",
661    feature = "ast-tools",
662    feature = "generate",
663    feature = "semantic"
664))]
665fn transform_pop_results(results: &mut Vec<Expression>, count: usize) -> Result<Vec<Expression>> {
666    if results.len() < count {
667        return Err(crate::error::Error::Internal(
668            "transform result stack underflow".to_string(),
669        ));
670    }
671    Ok(results.split_off(results.len() - count))
672}
673
674/// Applies a transform function bottom-up through an entire expression tree.
675///
676/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
677/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
678/// trees, and common binary/unary expression chains). Less common shapes currently
679/// reuse the reference recursive implementation so semantics stay identical while
680/// the hot path avoids stack growth.
681#[cfg(any(
682    feature = "transpile",
683    feature = "ast-tools",
684    feature = "generate",
685    feature = "semantic"
686))]
687pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
688where
689    F: Fn(Expression) -> Result<Expression>,
690{
691    #[cfg(feature = "stacker")]
692    {
693        let red_zone = if cfg!(debug_assertions) {
694            4 * 1024 * 1024
695        } else {
696            1024 * 1024
697        };
698        stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
699            transform_recursive_inner(expr, transform_fn)
700        })
701    }
702    #[cfg(not(feature = "stacker"))]
703    {
704        transform_recursive_inner(expr, transform_fn)
705    }
706}
707
708#[cfg(any(
709    feature = "transpile",
710    feature = "ast-tools",
711    feature = "generate",
712    feature = "semantic"
713))]
714fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
715where
716    F: Fn(Expression) -> Result<Expression>,
717{
718    let mut tasks = vec![TransformTask::Visit(expr)];
719    let mut results = Vec::new();
720
721    while let Some(task) = tasks.pop() {
722        match task {
723            TransformTask::Visit(expr) => {
724                if matches!(
725                    &expr,
726                    Expression::Literal(_)
727                        | Expression::Boolean(_)
728                        | Expression::Null(_)
729                        | Expression::Identifier(_)
730                        | Expression::Star(_)
731                        | Expression::Parameter(_)
732                        | Expression::Placeholder(_)
733                        | Expression::SessionParameter(_)
734                ) {
735                    results.push(transform_fn(expr)?);
736                    continue;
737                }
738
739                match expr {
740                    Expression::Alias(mut alias) => {
741                        let child = std::mem::replace(&mut alias.this, Expression::Null(Null));
742                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Alias(
743                            alias,
744                        ))));
745                        tasks.push(TransformTask::Visit(child));
746                    }
747                    Expression::Paren(mut paren) => {
748                        let child = std::mem::replace(&mut paren.this, Expression::Null(Null));
749                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Paren(
750                            paren,
751                        ))));
752                        tasks.push(TransformTask::Visit(child));
753                    }
754                    Expression::Not(mut not) => {
755                        let child = std::mem::replace(&mut not.this, Expression::Null(Null));
756                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Not(
757                            not,
758                        ))));
759                        tasks.push(TransformTask::Visit(child));
760                    }
761                    Expression::Neg(mut neg) => {
762                        let child = std::mem::replace(&mut neg.this, Expression::Null(Null));
763                        tasks.push(TransformTask::Finish(FinishTask::Unary(Expression::Neg(
764                            neg,
765                        ))));
766                        tasks.push(TransformTask::Visit(child));
767                    }
768                    Expression::IsNull(mut expr) => {
769                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
770                        tasks.push(TransformTask::Finish(FinishTask::Unary(
771                            Expression::IsNull(expr),
772                        )));
773                        tasks.push(TransformTask::Visit(child));
774                    }
775                    Expression::IsTrue(mut expr) => {
776                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
777                        tasks.push(TransformTask::Finish(FinishTask::Unary(
778                            Expression::IsTrue(expr),
779                        )));
780                        tasks.push(TransformTask::Visit(child));
781                    }
782                    Expression::IsFalse(mut expr) => {
783                        let child = std::mem::replace(&mut expr.this, Expression::Null(Null));
784                        tasks.push(TransformTask::Finish(FinishTask::Unary(
785                            Expression::IsFalse(expr),
786                        )));
787                        tasks.push(TransformTask::Visit(child));
788                    }
789                    Expression::Subquery(mut subquery) => {
790                        let child = std::mem::replace(&mut subquery.this, Expression::Null(Null));
791                        tasks.push(TransformTask::Finish(FinishTask::Unary(
792                            Expression::Subquery(subquery),
793                        )));
794                        tasks.push(TransformTask::Visit(child));
795                    }
796                    Expression::Exists(mut exists) => {
797                        let child = std::mem::replace(&mut exists.this, Expression::Null(Null));
798                        tasks.push(TransformTask::Finish(FinishTask::Unary(
799                            Expression::Exists(exists),
800                        )));
801                        tasks.push(TransformTask::Visit(child));
802                    }
803                    Expression::TableArgument(mut arg) => {
804                        let child = std::mem::replace(&mut arg.this, Expression::Null(Null));
805                        tasks.push(TransformTask::Finish(FinishTask::Unary(
806                            Expression::TableArgument(arg),
807                        )));
808                        tasks.push(TransformTask::Visit(child));
809                    }
810                    Expression::And(mut op) => {
811                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
812                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
813                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::And(
814                            op,
815                        ))));
816                        tasks.push(TransformTask::Visit(right));
817                        tasks.push(TransformTask::Visit(left));
818                    }
819                    Expression::Or(mut op) => {
820                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
821                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
822                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Or(
823                            op,
824                        ))));
825                        tasks.push(TransformTask::Visit(right));
826                        tasks.push(TransformTask::Visit(left));
827                    }
828                    Expression::Add(mut op) => {
829                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
830                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
831                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Add(
832                            op,
833                        ))));
834                        tasks.push(TransformTask::Visit(right));
835                        tasks.push(TransformTask::Visit(left));
836                    }
837                    Expression::Sub(mut op) => {
838                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
839                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
840                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Sub(
841                            op,
842                        ))));
843                        tasks.push(TransformTask::Visit(right));
844                        tasks.push(TransformTask::Visit(left));
845                    }
846                    Expression::Mul(mut op) => {
847                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
848                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
849                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mul(
850                            op,
851                        ))));
852                        tasks.push(TransformTask::Visit(right));
853                        tasks.push(TransformTask::Visit(left));
854                    }
855                    Expression::Div(mut op) => {
856                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
857                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
858                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Div(
859                            op,
860                        ))));
861                        tasks.push(TransformTask::Visit(right));
862                        tasks.push(TransformTask::Visit(left));
863                    }
864                    Expression::Eq(mut op) => {
865                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
866                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
867                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Eq(
868                            op,
869                        ))));
870                        tasks.push(TransformTask::Visit(right));
871                        tasks.push(TransformTask::Visit(left));
872                    }
873                    Expression::Lt(mut op) => {
874                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
875                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
876                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lt(
877                            op,
878                        ))));
879                        tasks.push(TransformTask::Visit(right));
880                        tasks.push(TransformTask::Visit(left));
881                    }
882                    Expression::Gt(mut op) => {
883                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
884                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
885                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gt(
886                            op,
887                        ))));
888                        tasks.push(TransformTask::Visit(right));
889                        tasks.push(TransformTask::Visit(left));
890                    }
891                    Expression::Neq(mut op) => {
892                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
893                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
894                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Neq(
895                            op,
896                        ))));
897                        tasks.push(TransformTask::Visit(right));
898                        tasks.push(TransformTask::Visit(left));
899                    }
900                    Expression::Lte(mut op) => {
901                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
902                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
903                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Lte(
904                            op,
905                        ))));
906                        tasks.push(TransformTask::Visit(right));
907                        tasks.push(TransformTask::Visit(left));
908                    }
909                    Expression::Gte(mut op) => {
910                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
911                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
912                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Gte(
913                            op,
914                        ))));
915                        tasks.push(TransformTask::Visit(right));
916                        tasks.push(TransformTask::Visit(left));
917                    }
918                    Expression::Mod(mut op) => {
919                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
920                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
921                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Mod(
922                            op,
923                        ))));
924                        tasks.push(TransformTask::Visit(right));
925                        tasks.push(TransformTask::Visit(left));
926                    }
927                    Expression::Concat(mut op) => {
928                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
929                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
930                        tasks.push(TransformTask::Finish(FinishTask::Binary(
931                            Expression::Concat(op),
932                        )));
933                        tasks.push(TransformTask::Visit(right));
934                        tasks.push(TransformTask::Visit(left));
935                    }
936                    Expression::BitwiseAnd(mut op) => {
937                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
938                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
939                        tasks.push(TransformTask::Finish(FinishTask::Binary(
940                            Expression::BitwiseAnd(op),
941                        )));
942                        tasks.push(TransformTask::Visit(right));
943                        tasks.push(TransformTask::Visit(left));
944                    }
945                    Expression::BitwiseOr(mut op) => {
946                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
947                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
948                        tasks.push(TransformTask::Finish(FinishTask::Binary(
949                            Expression::BitwiseOr(op),
950                        )));
951                        tasks.push(TransformTask::Visit(right));
952                        tasks.push(TransformTask::Visit(left));
953                    }
954                    Expression::BitwiseXor(mut op) => {
955                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
956                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
957                        tasks.push(TransformTask::Finish(FinishTask::Binary(
958                            Expression::BitwiseXor(op),
959                        )));
960                        tasks.push(TransformTask::Visit(right));
961                        tasks.push(TransformTask::Visit(left));
962                    }
963                    Expression::Is(mut op) => {
964                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
965                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
966                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Is(
967                            op,
968                        ))));
969                        tasks.push(TransformTask::Visit(right));
970                        tasks.push(TransformTask::Visit(left));
971                    }
972                    Expression::MemberOf(mut op) => {
973                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
974                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
975                        tasks.push(TransformTask::Finish(FinishTask::Binary(
976                            Expression::MemberOf(op),
977                        )));
978                        tasks.push(TransformTask::Visit(right));
979                        tasks.push(TransformTask::Visit(left));
980                    }
981                    Expression::ArrayContainsAll(mut op) => {
982                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
983                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
984                        tasks.push(TransformTask::Finish(FinishTask::Binary(
985                            Expression::ArrayContainsAll(op),
986                        )));
987                        tasks.push(TransformTask::Visit(right));
988                        tasks.push(TransformTask::Visit(left));
989                    }
990                    Expression::ArrayContainedBy(mut op) => {
991                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
992                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
993                        tasks.push(TransformTask::Finish(FinishTask::Binary(
994                            Expression::ArrayContainedBy(op),
995                        )));
996                        tasks.push(TransformTask::Visit(right));
997                        tasks.push(TransformTask::Visit(left));
998                    }
999                    Expression::ArrayOverlaps(mut op) => {
1000                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1001                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1002                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1003                            Expression::ArrayOverlaps(op),
1004                        )));
1005                        tasks.push(TransformTask::Visit(right));
1006                        tasks.push(TransformTask::Visit(left));
1007                    }
1008                    Expression::TsMatch(mut op) => {
1009                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1010                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1011                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1012                            Expression::TsMatch(op),
1013                        )));
1014                        tasks.push(TransformTask::Visit(right));
1015                        tasks.push(TransformTask::Visit(left));
1016                    }
1017                    Expression::Adjacent(mut op) => {
1018                        let right = std::mem::replace(&mut op.right, Expression::Null(Null));
1019                        let left = std::mem::replace(&mut op.left, Expression::Null(Null));
1020                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1021                            Expression::Adjacent(op),
1022                        )));
1023                        tasks.push(TransformTask::Visit(right));
1024                        tasks.push(TransformTask::Visit(left));
1025                    }
1026                    Expression::Like(mut like) => {
1027                        let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1028                        let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1029                        tasks.push(TransformTask::Finish(FinishTask::Binary(Expression::Like(
1030                            like,
1031                        ))));
1032                        tasks.push(TransformTask::Visit(right));
1033                        tasks.push(TransformTask::Visit(left));
1034                    }
1035                    Expression::ILike(mut like) => {
1036                        let right = std::mem::replace(&mut like.right, Expression::Null(Null));
1037                        let left = std::mem::replace(&mut like.left, Expression::Null(Null));
1038                        tasks.push(TransformTask::Finish(FinishTask::Binary(
1039                            Expression::ILike(like),
1040                        )));
1041                        tasks.push(TransformTask::Visit(right));
1042                        tasks.push(TransformTask::Visit(left));
1043                    }
1044                    Expression::Cast(mut cast) => {
1045                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1046                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1047                            Expression::Cast(cast),
1048                        )));
1049                        tasks.push(TransformTask::Visit(child));
1050                    }
1051                    Expression::TryCast(mut cast) => {
1052                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1053                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1054                            Expression::TryCast(cast),
1055                        )));
1056                        tasks.push(TransformTask::Visit(child));
1057                    }
1058                    Expression::SafeCast(mut cast) => {
1059                        let child = std::mem::replace(&mut cast.this, Expression::Null(Null));
1060                        tasks.push(TransformTask::Finish(FinishTask::CastLike(
1061                            Expression::SafeCast(cast),
1062                        )));
1063                        tasks.push(TransformTask::Visit(child));
1064                    }
1065                    Expression::Function(mut function) => {
1066                        let args = std::mem::take(&mut function.args);
1067                        let count = args.len();
1068                        tasks.push(TransformTask::Finish(FinishTask::List(
1069                            Expression::Function(function),
1070                            count,
1071                        )));
1072                        for child in args.into_iter().rev() {
1073                            tasks.push(TransformTask::Visit(child));
1074                        }
1075                    }
1076                    Expression::Array(mut array) => {
1077                        let expressions = std::mem::take(&mut array.expressions);
1078                        let count = expressions.len();
1079                        tasks.push(TransformTask::Finish(FinishTask::List(
1080                            Expression::Array(array),
1081                            count,
1082                        )));
1083                        for child in expressions.into_iter().rev() {
1084                            tasks.push(TransformTask::Visit(child));
1085                        }
1086                    }
1087                    Expression::Tuple(mut tuple) => {
1088                        let expressions = std::mem::take(&mut tuple.expressions);
1089                        let count = expressions.len();
1090                        tasks.push(TransformTask::Finish(FinishTask::List(
1091                            Expression::Tuple(tuple),
1092                            count,
1093                        )));
1094                        for child in expressions.into_iter().rev() {
1095                            tasks.push(TransformTask::Visit(child));
1096                        }
1097                    }
1098                    Expression::ArrayFunc(mut array) => {
1099                        let expressions = std::mem::take(&mut array.expressions);
1100                        let count = expressions.len();
1101                        tasks.push(TransformTask::Finish(FinishTask::List(
1102                            Expression::ArrayFunc(array),
1103                            count,
1104                        )));
1105                        for child in expressions.into_iter().rev() {
1106                            tasks.push(TransformTask::Visit(child));
1107                        }
1108                    }
1109                    Expression::Coalesce(mut func) => {
1110                        let expressions = std::mem::take(&mut func.expressions);
1111                        let count = expressions.len();
1112                        tasks.push(TransformTask::Finish(FinishTask::List(
1113                            Expression::Coalesce(func),
1114                            count,
1115                        )));
1116                        for child in expressions.into_iter().rev() {
1117                            tasks.push(TransformTask::Visit(child));
1118                        }
1119                    }
1120                    Expression::Greatest(mut func) => {
1121                        let expressions = std::mem::take(&mut func.expressions);
1122                        let count = expressions.len();
1123                        tasks.push(TransformTask::Finish(FinishTask::List(
1124                            Expression::Greatest(func),
1125                            count,
1126                        )));
1127                        for child in expressions.into_iter().rev() {
1128                            tasks.push(TransformTask::Visit(child));
1129                        }
1130                    }
1131                    Expression::Least(mut func) => {
1132                        let expressions = std::mem::take(&mut func.expressions);
1133                        let count = expressions.len();
1134                        tasks.push(TransformTask::Finish(FinishTask::List(
1135                            Expression::Least(func),
1136                            count,
1137                        )));
1138                        for child in expressions.into_iter().rev() {
1139                            tasks.push(TransformTask::Visit(child));
1140                        }
1141                    }
1142                    Expression::ArrayConcat(mut func) => {
1143                        let expressions = std::mem::take(&mut func.expressions);
1144                        let count = expressions.len();
1145                        tasks.push(TransformTask::Finish(FinishTask::List(
1146                            Expression::ArrayConcat(func),
1147                            count,
1148                        )));
1149                        for child in expressions.into_iter().rev() {
1150                            tasks.push(TransformTask::Visit(child));
1151                        }
1152                    }
1153                    Expression::ArrayIntersect(mut func) => {
1154                        let expressions = std::mem::take(&mut func.expressions);
1155                        let count = expressions.len();
1156                        tasks.push(TransformTask::Finish(FinishTask::List(
1157                            Expression::ArrayIntersect(func),
1158                            count,
1159                        )));
1160                        for child in expressions.into_iter().rev() {
1161                            tasks.push(TransformTask::Visit(child));
1162                        }
1163                    }
1164                    Expression::ArrayZip(mut func) => {
1165                        let expressions = std::mem::take(&mut func.expressions);
1166                        let count = expressions.len();
1167                        tasks.push(TransformTask::Finish(FinishTask::List(
1168                            Expression::ArrayZip(func),
1169                            count,
1170                        )));
1171                        for child in expressions.into_iter().rev() {
1172                            tasks.push(TransformTask::Visit(child));
1173                        }
1174                    }
1175                    Expression::MapConcat(mut func) => {
1176                        let expressions = std::mem::take(&mut func.expressions);
1177                        let count = expressions.len();
1178                        tasks.push(TransformTask::Finish(FinishTask::List(
1179                            Expression::MapConcat(func),
1180                            count,
1181                        )));
1182                        for child in expressions.into_iter().rev() {
1183                            tasks.push(TransformTask::Visit(child));
1184                        }
1185                    }
1186                    Expression::JsonArray(mut func) => {
1187                        let expressions = std::mem::take(&mut func.expressions);
1188                        let count = expressions.len();
1189                        tasks.push(TransformTask::Finish(FinishTask::List(
1190                            Expression::JsonArray(func),
1191                            count,
1192                        )));
1193                        for child in expressions.into_iter().rev() {
1194                            tasks.push(TransformTask::Visit(child));
1195                        }
1196                    }
1197                    Expression::From(mut from) => {
1198                        let expressions = std::mem::take(&mut from.expressions);
1199                        let count = expressions.len();
1200                        tasks.push(TransformTask::Finish(FinishTask::From(*from, count)));
1201                        for child in expressions.into_iter().rev() {
1202                            tasks.push(TransformTask::Visit(child));
1203                        }
1204                    }
1205                    Expression::Select(mut select) => {
1206                        let expressions = std::mem::take(&mut select.expressions);
1207                        let expr_count = expressions.len();
1208
1209                        let from_info = select.from.take().map(|mut from| {
1210                            let children = std::mem::take(&mut from.expressions);
1211                            (from, children)
1212                        });
1213                        let from_present = from_info.is_some();
1214
1215                        let where_child = select.where_clause.as_mut().map(|where_clause| {
1216                            std::mem::replace(&mut where_clause.this, Expression::Null(Null))
1217                        });
1218                        let where_present = where_child.is_some();
1219
1220                        let group_expressions = select
1221                            .group_by
1222                            .as_mut()
1223                            .map(|group_by| std::mem::take(&mut group_by.expressions))
1224                            .unwrap_or_default();
1225                        let group_by_count = group_expressions.len();
1226
1227                        let having_child = select.having.as_mut().map(|having| {
1228                            std::mem::replace(&mut having.this, Expression::Null(Null))
1229                        });
1230                        let having_present = having_child.is_some();
1231
1232                        let qualify_child = select.qualify.as_mut().map(|qualify| {
1233                            std::mem::replace(&mut qualify.this, Expression::Null(Null))
1234                        });
1235                        let qualify_present = qualify_child.is_some();
1236
1237                        tasks.push(TransformTask::Finish(FinishTask::Select(SelectFrame {
1238                            select,
1239                            expr_count,
1240                            from_present,
1241                            where_present,
1242                            group_by_count,
1243                            having_present,
1244                            qualify_present,
1245                        })));
1246
1247                        if let Some(child) = qualify_child {
1248                            tasks.push(TransformTask::Visit(child));
1249                        }
1250                        if let Some(child) = having_child {
1251                            tasks.push(TransformTask::Visit(child));
1252                        }
1253                        for child in group_expressions.into_iter().rev() {
1254                            tasks.push(TransformTask::Visit(child));
1255                        }
1256                        if let Some(child) = where_child {
1257                            tasks.push(TransformTask::Visit(child));
1258                        }
1259                        if let Some((from, children)) = from_info {
1260                            tasks.push(TransformTask::Finish(FinishTask::From(
1261                                from,
1262                                children.len(),
1263                            )));
1264                            for child in children.into_iter().rev() {
1265                                tasks.push(TransformTask::Visit(child));
1266                            }
1267                        }
1268                        for child in expressions.into_iter().rev() {
1269                            tasks.push(TransformTask::Visit(child));
1270                        }
1271                    }
1272                    Expression::Union(mut union) => {
1273                        let right = std::mem::replace(&mut union.right, Expression::Null(Null));
1274                        let left = std::mem::replace(&mut union.left, Expression::Null(Null));
1275                        tasks.push(TransformTask::Finish(FinishTask::SetOp(Expression::Union(
1276                            union,
1277                        ))));
1278                        tasks.push(TransformTask::Visit(right));
1279                        tasks.push(TransformTask::Visit(left));
1280                    }
1281                    Expression::Intersect(mut intersect) => {
1282                        let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
1283                        let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
1284                        tasks.push(TransformTask::Finish(FinishTask::SetOp(
1285                            Expression::Intersect(intersect),
1286                        )));
1287                        tasks.push(TransformTask::Visit(right));
1288                        tasks.push(TransformTask::Visit(left));
1289                    }
1290                    Expression::Except(mut except) => {
1291                        let right = std::mem::replace(&mut except.right, Expression::Null(Null));
1292                        let left = std::mem::replace(&mut except.left, Expression::Null(Null));
1293                        tasks.push(TransformTask::Finish(FinishTask::SetOp(
1294                            Expression::Except(except),
1295                        )));
1296                        tasks.push(TransformTask::Visit(right));
1297                        tasks.push(TransformTask::Visit(left));
1298                    }
1299                    other => {
1300                        results.push(transform_recursive_reference(other, transform_fn)?);
1301                    }
1302                }
1303            }
1304            TransformTask::Finish(finish) => match finish {
1305                FinishTask::Unary(expr) => {
1306                    let child = transform_pop_result(&mut results)?;
1307                    let rebuilt = match expr {
1308                        Expression::Alias(mut alias) => {
1309                            alias.this = child;
1310                            Expression::Alias(alias)
1311                        }
1312                        Expression::Paren(mut paren) => {
1313                            paren.this = child;
1314                            Expression::Paren(paren)
1315                        }
1316                        Expression::Not(mut not) => {
1317                            not.this = child;
1318                            Expression::Not(not)
1319                        }
1320                        Expression::Neg(mut neg) => {
1321                            neg.this = child;
1322                            Expression::Neg(neg)
1323                        }
1324                        Expression::IsNull(mut expr) => {
1325                            expr.this = child;
1326                            Expression::IsNull(expr)
1327                        }
1328                        Expression::IsTrue(mut expr) => {
1329                            expr.this = child;
1330                            Expression::IsTrue(expr)
1331                        }
1332                        Expression::IsFalse(mut expr) => {
1333                            expr.this = child;
1334                            Expression::IsFalse(expr)
1335                        }
1336                        Expression::Subquery(mut subquery) => {
1337                            subquery.this = child;
1338                            Expression::Subquery(subquery)
1339                        }
1340                        Expression::Exists(mut exists) => {
1341                            exists.this = child;
1342                            Expression::Exists(exists)
1343                        }
1344                        Expression::TableArgument(mut arg) => {
1345                            arg.this = child;
1346                            Expression::TableArgument(arg)
1347                        }
1348                        _ => {
1349                            return Err(crate::error::Error::Internal(
1350                                "unexpected unary transform task".to_string(),
1351                            ));
1352                        }
1353                    };
1354                    results.push(transform_fn(rebuilt)?);
1355                }
1356                FinishTask::Binary(expr) => {
1357                    let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1358                    let left = children.next().expect("left child");
1359                    let right = children.next().expect("right child");
1360                    let rebuilt = match expr {
1361                        Expression::And(mut op) => {
1362                            op.left = left;
1363                            op.right = right;
1364                            Expression::And(op)
1365                        }
1366                        Expression::Or(mut op) => {
1367                            op.left = left;
1368                            op.right = right;
1369                            Expression::Or(op)
1370                        }
1371                        Expression::Add(mut op) => {
1372                            op.left = left;
1373                            op.right = right;
1374                            Expression::Add(op)
1375                        }
1376                        Expression::Sub(mut op) => {
1377                            op.left = left;
1378                            op.right = right;
1379                            Expression::Sub(op)
1380                        }
1381                        Expression::Mul(mut op) => {
1382                            op.left = left;
1383                            op.right = right;
1384                            Expression::Mul(op)
1385                        }
1386                        Expression::Div(mut op) => {
1387                            op.left = left;
1388                            op.right = right;
1389                            Expression::Div(op)
1390                        }
1391                        Expression::Eq(mut op) => {
1392                            op.left = left;
1393                            op.right = right;
1394                            Expression::Eq(op)
1395                        }
1396                        Expression::Lt(mut op) => {
1397                            op.left = left;
1398                            op.right = right;
1399                            Expression::Lt(op)
1400                        }
1401                        Expression::Gt(mut op) => {
1402                            op.left = left;
1403                            op.right = right;
1404                            Expression::Gt(op)
1405                        }
1406                        Expression::Neq(mut op) => {
1407                            op.left = left;
1408                            op.right = right;
1409                            Expression::Neq(op)
1410                        }
1411                        Expression::Lte(mut op) => {
1412                            op.left = left;
1413                            op.right = right;
1414                            Expression::Lte(op)
1415                        }
1416                        Expression::Gte(mut op) => {
1417                            op.left = left;
1418                            op.right = right;
1419                            Expression::Gte(op)
1420                        }
1421                        Expression::Mod(mut op) => {
1422                            op.left = left;
1423                            op.right = right;
1424                            Expression::Mod(op)
1425                        }
1426                        Expression::Concat(mut op) => {
1427                            op.left = left;
1428                            op.right = right;
1429                            Expression::Concat(op)
1430                        }
1431                        Expression::BitwiseAnd(mut op) => {
1432                            op.left = left;
1433                            op.right = right;
1434                            Expression::BitwiseAnd(op)
1435                        }
1436                        Expression::BitwiseOr(mut op) => {
1437                            op.left = left;
1438                            op.right = right;
1439                            Expression::BitwiseOr(op)
1440                        }
1441                        Expression::BitwiseXor(mut op) => {
1442                            op.left = left;
1443                            op.right = right;
1444                            Expression::BitwiseXor(op)
1445                        }
1446                        Expression::Is(mut op) => {
1447                            op.left = left;
1448                            op.right = right;
1449                            Expression::Is(op)
1450                        }
1451                        Expression::MemberOf(mut op) => {
1452                            op.left = left;
1453                            op.right = right;
1454                            Expression::MemberOf(op)
1455                        }
1456                        Expression::ArrayContainsAll(mut op) => {
1457                            op.left = left;
1458                            op.right = right;
1459                            Expression::ArrayContainsAll(op)
1460                        }
1461                        Expression::ArrayContainedBy(mut op) => {
1462                            op.left = left;
1463                            op.right = right;
1464                            Expression::ArrayContainedBy(op)
1465                        }
1466                        Expression::ArrayOverlaps(mut op) => {
1467                            op.left = left;
1468                            op.right = right;
1469                            Expression::ArrayOverlaps(op)
1470                        }
1471                        Expression::TsMatch(mut op) => {
1472                            op.left = left;
1473                            op.right = right;
1474                            Expression::TsMatch(op)
1475                        }
1476                        Expression::Adjacent(mut op) => {
1477                            op.left = left;
1478                            op.right = right;
1479                            Expression::Adjacent(op)
1480                        }
1481                        Expression::Like(mut like) => {
1482                            like.left = left;
1483                            like.right = right;
1484                            Expression::Like(like)
1485                        }
1486                        Expression::ILike(mut like) => {
1487                            like.left = left;
1488                            like.right = right;
1489                            Expression::ILike(like)
1490                        }
1491                        _ => {
1492                            return Err(crate::error::Error::Internal(
1493                                "unexpected binary transform task".to_string(),
1494                            ));
1495                        }
1496                    };
1497                    results.push(transform_fn(rebuilt)?);
1498                }
1499                FinishTask::CastLike(expr) => {
1500                    let child = transform_pop_result(&mut results)?;
1501                    let rebuilt = match expr {
1502                        Expression::Cast(mut cast) => {
1503                            cast.this = child;
1504                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1505                            Expression::Cast(cast)
1506                        }
1507                        Expression::TryCast(mut cast) => {
1508                            cast.this = child;
1509                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1510                            Expression::TryCast(cast)
1511                        }
1512                        Expression::SafeCast(mut cast) => {
1513                            cast.this = child;
1514                            cast.to = transform_data_type_recursive(cast.to, transform_fn)?;
1515                            Expression::SafeCast(cast)
1516                        }
1517                        _ => {
1518                            return Err(crate::error::Error::Internal(
1519                                "unexpected cast transform task".to_string(),
1520                            ));
1521                        }
1522                    };
1523                    results.push(transform_fn(rebuilt)?);
1524                }
1525                FinishTask::List(expr, count) => {
1526                    let children = transform_pop_results(&mut results, count)?;
1527                    let rebuilt = match expr {
1528                        Expression::Function(mut function) => {
1529                            function.args = children;
1530                            Expression::Function(function)
1531                        }
1532                        Expression::Array(mut array) => {
1533                            array.expressions = children;
1534                            Expression::Array(array)
1535                        }
1536                        Expression::Tuple(mut tuple) => {
1537                            tuple.expressions = children;
1538                            Expression::Tuple(tuple)
1539                        }
1540                        Expression::ArrayFunc(mut array) => {
1541                            array.expressions = children;
1542                            Expression::ArrayFunc(array)
1543                        }
1544                        Expression::Coalesce(mut func) => {
1545                            func.expressions = children;
1546                            Expression::Coalesce(func)
1547                        }
1548                        Expression::Greatest(mut func) => {
1549                            func.expressions = children;
1550                            Expression::Greatest(func)
1551                        }
1552                        Expression::Least(mut func) => {
1553                            func.expressions = children;
1554                            Expression::Least(func)
1555                        }
1556                        Expression::ArrayConcat(mut func) => {
1557                            func.expressions = children;
1558                            Expression::ArrayConcat(func)
1559                        }
1560                        Expression::ArrayIntersect(mut func) => {
1561                            func.expressions = children;
1562                            Expression::ArrayIntersect(func)
1563                        }
1564                        Expression::ArrayZip(mut func) => {
1565                            func.expressions = children;
1566                            Expression::ArrayZip(func)
1567                        }
1568                        Expression::MapConcat(mut func) => {
1569                            func.expressions = children;
1570                            Expression::MapConcat(func)
1571                        }
1572                        Expression::JsonArray(mut func) => {
1573                            func.expressions = children;
1574                            Expression::JsonArray(func)
1575                        }
1576                        _ => {
1577                            return Err(crate::error::Error::Internal(
1578                                "unexpected list transform task".to_string(),
1579                            ));
1580                        }
1581                    };
1582                    results.push(transform_fn(rebuilt)?);
1583                }
1584                FinishTask::From(mut from, count) => {
1585                    from.expressions = transform_pop_results(&mut results, count)?;
1586                    results.push(transform_fn(Expression::From(Box::new(from)))?);
1587                }
1588                FinishTask::Select(frame) => {
1589                    let mut select = *frame.select;
1590
1591                    if frame.qualify_present {
1592                        if let Some(ref mut qualify) = select.qualify {
1593                            qualify.this = transform_pop_result(&mut results)?;
1594                        }
1595                    }
1596                    if frame.having_present {
1597                        if let Some(ref mut having) = select.having {
1598                            having.this = transform_pop_result(&mut results)?;
1599                        }
1600                    }
1601                    if frame.group_by_count > 0 {
1602                        if let Some(ref mut group_by) = select.group_by {
1603                            group_by.expressions =
1604                                transform_pop_results(&mut results, frame.group_by_count)?;
1605                        }
1606                    }
1607                    if frame.where_present {
1608                        if let Some(ref mut where_clause) = select.where_clause {
1609                            where_clause.this = transform_pop_result(&mut results)?;
1610                        }
1611                    }
1612                    if frame.from_present {
1613                        match transform_pop_result(&mut results)? {
1614                            Expression::From(from) => {
1615                                select.from = Some(*from);
1616                            }
1617                            _ => {
1618                                return Err(crate::error::Error::Internal(
1619                                    "expected FROM expression result".to_string(),
1620                                ));
1621                            }
1622                        }
1623                    }
1624                    select.expressions = transform_pop_results(&mut results, frame.expr_count)?;
1625
1626                    select.joins = select
1627                        .joins
1628                        .into_iter()
1629                        .map(|mut join| {
1630                            join.this = transform_recursive(join.this, transform_fn)?;
1631                            if let Some(on) = join.on.take() {
1632                                join.on = Some(transform_recursive(on, transform_fn)?);
1633                            }
1634                            match transform_fn(Expression::Join(Box::new(join)))? {
1635                                Expression::Join(j) => Ok(*j),
1636                                _ => Err(crate::error::Error::parse(
1637                                    "Join transformation returned non-join expression",
1638                                    0,
1639                                    0,
1640                                    0,
1641                                    0,
1642                                )),
1643                            }
1644                        })
1645                        .collect::<Result<Vec<_>>>()?;
1646
1647                    select.lateral_views = select
1648                        .lateral_views
1649                        .into_iter()
1650                        .map(|mut lv| {
1651                            lv.this = transform_recursive(lv.this, transform_fn)?;
1652                            Ok(lv)
1653                        })
1654                        .collect::<Result<Vec<_>>>()?;
1655
1656                    if let Some(mut with) = select.with.take() {
1657                        with.ctes = with
1658                            .ctes
1659                            .into_iter()
1660                            .map(|mut cte| {
1661                                let original = cte.this.clone();
1662                                cte.this =
1663                                    transform_recursive(cte.this, transform_fn).unwrap_or(original);
1664                                cte
1665                            })
1666                            .collect();
1667                        select.with = Some(with);
1668                    }
1669
1670                    if let Some(mut order) = select.order_by.take() {
1671                        order.expressions = order
1672                            .expressions
1673                            .into_iter()
1674                            .map(|o| {
1675                                let mut o = o;
1676                                let original = o.this.clone();
1677                                o.this =
1678                                    transform_recursive(o.this, transform_fn).unwrap_or(original);
1679                                match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1680                                    Ok(Expression::Ordered(transformed)) => *transformed,
1681                                    Ok(_) | Err(_) => o,
1682                                }
1683                            })
1684                            .collect();
1685                        select.order_by = Some(order);
1686                    }
1687
1688                    if let Some(ref mut windows) = select.windows {
1689                        for nw in windows.iter_mut() {
1690                            nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1691                                .into_iter()
1692                                .map(|o| {
1693                                    let mut o = o;
1694                                    let original = o.this.clone();
1695                                    o.this = transform_recursive(o.this, transform_fn)
1696                                        .unwrap_or(original);
1697                                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1698                                        Ok(Expression::Ordered(transformed)) => *transformed,
1699                                        Ok(_) | Err(_) => o,
1700                                    }
1701                                })
1702                                .collect();
1703                        }
1704                    }
1705
1706                    results.push(transform_fn(Expression::Select(Box::new(select)))?);
1707                }
1708                FinishTask::SetOp(expr) => {
1709                    let mut children = transform_pop_results(&mut results, 2)?.into_iter();
1710                    let left = children.next().expect("left child");
1711                    let right = children.next().expect("right child");
1712
1713                    let rebuilt = match expr {
1714                        Expression::Union(mut union) => {
1715                            union.left = left;
1716                            union.right = right;
1717                            if let Some(mut order) = union.order_by.take() {
1718                                order.expressions = order
1719                                    .expressions
1720                                    .into_iter()
1721                                    .map(|o| {
1722                                        let mut o = o;
1723                                        let original = o.this.clone();
1724                                        o.this = transform_recursive(o.this, transform_fn)
1725                                            .unwrap_or(original);
1726                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1727                                        {
1728                                            Ok(Expression::Ordered(transformed)) => *transformed,
1729                                            Ok(_) | Err(_) => o,
1730                                        }
1731                                    })
1732                                    .collect();
1733                                union.order_by = Some(order);
1734                            }
1735                            if let Some(mut with) = union.with.take() {
1736                                with.ctes = with
1737                                    .ctes
1738                                    .into_iter()
1739                                    .map(|mut cte| {
1740                                        let original = cte.this.clone();
1741                                        cte.this = transform_recursive(cte.this, transform_fn)
1742                                            .unwrap_or(original);
1743                                        cte
1744                                    })
1745                                    .collect();
1746                                union.with = Some(with);
1747                            }
1748                            Expression::Union(union)
1749                        }
1750                        Expression::Intersect(mut intersect) => {
1751                            intersect.left = left;
1752                            intersect.right = right;
1753                            if let Some(mut order) = intersect.order_by.take() {
1754                                order.expressions = order
1755                                    .expressions
1756                                    .into_iter()
1757                                    .map(|o| {
1758                                        let mut o = o;
1759                                        let original = o.this.clone();
1760                                        o.this = transform_recursive(o.this, transform_fn)
1761                                            .unwrap_or(original);
1762                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1763                                        {
1764                                            Ok(Expression::Ordered(transformed)) => *transformed,
1765                                            Ok(_) | Err(_) => o,
1766                                        }
1767                                    })
1768                                    .collect();
1769                                intersect.order_by = Some(order);
1770                            }
1771                            if let Some(mut with) = intersect.with.take() {
1772                                with.ctes = with
1773                                    .ctes
1774                                    .into_iter()
1775                                    .map(|mut cte| {
1776                                        let original = cte.this.clone();
1777                                        cte.this = transform_recursive(cte.this, transform_fn)
1778                                            .unwrap_or(original);
1779                                        cte
1780                                    })
1781                                    .collect();
1782                                intersect.with = Some(with);
1783                            }
1784                            Expression::Intersect(intersect)
1785                        }
1786                        Expression::Except(mut except) => {
1787                            except.left = left;
1788                            except.right = right;
1789                            if let Some(mut order) = except.order_by.take() {
1790                                order.expressions = order
1791                                    .expressions
1792                                    .into_iter()
1793                                    .map(|o| {
1794                                        let mut o = o;
1795                                        let original = o.this.clone();
1796                                        o.this = transform_recursive(o.this, transform_fn)
1797                                            .unwrap_or(original);
1798                                        match transform_fn(Expression::Ordered(Box::new(o.clone())))
1799                                        {
1800                                            Ok(Expression::Ordered(transformed)) => *transformed,
1801                                            Ok(_) | Err(_) => o,
1802                                        }
1803                                    })
1804                                    .collect();
1805                                except.order_by = Some(order);
1806                            }
1807                            if let Some(mut with) = except.with.take() {
1808                                with.ctes = with
1809                                    .ctes
1810                                    .into_iter()
1811                                    .map(|mut cte| {
1812                                        let original = cte.this.clone();
1813                                        cte.this = transform_recursive(cte.this, transform_fn)
1814                                            .unwrap_or(original);
1815                                        cte
1816                                    })
1817                                    .collect();
1818                                except.with = Some(with);
1819                            }
1820                            Expression::Except(except)
1821                        }
1822                        _ => {
1823                            return Err(crate::error::Error::Internal(
1824                                "unexpected set-op transform task".to_string(),
1825                            ));
1826                        }
1827                    };
1828                    results.push(transform_fn(rebuilt)?);
1829                }
1830            },
1831        }
1832    }
1833
1834    match results.len() {
1835        1 => Ok(results.pop().expect("single transform result")),
1836        _ => Err(crate::error::Error::Internal(
1837            "unexpected transform result stack size".to_string(),
1838        )),
1839    }
1840}
1841
1842#[cfg(any(
1843    feature = "transpile",
1844    feature = "ast-tools",
1845    feature = "generate",
1846    feature = "semantic"
1847))]
1848fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1849where
1850    F: Fn(Expression) -> Result<Expression>,
1851{
1852    match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1853        Expression::Table(table) => Ok(*table),
1854        _ => Err(crate::error::Error::parse(
1855            "TableRef transformation returned non-table expression",
1856            0,
1857            0,
1858            0,
1859            0,
1860        )),
1861    }
1862}
1863
1864#[cfg(any(
1865    feature = "transpile",
1866    feature = "ast-tools",
1867    feature = "generate",
1868    feature = "semantic"
1869))]
1870fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1871where
1872    F: Fn(Expression) -> Result<Expression>,
1873{
1874    match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1875        Expression::From(from) => Ok(*from),
1876        _ => Err(crate::error::Error::parse(
1877            "FROM transformation returned non-FROM expression",
1878            0,
1879            0,
1880            0,
1881            0,
1882        )),
1883    }
1884}
1885
1886#[cfg(any(
1887    feature = "transpile",
1888    feature = "ast-tools",
1889    feature = "generate",
1890    feature = "semantic"
1891))]
1892fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1893where
1894    F: Fn(Expression) -> Result<Expression>,
1895{
1896    join.this = transform_recursive(join.this, transform_fn)?;
1897    if let Some(on) = join.on.take() {
1898        join.on = Some(transform_recursive(on, transform_fn)?);
1899    }
1900    if let Some(match_condition) = join.match_condition.take() {
1901        join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1902    }
1903    join.pivots = join
1904        .pivots
1905        .into_iter()
1906        .map(|pivot| transform_recursive(pivot, transform_fn))
1907        .collect::<Result<Vec<_>>>()?;
1908
1909    match transform_fn(Expression::Join(Box::new(join)))? {
1910        Expression::Join(join) => Ok(*join),
1911        _ => Err(crate::error::Error::parse(
1912            "Join transformation returned non-join expression",
1913            0,
1914            0,
1915            0,
1916            0,
1917        )),
1918    }
1919}
1920
1921#[cfg(any(
1922    feature = "transpile",
1923    feature = "ast-tools",
1924    feature = "generate",
1925    feature = "semantic"
1926))]
1927fn transform_output_clause_recursive<F>(
1928    mut output: OutputClause,
1929    transform_fn: &F,
1930) -> Result<OutputClause>
1931where
1932    F: Fn(Expression) -> Result<Expression>,
1933{
1934    output.columns = output
1935        .columns
1936        .into_iter()
1937        .map(|column| transform_recursive(column, transform_fn))
1938        .collect::<Result<Vec<_>>>()?;
1939    if let Some(into_table) = output.into_table.take() {
1940        output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1941    }
1942    Ok(output)
1943}
1944
1945#[cfg(any(
1946    feature = "transpile",
1947    feature = "ast-tools",
1948    feature = "generate",
1949    feature = "semantic"
1950))]
1951fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1952where
1953    F: Fn(Expression) -> Result<Expression>,
1954{
1955    with.ctes = with
1956        .ctes
1957        .into_iter()
1958        .map(|mut cte| {
1959            cte.this = transform_recursive(cte.this, transform_fn)?;
1960            Ok(cte)
1961        })
1962        .collect::<Result<Vec<_>>>()?;
1963    if let Some(search) = with.search.take() {
1964        with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1965    }
1966    Ok(with)
1967}
1968
1969#[cfg(any(
1970    feature = "transpile",
1971    feature = "ast-tools",
1972    feature = "generate",
1973    feature = "semantic"
1974))]
1975fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1976where
1977    F: Fn(Expression) -> Result<Expression>,
1978{
1979    order.expressions = order
1980        .expressions
1981        .into_iter()
1982        .map(|mut ordered| {
1983            let original = ordered.this.clone();
1984            ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1985            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1986                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1987                Ok(_) | Err(_) => Ok(ordered),
1988            }
1989        })
1990        .collect::<Result<Vec<_>>>()?;
1991    Ok(order)
1992}
1993
1994#[cfg(any(
1995    feature = "transpile",
1996    feature = "ast-tools",
1997    feature = "generate",
1998    feature = "semantic"
1999))]
2000fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
2001where
2002    F: Fn(Expression) -> Result<Expression>,
2003{
2004    use crate::expressions::BinaryOp;
2005
2006    // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
2007    macro_rules! recurse_agg {
2008        ($variant:ident, $f:expr) => {{
2009            let mut f = $f;
2010            f.this = transform_recursive(f.this, transform_fn)?;
2011            if let Some(filter) = f.filter.take() {
2012                f.filter = Some(transform_recursive(filter, transform_fn)?);
2013            }
2014            for ord in &mut f.order_by {
2015                ord.this = transform_recursive(
2016                    std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
2017                    transform_fn,
2018                )?;
2019            }
2020            if let Some((ref mut expr, _)) = f.having_max {
2021                *expr = Box::new(transform_recursive(
2022                    std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
2023                    transform_fn,
2024                )?);
2025            }
2026            if let Some(limit) = f.limit.take() {
2027                f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
2028            }
2029            Expression::$variant(f)
2030        }};
2031    }
2032
2033    // Helper macro to transform binary ops with Box<BinaryOp>
2034    macro_rules! transform_binary {
2035        ($variant:ident, $op:expr) => {{
2036            let left = transform_recursive($op.left, transform_fn)?;
2037            let right = transform_recursive($op.right, transform_fn)?;
2038            Expression::$variant(Box::new(BinaryOp {
2039                left,
2040                right,
2041                left_comments: $op.left_comments,
2042                operator_comments: $op.operator_comments,
2043                trailing_comments: $op.trailing_comments,
2044                inferred_type: $op.inferred_type,
2045            }))
2046        }};
2047    }
2048
2049    // Fast path: leaf nodes never need child traversal, apply transform directly
2050    if matches!(
2051        &expr,
2052        Expression::Literal(_)
2053            | Expression::Boolean(_)
2054            | Expression::Null(_)
2055            | Expression::Identifier(_)
2056            | Expression::Star(_)
2057            | Expression::Parameter(_)
2058            | Expression::Placeholder(_)
2059            | Expression::SessionParameter(_)
2060    ) {
2061        return transform_fn(expr);
2062    }
2063
2064    // First recursively transform children, then apply the transform function
2065    let expr = match expr {
2066        Expression::Select(mut select) => {
2067            select.expressions = select
2068                .expressions
2069                .into_iter()
2070                .map(|e| transform_recursive(e, transform_fn))
2071                .collect::<Result<Vec<_>>>()?;
2072
2073            // Transform FROM clause
2074            if let Some(mut from) = select.from.take() {
2075                from.expressions = from
2076                    .expressions
2077                    .into_iter()
2078                    .map(|e| transform_recursive(e, transform_fn))
2079                    .collect::<Result<Vec<_>>>()?;
2080                select.from = Some(from);
2081            }
2082
2083            // Transform JOINs - important for CROSS APPLY / LATERAL transformations
2084            select.joins = select
2085                .joins
2086                .into_iter()
2087                .map(|mut join| {
2088                    join.this = transform_recursive(join.this, transform_fn)?;
2089                    if let Some(on) = join.on.take() {
2090                        join.on = Some(transform_recursive(on, transform_fn)?);
2091                    }
2092                    // Wrap join in Expression::Join to allow transform_fn to transform it
2093                    match transform_fn(Expression::Join(Box::new(join)))? {
2094                        Expression::Join(j) => Ok(*j),
2095                        _ => Err(crate::error::Error::parse(
2096                            "Join transformation returned non-join expression",
2097                            0,
2098                            0,
2099                            0,
2100                            0,
2101                        )),
2102                    }
2103                })
2104                .collect::<Result<Vec<_>>>()?;
2105
2106            // Transform LATERAL VIEW expressions (Hive/Spark)
2107            select.lateral_views = select
2108                .lateral_views
2109                .into_iter()
2110                .map(|mut lv| {
2111                    lv.this = transform_recursive(lv.this, transform_fn)?;
2112                    Ok(lv)
2113                })
2114                .collect::<Result<Vec<_>>>()?;
2115
2116            // Transform WHERE clause
2117            if let Some(mut where_clause) = select.where_clause.take() {
2118                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2119                select.where_clause = Some(where_clause);
2120            }
2121
2122            // Transform GROUP BY
2123            if let Some(mut group_by) = select.group_by.take() {
2124                group_by.expressions = group_by
2125                    .expressions
2126                    .into_iter()
2127                    .map(|e| transform_recursive(e, transform_fn))
2128                    .collect::<Result<Vec<_>>>()?;
2129                select.group_by = Some(group_by);
2130            }
2131
2132            // Transform HAVING
2133            if let Some(mut having) = select.having.take() {
2134                having.this = transform_recursive(having.this, transform_fn)?;
2135                select.having = Some(having);
2136            }
2137
2138            // Transform WITH (CTEs)
2139            if let Some(mut with) = select.with.take() {
2140                with.ctes = with
2141                    .ctes
2142                    .into_iter()
2143                    .map(|mut cte| {
2144                        let original = cte.this.clone();
2145                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2146                        cte
2147                    })
2148                    .collect();
2149                select.with = Some(with);
2150            }
2151
2152            // Transform ORDER BY
2153            if let Some(mut order) = select.order_by.take() {
2154                order.expressions = order
2155                    .expressions
2156                    .into_iter()
2157                    .map(|o| {
2158                        let mut o = o;
2159                        let original = o.this.clone();
2160                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2161                        // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
2162                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2163                            Ok(Expression::Ordered(transformed)) => *transformed,
2164                            Ok(_) | Err(_) => o,
2165                        }
2166                    })
2167                    .collect();
2168                select.order_by = Some(order);
2169            }
2170
2171            // Transform WINDOW clause order_by
2172            if let Some(ref mut windows) = select.windows {
2173                for nw in windows.iter_mut() {
2174                    nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
2175                        .into_iter()
2176                        .map(|o| {
2177                            let mut o = o;
2178                            let original = o.this.clone();
2179                            o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2180                            match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2181                                Ok(Expression::Ordered(transformed)) => *transformed,
2182                                Ok(_) | Err(_) => o,
2183                            }
2184                        })
2185                        .collect();
2186                }
2187            }
2188
2189            // Transform QUALIFY
2190            if let Some(mut qual) = select.qualify.take() {
2191                qual.this = transform_recursive(qual.this, transform_fn)?;
2192                select.qualify = Some(qual);
2193            }
2194
2195            Expression::Select(select)
2196        }
2197        Expression::Function(mut f) => {
2198            f.args = f
2199                .args
2200                .into_iter()
2201                .map(|e| transform_recursive(e, transform_fn))
2202                .collect::<Result<Vec<_>>>()?;
2203            Expression::Function(f)
2204        }
2205        Expression::AggregateFunction(mut f) => {
2206            f.args = f
2207                .args
2208                .into_iter()
2209                .map(|e| transform_recursive(e, transform_fn))
2210                .collect::<Result<Vec<_>>>()?;
2211            if let Some(filter) = f.filter {
2212                f.filter = Some(transform_recursive(filter, transform_fn)?);
2213            }
2214            Expression::AggregateFunction(f)
2215        }
2216        Expression::WindowFunction(mut wf) => {
2217            wf.this = transform_recursive(wf.this, transform_fn)?;
2218            wf.over.partition_by = wf
2219                .over
2220                .partition_by
2221                .into_iter()
2222                .map(|e| transform_recursive(e, transform_fn))
2223                .collect::<Result<Vec<_>>>()?;
2224            // Transform order_by items through Expression::Ordered wrapper
2225            wf.over.order_by = wf
2226                .over
2227                .order_by
2228                .into_iter()
2229                .map(|o| {
2230                    let mut o = o;
2231                    o.this = transform_recursive(o.this, transform_fn)?;
2232                    match transform_fn(Expression::Ordered(Box::new(o)))? {
2233                        Expression::Ordered(transformed) => Ok(*transformed),
2234                        _ => Err(crate::error::Error::parse(
2235                            "Ordered transformation returned non-Ordered expression",
2236                            0,
2237                            0,
2238                            0,
2239                            0,
2240                        )),
2241                    }
2242                })
2243                .collect::<Result<Vec<_>>>()?;
2244            Expression::WindowFunction(wf)
2245        }
2246        Expression::Alias(mut a) => {
2247            a.this = transform_recursive(a.this, transform_fn)?;
2248            Expression::Alias(a)
2249        }
2250        Expression::Cast(mut c) => {
2251            c.this = transform_recursive(c.this, transform_fn)?;
2252            // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
2253            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2254            Expression::Cast(c)
2255        }
2256        Expression::And(op) => transform_binary!(And, *op),
2257        Expression::Or(op) => transform_binary!(Or, *op),
2258        Expression::Add(op) => transform_binary!(Add, *op),
2259        Expression::Sub(op) => transform_binary!(Sub, *op),
2260        Expression::Mul(op) => transform_binary!(Mul, *op),
2261        Expression::Div(op) => transform_binary!(Div, *op),
2262        Expression::Eq(op) => transform_binary!(Eq, *op),
2263        Expression::Lt(op) => transform_binary!(Lt, *op),
2264        Expression::Gt(op) => transform_binary!(Gt, *op),
2265        Expression::Paren(mut p) => {
2266            p.this = transform_recursive(p.this, transform_fn)?;
2267            Expression::Paren(p)
2268        }
2269        Expression::Coalesce(mut f) => {
2270            f.expressions = f
2271                .expressions
2272                .into_iter()
2273                .map(|e| transform_recursive(e, transform_fn))
2274                .collect::<Result<Vec<_>>>()?;
2275            Expression::Coalesce(f)
2276        }
2277        Expression::IfNull(mut f) => {
2278            f.this = transform_recursive(f.this, transform_fn)?;
2279            f.expression = transform_recursive(f.expression, transform_fn)?;
2280            Expression::IfNull(f)
2281        }
2282        Expression::Nvl(mut f) => {
2283            f.this = transform_recursive(f.this, transform_fn)?;
2284            f.expression = transform_recursive(f.expression, transform_fn)?;
2285            Expression::Nvl(f)
2286        }
2287        Expression::In(mut i) => {
2288            i.this = transform_recursive(i.this, transform_fn)?;
2289            i.expressions = i
2290                .expressions
2291                .into_iter()
2292                .map(|e| transform_recursive(e, transform_fn))
2293                .collect::<Result<Vec<_>>>()?;
2294            if let Some(query) = i.query {
2295                i.query = Some(transform_recursive(query, transform_fn)?);
2296            }
2297            Expression::In(i)
2298        }
2299        Expression::Not(mut n) => {
2300            n.this = transform_recursive(n.this, transform_fn)?;
2301            Expression::Not(n)
2302        }
2303        Expression::ArraySlice(mut s) => {
2304            s.this = transform_recursive(s.this, transform_fn)?;
2305            if let Some(start) = s.start {
2306                s.start = Some(transform_recursive(start, transform_fn)?);
2307            }
2308            if let Some(end) = s.end {
2309                s.end = Some(transform_recursive(end, transform_fn)?);
2310            }
2311            Expression::ArraySlice(s)
2312        }
2313        Expression::Subscript(mut s) => {
2314            s.this = transform_recursive(s.this, transform_fn)?;
2315            s.index = transform_recursive(s.index, transform_fn)?;
2316            Expression::Subscript(s)
2317        }
2318        Expression::Array(mut a) => {
2319            a.expressions = a
2320                .expressions
2321                .into_iter()
2322                .map(|e| transform_recursive(e, transform_fn))
2323                .collect::<Result<Vec<_>>>()?;
2324            Expression::Array(a)
2325        }
2326        Expression::Struct(mut s) => {
2327            let mut new_fields = Vec::new();
2328            for (name, expr) in s.fields {
2329                let transformed = transform_recursive(expr, transform_fn)?;
2330                new_fields.push((name, transformed));
2331            }
2332            s.fields = new_fields;
2333            Expression::Struct(s)
2334        }
2335        Expression::NamedArgument(mut na) => {
2336            na.value = transform_recursive(na.value, transform_fn)?;
2337            Expression::NamedArgument(na)
2338        }
2339        Expression::MapFunc(mut m) => {
2340            m.keys = m
2341                .keys
2342                .into_iter()
2343                .map(|e| transform_recursive(e, transform_fn))
2344                .collect::<Result<Vec<_>>>()?;
2345            m.values = m
2346                .values
2347                .into_iter()
2348                .map(|e| transform_recursive(e, transform_fn))
2349                .collect::<Result<Vec<_>>>()?;
2350            Expression::MapFunc(m)
2351        }
2352        Expression::ArrayFunc(mut a) => {
2353            a.expressions = a
2354                .expressions
2355                .into_iter()
2356                .map(|e| transform_recursive(e, transform_fn))
2357                .collect::<Result<Vec<_>>>()?;
2358            Expression::ArrayFunc(a)
2359        }
2360        Expression::Lambda(mut l) => {
2361            l.body = transform_recursive(l.body, transform_fn)?;
2362            Expression::Lambda(l)
2363        }
2364        Expression::JsonExtract(mut f) => {
2365            f.this = transform_recursive(f.this, transform_fn)?;
2366            f.path = transform_recursive(f.path, transform_fn)?;
2367            Expression::JsonExtract(f)
2368        }
2369        Expression::JsonExtractScalar(mut f) => {
2370            f.this = transform_recursive(f.this, transform_fn)?;
2371            f.path = transform_recursive(f.path, transform_fn)?;
2372            Expression::JsonExtractScalar(f)
2373        }
2374
2375        // ===== UnaryFunc-based expressions =====
2376        // These all have a single `this: Expression` child
2377        Expression::Length(mut f) => {
2378            f.this = transform_recursive(f.this, transform_fn)?;
2379            Expression::Length(f)
2380        }
2381        Expression::Upper(mut f) => {
2382            f.this = transform_recursive(f.this, transform_fn)?;
2383            Expression::Upper(f)
2384        }
2385        Expression::Lower(mut f) => {
2386            f.this = transform_recursive(f.this, transform_fn)?;
2387            Expression::Lower(f)
2388        }
2389        Expression::LTrim(mut f) => {
2390            f.this = transform_recursive(f.this, transform_fn)?;
2391            Expression::LTrim(f)
2392        }
2393        Expression::RTrim(mut f) => {
2394            f.this = transform_recursive(f.this, transform_fn)?;
2395            Expression::RTrim(f)
2396        }
2397        Expression::Reverse(mut f) => {
2398            f.this = transform_recursive(f.this, transform_fn)?;
2399            Expression::Reverse(f)
2400        }
2401        Expression::Abs(mut f) => {
2402            f.this = transform_recursive(f.this, transform_fn)?;
2403            Expression::Abs(f)
2404        }
2405        Expression::Ceil(mut f) => {
2406            f.this = transform_recursive(f.this, transform_fn)?;
2407            Expression::Ceil(f)
2408        }
2409        Expression::Floor(mut f) => {
2410            f.this = transform_recursive(f.this, transform_fn)?;
2411            Expression::Floor(f)
2412        }
2413        Expression::Sign(mut f) => {
2414            f.this = transform_recursive(f.this, transform_fn)?;
2415            Expression::Sign(f)
2416        }
2417        Expression::Sqrt(mut f) => {
2418            f.this = transform_recursive(f.this, transform_fn)?;
2419            Expression::Sqrt(f)
2420        }
2421        Expression::Cbrt(mut f) => {
2422            f.this = transform_recursive(f.this, transform_fn)?;
2423            Expression::Cbrt(f)
2424        }
2425        Expression::Ln(mut f) => {
2426            f.this = transform_recursive(f.this, transform_fn)?;
2427            Expression::Ln(f)
2428        }
2429        Expression::Log(mut f) => {
2430            f.this = transform_recursive(f.this, transform_fn)?;
2431            if let Some(base) = f.base {
2432                f.base = Some(transform_recursive(base, transform_fn)?);
2433            }
2434            Expression::Log(f)
2435        }
2436        Expression::Exp(mut f) => {
2437            f.this = transform_recursive(f.this, transform_fn)?;
2438            Expression::Exp(f)
2439        }
2440        Expression::Date(mut f) => {
2441            f.this = transform_recursive(f.this, transform_fn)?;
2442            Expression::Date(f)
2443        }
2444        Expression::Stddev(f) => recurse_agg!(Stddev, f),
2445        Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
2446        Expression::Variance(f) => recurse_agg!(Variance, f),
2447
2448        // ===== BinaryFunc-based expressions =====
2449        Expression::ModFunc(mut f) => {
2450            f.this = transform_recursive(f.this, transform_fn)?;
2451            f.expression = transform_recursive(f.expression, transform_fn)?;
2452            Expression::ModFunc(f)
2453        }
2454        Expression::Power(mut f) => {
2455            f.this = transform_recursive(f.this, transform_fn)?;
2456            f.expression = transform_recursive(f.expression, transform_fn)?;
2457            Expression::Power(f)
2458        }
2459        Expression::MapFromArrays(mut f) => {
2460            f.this = transform_recursive(f.this, transform_fn)?;
2461            f.expression = transform_recursive(f.expression, transform_fn)?;
2462            Expression::MapFromArrays(f)
2463        }
2464        Expression::ElementAt(mut f) => {
2465            f.this = transform_recursive(f.this, transform_fn)?;
2466            f.expression = transform_recursive(f.expression, transform_fn)?;
2467            Expression::ElementAt(f)
2468        }
2469        Expression::MapContainsKey(mut f) => {
2470            f.this = transform_recursive(f.this, transform_fn)?;
2471            f.expression = transform_recursive(f.expression, transform_fn)?;
2472            Expression::MapContainsKey(f)
2473        }
2474        Expression::Left(mut f) => {
2475            f.this = transform_recursive(f.this, transform_fn)?;
2476            f.length = transform_recursive(f.length, transform_fn)?;
2477            Expression::Left(f)
2478        }
2479        Expression::Right(mut f) => {
2480            f.this = transform_recursive(f.this, transform_fn)?;
2481            f.length = transform_recursive(f.length, transform_fn)?;
2482            Expression::Right(f)
2483        }
2484        Expression::Repeat(mut f) => {
2485            f.this = transform_recursive(f.this, transform_fn)?;
2486            f.times = transform_recursive(f.times, transform_fn)?;
2487            Expression::Repeat(f)
2488        }
2489
2490        // ===== Complex function expressions =====
2491        Expression::Substring(mut f) => {
2492            f.this = transform_recursive(f.this, transform_fn)?;
2493            f.start = transform_recursive(f.start, transform_fn)?;
2494            if let Some(len) = f.length {
2495                f.length = Some(transform_recursive(len, transform_fn)?);
2496            }
2497            Expression::Substring(f)
2498        }
2499        Expression::Replace(mut f) => {
2500            f.this = transform_recursive(f.this, transform_fn)?;
2501            f.old = transform_recursive(f.old, transform_fn)?;
2502            f.new = transform_recursive(f.new, transform_fn)?;
2503            Expression::Replace(f)
2504        }
2505        Expression::ConcatWs(mut f) => {
2506            f.separator = transform_recursive(f.separator, transform_fn)?;
2507            f.expressions = f
2508                .expressions
2509                .into_iter()
2510                .map(|e| transform_recursive(e, transform_fn))
2511                .collect::<Result<Vec<_>>>()?;
2512            Expression::ConcatWs(f)
2513        }
2514        Expression::Trim(mut f) => {
2515            f.this = transform_recursive(f.this, transform_fn)?;
2516            if let Some(chars) = f.characters {
2517                f.characters = Some(transform_recursive(chars, transform_fn)?);
2518            }
2519            Expression::Trim(f)
2520        }
2521        Expression::Split(mut f) => {
2522            f.this = transform_recursive(f.this, transform_fn)?;
2523            f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
2524            Expression::Split(f)
2525        }
2526        Expression::Lpad(mut f) => {
2527            f.this = transform_recursive(f.this, transform_fn)?;
2528            f.length = transform_recursive(f.length, transform_fn)?;
2529            if let Some(fill) = f.fill {
2530                f.fill = Some(transform_recursive(fill, transform_fn)?);
2531            }
2532            Expression::Lpad(f)
2533        }
2534        Expression::Rpad(mut f) => {
2535            f.this = transform_recursive(f.this, transform_fn)?;
2536            f.length = transform_recursive(f.length, transform_fn)?;
2537            if let Some(fill) = f.fill {
2538                f.fill = Some(transform_recursive(fill, transform_fn)?);
2539            }
2540            Expression::Rpad(f)
2541        }
2542
2543        // ===== Conditional expressions =====
2544        Expression::Case(mut c) => {
2545            if let Some(operand) = c.operand {
2546                c.operand = Some(transform_recursive(operand, transform_fn)?);
2547            }
2548            c.whens = c
2549                .whens
2550                .into_iter()
2551                .map(|(cond, then)| {
2552                    let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
2553                    let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
2554                    (new_cond, new_then)
2555                })
2556                .collect();
2557            if let Some(else_expr) = c.else_ {
2558                c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
2559            }
2560            Expression::Case(c)
2561        }
2562        Expression::IfFunc(mut f) => {
2563            f.condition = transform_recursive(f.condition, transform_fn)?;
2564            f.true_value = transform_recursive(f.true_value, transform_fn)?;
2565            if let Some(false_val) = f.false_value {
2566                f.false_value = Some(transform_recursive(false_val, transform_fn)?);
2567            }
2568            Expression::IfFunc(f)
2569        }
2570
2571        // ===== Date/Time expressions =====
2572        Expression::DateAdd(mut f) => {
2573            f.this = transform_recursive(f.this, transform_fn)?;
2574            f.interval = transform_recursive(f.interval, transform_fn)?;
2575            Expression::DateAdd(f)
2576        }
2577        Expression::DateSub(mut f) => {
2578            f.this = transform_recursive(f.this, transform_fn)?;
2579            f.interval = transform_recursive(f.interval, transform_fn)?;
2580            Expression::DateSub(f)
2581        }
2582        Expression::DateDiff(mut f) => {
2583            f.this = transform_recursive(f.this, transform_fn)?;
2584            f.expression = transform_recursive(f.expression, transform_fn)?;
2585            Expression::DateDiff(f)
2586        }
2587        Expression::DateTrunc(mut f) => {
2588            f.this = transform_recursive(f.this, transform_fn)?;
2589            Expression::DateTrunc(f)
2590        }
2591        Expression::Extract(mut f) => {
2592            f.this = transform_recursive(f.this, transform_fn)?;
2593            Expression::Extract(f)
2594        }
2595
2596        // ===== JSON expressions =====
2597        Expression::JsonObject(mut f) => {
2598            f.pairs = f
2599                .pairs
2600                .into_iter()
2601                .map(|(k, v)| {
2602                    let new_k = transform_recursive(k, transform_fn)?;
2603                    let new_v = transform_recursive(v, transform_fn)?;
2604                    Ok((new_k, new_v))
2605                })
2606                .collect::<Result<Vec<_>>>()?;
2607            Expression::JsonObject(f)
2608        }
2609
2610        // ===== Subquery expressions =====
2611        Expression::Subquery(mut s) => {
2612            s.this = transform_recursive(s.this, transform_fn)?;
2613            Expression::Subquery(s)
2614        }
2615        Expression::Exists(mut e) => {
2616            e.this = transform_recursive(e.this, transform_fn)?;
2617            Expression::Exists(e)
2618        }
2619        Expression::Describe(mut d) => {
2620            d.target = transform_recursive(d.target, transform_fn)?;
2621            Expression::Describe(d)
2622        }
2623
2624        // ===== Set operations =====
2625        Expression::Union(mut u) => {
2626            let left = std::mem::replace(&mut u.left, Expression::Null(Null));
2627            u.left = transform_recursive(left, transform_fn)?;
2628            let right = std::mem::replace(&mut u.right, Expression::Null(Null));
2629            u.right = transform_recursive(right, transform_fn)?;
2630            if let Some(mut order) = u.order_by.take() {
2631                order.expressions = order
2632                    .expressions
2633                    .into_iter()
2634                    .map(|o| {
2635                        let mut o = o;
2636                        let original = o.this.clone();
2637                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2638                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2639                            Ok(Expression::Ordered(transformed)) => *transformed,
2640                            Ok(_) | Err(_) => o,
2641                        }
2642                    })
2643                    .collect();
2644                u.order_by = Some(order);
2645            }
2646            if let Some(mut with) = u.with.take() {
2647                with.ctes = with
2648                    .ctes
2649                    .into_iter()
2650                    .map(|mut cte| {
2651                        let original = cte.this.clone();
2652                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2653                        cte
2654                    })
2655                    .collect();
2656                u.with = Some(with);
2657            }
2658            Expression::Union(u)
2659        }
2660        Expression::Intersect(mut i) => {
2661            let left = std::mem::replace(&mut i.left, Expression::Null(Null));
2662            i.left = transform_recursive(left, transform_fn)?;
2663            let right = std::mem::replace(&mut i.right, Expression::Null(Null));
2664            i.right = transform_recursive(right, transform_fn)?;
2665            if let Some(mut order) = i.order_by.take() {
2666                order.expressions = order
2667                    .expressions
2668                    .into_iter()
2669                    .map(|o| {
2670                        let mut o = o;
2671                        let original = o.this.clone();
2672                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2673                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2674                            Ok(Expression::Ordered(transformed)) => *transformed,
2675                            Ok(_) | Err(_) => o,
2676                        }
2677                    })
2678                    .collect();
2679                i.order_by = Some(order);
2680            }
2681            if let Some(mut with) = i.with.take() {
2682                with.ctes = with
2683                    .ctes
2684                    .into_iter()
2685                    .map(|mut cte| {
2686                        let original = cte.this.clone();
2687                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2688                        cte
2689                    })
2690                    .collect();
2691                i.with = Some(with);
2692            }
2693            Expression::Intersect(i)
2694        }
2695        Expression::Except(mut e) => {
2696            let left = std::mem::replace(&mut e.left, Expression::Null(Null));
2697            e.left = transform_recursive(left, transform_fn)?;
2698            let right = std::mem::replace(&mut e.right, Expression::Null(Null));
2699            e.right = transform_recursive(right, transform_fn)?;
2700            if let Some(mut order) = e.order_by.take() {
2701                order.expressions = order
2702                    .expressions
2703                    .into_iter()
2704                    .map(|o| {
2705                        let mut o = o;
2706                        let original = o.this.clone();
2707                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2708                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2709                            Ok(Expression::Ordered(transformed)) => *transformed,
2710                            Ok(_) | Err(_) => o,
2711                        }
2712                    })
2713                    .collect();
2714                e.order_by = Some(order);
2715            }
2716            if let Some(mut with) = e.with.take() {
2717                with.ctes = with
2718                    .ctes
2719                    .into_iter()
2720                    .map(|mut cte| {
2721                        let original = cte.this.clone();
2722                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2723                        cte
2724                    })
2725                    .collect();
2726                e.with = Some(with);
2727            }
2728            Expression::Except(e)
2729        }
2730
2731        // ===== DML expressions =====
2732        Expression::Insert(mut ins) => {
2733            // Transform VALUES clause expressions
2734            let mut new_values = Vec::new();
2735            for row in ins.values {
2736                let mut new_row = Vec::new();
2737                for e in row {
2738                    new_row.push(transform_recursive(e, transform_fn)?);
2739                }
2740                new_values.push(new_row);
2741            }
2742            ins.values = new_values;
2743
2744            // Transform query (for INSERT ... SELECT)
2745            if let Some(query) = ins.query {
2746                ins.query = Some(transform_recursive(query, transform_fn)?);
2747            }
2748
2749            // Transform RETURNING clause
2750            let mut new_returning = Vec::new();
2751            for e in ins.returning {
2752                new_returning.push(transform_recursive(e, transform_fn)?);
2753            }
2754            ins.returning = new_returning;
2755
2756            // Transform ON CONFLICT clause
2757            if let Some(on_conflict) = ins.on_conflict {
2758                ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
2759            }
2760
2761            Expression::Insert(ins)
2762        }
2763        Expression::Update(mut upd) => {
2764            upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
2765            upd.extra_tables = upd
2766                .extra_tables
2767                .into_iter()
2768                .map(|table| transform_table_ref_recursive(table, transform_fn))
2769                .collect::<Result<Vec<_>>>()?;
2770            upd.table_joins = upd
2771                .table_joins
2772                .into_iter()
2773                .map(|join| transform_join_recursive(join, transform_fn))
2774                .collect::<Result<Vec<_>>>()?;
2775            upd.set = upd
2776                .set
2777                .into_iter()
2778                .map(|(id, val)| {
2779                    let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
2780                    (id, new_val)
2781                })
2782                .collect();
2783            if let Some(from_clause) = upd.from_clause.take() {
2784                upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
2785            }
2786            upd.from_joins = upd
2787                .from_joins
2788                .into_iter()
2789                .map(|join| transform_join_recursive(join, transform_fn))
2790                .collect::<Result<Vec<_>>>()?;
2791            if let Some(mut where_clause) = upd.where_clause.take() {
2792                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2793                upd.where_clause = Some(where_clause);
2794            }
2795            upd.returning = upd
2796                .returning
2797                .into_iter()
2798                .map(|expr| transform_recursive(expr, transform_fn))
2799                .collect::<Result<Vec<_>>>()?;
2800            if let Some(output) = upd.output.take() {
2801                upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2802            }
2803            if let Some(with) = upd.with.take() {
2804                upd.with = Some(transform_with_recursive(with, transform_fn)?);
2805            }
2806            if let Some(limit) = upd.limit.take() {
2807                upd.limit = Some(transform_recursive(limit, transform_fn)?);
2808            }
2809            if let Some(order_by) = upd.order_by.take() {
2810                upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2811            }
2812            Expression::Update(upd)
2813        }
2814        Expression::Delete(mut del) => {
2815            del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2816            del.using = del
2817                .using
2818                .into_iter()
2819                .map(|table| transform_table_ref_recursive(table, transform_fn))
2820                .collect::<Result<Vec<_>>>()?;
2821            if let Some(mut where_clause) = del.where_clause.take() {
2822                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2823                del.where_clause = Some(where_clause);
2824            }
2825            if let Some(output) = del.output.take() {
2826                del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2827            }
2828            if let Some(with) = del.with.take() {
2829                del.with = Some(transform_with_recursive(with, transform_fn)?);
2830            }
2831            if let Some(limit) = del.limit.take() {
2832                del.limit = Some(transform_recursive(limit, transform_fn)?);
2833            }
2834            if let Some(order_by) = del.order_by.take() {
2835                del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2836            }
2837            del.returning = del
2838                .returning
2839                .into_iter()
2840                .map(|expr| transform_recursive(expr, transform_fn))
2841                .collect::<Result<Vec<_>>>()?;
2842            del.tables = del
2843                .tables
2844                .into_iter()
2845                .map(|table| transform_table_ref_recursive(table, transform_fn))
2846                .collect::<Result<Vec<_>>>()?;
2847            del.joins = del
2848                .joins
2849                .into_iter()
2850                .map(|join| transform_join_recursive(join, transform_fn))
2851                .collect::<Result<Vec<_>>>()?;
2852            Expression::Delete(del)
2853        }
2854
2855        // ===== CTE expressions =====
2856        Expression::With(mut w) => {
2857            w.ctes = w
2858                .ctes
2859                .into_iter()
2860                .map(|mut cte| {
2861                    let original = cte.this.clone();
2862                    cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2863                    cte
2864                })
2865                .collect();
2866            Expression::With(w)
2867        }
2868        Expression::Cte(mut c) => {
2869            c.this = transform_recursive(c.this, transform_fn)?;
2870            Expression::Cte(c)
2871        }
2872
2873        // ===== Order expressions =====
2874        Expression::Ordered(mut o) => {
2875            o.this = transform_recursive(o.this, transform_fn)?;
2876            Expression::Ordered(o)
2877        }
2878
2879        // ===== Negation =====
2880        Expression::Neg(mut n) => {
2881            n.this = transform_recursive(n.this, transform_fn)?;
2882            Expression::Neg(n)
2883        }
2884
2885        // ===== Between =====
2886        Expression::Between(mut b) => {
2887            b.this = transform_recursive(b.this, transform_fn)?;
2888            b.low = transform_recursive(b.low, transform_fn)?;
2889            b.high = transform_recursive(b.high, transform_fn)?;
2890            Expression::Between(b)
2891        }
2892        Expression::IsNull(mut i) => {
2893            i.this = transform_recursive(i.this, transform_fn)?;
2894            Expression::IsNull(i)
2895        }
2896        Expression::IsTrue(mut i) => {
2897            i.this = transform_recursive(i.this, transform_fn)?;
2898            Expression::IsTrue(i)
2899        }
2900        Expression::IsFalse(mut i) => {
2901            i.this = transform_recursive(i.this, transform_fn)?;
2902            Expression::IsFalse(i)
2903        }
2904
2905        // ===== Like expressions =====
2906        Expression::Like(mut l) => {
2907            l.left = transform_recursive(l.left, transform_fn)?;
2908            l.right = transform_recursive(l.right, transform_fn)?;
2909            Expression::Like(l)
2910        }
2911        Expression::ILike(mut l) => {
2912            l.left = transform_recursive(l.left, transform_fn)?;
2913            l.right = transform_recursive(l.right, transform_fn)?;
2914            Expression::ILike(l)
2915        }
2916
2917        // ===== Additional binary ops not covered by macro =====
2918        Expression::Neq(op) => transform_binary!(Neq, *op),
2919        Expression::Lte(op) => transform_binary!(Lte, *op),
2920        Expression::Gte(op) => transform_binary!(Gte, *op),
2921        Expression::Mod(op) => transform_binary!(Mod, *op),
2922        Expression::Concat(op) => transform_binary!(Concat, *op),
2923        Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2924        Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2925        Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2926        Expression::Is(op) => transform_binary!(Is, *op),
2927
2928        // ===== TryCast / SafeCast =====
2929        Expression::TryCast(mut c) => {
2930            c.this = transform_recursive(c.this, transform_fn)?;
2931            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2932            Expression::TryCast(c)
2933        }
2934        Expression::SafeCast(mut c) => {
2935            c.this = transform_recursive(c.this, transform_fn)?;
2936            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2937            Expression::SafeCast(c)
2938        }
2939
2940        // ===== Misc =====
2941        Expression::Unnest(mut f) => {
2942            f.this = transform_recursive(f.this, transform_fn)?;
2943            f.expressions = f
2944                .expressions
2945                .into_iter()
2946                .map(|e| transform_recursive(e, transform_fn))
2947                .collect::<Result<Vec<_>>>()?;
2948            Expression::Unnest(f)
2949        }
2950        Expression::Explode(mut f) => {
2951            f.this = transform_recursive(f.this, transform_fn)?;
2952            Expression::Explode(f)
2953        }
2954        Expression::GroupConcat(mut f) => {
2955            f.this = transform_recursive(f.this, transform_fn)?;
2956            Expression::GroupConcat(f)
2957        }
2958        Expression::StringAgg(mut f) => {
2959            f.this = transform_recursive(f.this, transform_fn)?;
2960            if let Some(order_by) = f.order_by.take() {
2961                f.order_by = Some(
2962                    order_by
2963                        .into_iter()
2964                        .map(|mut ordered| {
2965                            let original = ordered.this.clone();
2966                            ordered.this =
2967                                transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2968                            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2969                                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2970                                Ok(_) | Err(_) => Ok(ordered),
2971                            }
2972                        })
2973                        .collect::<Result<Vec<_>>>()?,
2974                );
2975            }
2976            Expression::StringAgg(f)
2977        }
2978        Expression::ListAgg(mut f) => {
2979            f.this = transform_recursive(f.this, transform_fn)?;
2980            Expression::ListAgg(f)
2981        }
2982        Expression::ArrayAgg(mut f) => {
2983            f.this = transform_recursive(f.this, transform_fn)?;
2984            Expression::ArrayAgg(f)
2985        }
2986        Expression::ParseJson(mut f) => {
2987            f.this = transform_recursive(f.this, transform_fn)?;
2988            Expression::ParseJson(f)
2989        }
2990        Expression::ToJson(mut f) => {
2991            f.this = transform_recursive(f.this, transform_fn)?;
2992            Expression::ToJson(f)
2993        }
2994        Expression::JSONExtract(mut e) => {
2995            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2996            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2997            Expression::JSONExtract(e)
2998        }
2999        Expression::JSONExtractScalar(mut e) => {
3000            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3001            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
3002            Expression::JSONExtractScalar(e)
3003        }
3004
3005        // StrToTime: recurse into this
3006        Expression::StrToTime(mut e) => {
3007            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3008            Expression::StrToTime(e)
3009        }
3010
3011        // UnixToTime: recurse into this
3012        Expression::UnixToTime(mut e) => {
3013            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
3014            Expression::UnixToTime(e)
3015        }
3016
3017        // CreateTable: recurse into column defaults, on_update expressions, and data types
3018        Expression::CreateTable(mut ct) => {
3019            for col in &mut ct.columns {
3020                if let Some(default_expr) = col.default.take() {
3021                    col.default = Some(transform_recursive(default_expr, transform_fn)?);
3022                }
3023                if let Some(on_update_expr) = col.on_update.take() {
3024                    col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
3025                }
3026                // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
3027                // are NOT applied here because per-dialect transforms are designed for CAST/expression
3028                // contexts and may not produce correct results for DDL column definitions.
3029                // The DDL type mappings would need dedicated handling per source/target pair.
3030            }
3031            if let Some(as_select) = ct.as_select.take() {
3032                ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
3033            }
3034            Expression::CreateTable(ct)
3035        }
3036
3037        // CreateView: recurse into the view body query
3038        Expression::CreateView(mut cv) => {
3039            cv.query = transform_recursive(cv.query, transform_fn)?;
3040            Expression::CreateView(cv)
3041        }
3042
3043        // CreateTask: recurse into the task body
3044        Expression::CreateTask(mut ct) => {
3045            ct.body = transform_recursive(ct.body, transform_fn)?;
3046            Expression::CreateTask(ct)
3047        }
3048
3049        // Prepare: recurse into the prepared statement body
3050        Expression::Prepare(mut prepare) => {
3051            prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
3052            Expression::Prepare(prepare)
3053        }
3054
3055        // Execute: recurse into procedure/prepared name and argument values
3056        Expression::Execute(mut execute) => {
3057            execute.this = transform_recursive(execute.this, transform_fn)?;
3058            execute.arguments = execute
3059                .arguments
3060                .into_iter()
3061                .map(|argument| transform_recursive(argument, transform_fn))
3062                .collect::<Result<Vec<_>>>()?;
3063            execute.parameters = execute
3064                .parameters
3065                .into_iter()
3066                .map(|mut parameter| {
3067                    parameter.value = transform_recursive(parameter.value, transform_fn)?;
3068                    Ok(parameter)
3069                })
3070                .collect::<Result<Vec<_>>>()?;
3071            Expression::Execute(execute)
3072        }
3073
3074        // CreateProcedure: recurse into body expressions
3075        Expression::CreateProcedure(mut cp) => {
3076            if let Some(body) = cp.body.take() {
3077                cp.body = Some(match body {
3078                    FunctionBody::Expression(expr) => {
3079                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3080                    }
3081                    FunctionBody::Return(expr) => {
3082                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3083                    }
3084                    FunctionBody::Statements(stmts) => {
3085                        let transformed_stmts = stmts
3086                            .into_iter()
3087                            .map(|s| transform_recursive(s, transform_fn))
3088                            .collect::<Result<Vec<_>>>()?;
3089                        FunctionBody::Statements(transformed_stmts)
3090                    }
3091                    other => other,
3092                });
3093            }
3094            Expression::CreateProcedure(cp)
3095        }
3096
3097        // CreateFunction: recurse into body expressions
3098        Expression::CreateFunction(mut cf) => {
3099            if let Some(body) = cf.body.take() {
3100                cf.body = Some(match body {
3101                    FunctionBody::Expression(expr) => {
3102                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
3103                    }
3104                    FunctionBody::Return(expr) => {
3105                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
3106                    }
3107                    FunctionBody::Statements(stmts) => {
3108                        let transformed_stmts = stmts
3109                            .into_iter()
3110                            .map(|s| transform_recursive(s, transform_fn))
3111                            .collect::<Result<Vec<_>>>()?;
3112                        FunctionBody::Statements(transformed_stmts)
3113                    }
3114                    other => other,
3115                });
3116            }
3117            Expression::CreateFunction(cf)
3118        }
3119
3120        // MemberOf: recurse into left and right operands
3121        Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
3122        // ArrayContainsAll (@>): recurse into left and right operands
3123        Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
3124        // ArrayContainedBy (<@): recurse into left and right operands
3125        Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
3126        // ArrayOverlaps (&&): recurse into left and right operands
3127        Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
3128        // TsMatch (@@): recurse into left and right operands
3129        Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
3130        // Adjacent (-|-): recurse into left and right operands
3131        Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
3132
3133        // Table: recurse into when (HistoricalData) and changes fields
3134        Expression::Table(mut t) => {
3135            if let Some(when) = t.when.take() {
3136                let transformed =
3137                    transform_recursive(Expression::HistoricalData(when), transform_fn)?;
3138                if let Expression::HistoricalData(hd) = transformed {
3139                    t.when = Some(hd);
3140                }
3141            }
3142            if let Some(changes) = t.changes.take() {
3143                let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
3144                if let Expression::Changes(c) = transformed {
3145                    t.changes = Some(c);
3146                }
3147            }
3148            Expression::Table(t)
3149        }
3150
3151        // HistoricalData (Snowflake time travel): recurse into expression
3152        Expression::HistoricalData(mut hd) => {
3153            *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
3154            Expression::HistoricalData(hd)
3155        }
3156
3157        // Changes (Snowflake CHANGES clause): recurse into at_before and end
3158        Expression::Changes(mut c) => {
3159            if let Some(at_before) = c.at_before.take() {
3160                c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
3161            }
3162            if let Some(end) = c.end.take() {
3163                c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
3164            }
3165            Expression::Changes(c)
3166        }
3167
3168        // TableArgument: TABLE(expr) or MODEL(expr)
3169        Expression::TableArgument(mut ta) => {
3170            ta.this = transform_recursive(ta.this, transform_fn)?;
3171            Expression::TableArgument(ta)
3172        }
3173
3174        // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
3175        Expression::JoinedTable(mut jt) => {
3176            jt.left = transform_recursive(jt.left, transform_fn)?;
3177            jt.joins = jt
3178                .joins
3179                .into_iter()
3180                .map(|mut join| {
3181                    join.this = transform_recursive(join.this, transform_fn)?;
3182                    if let Some(on) = join.on.take() {
3183                        join.on = Some(transform_recursive(on, transform_fn)?);
3184                    }
3185                    match transform_fn(Expression::Join(Box::new(join)))? {
3186                        Expression::Join(j) => Ok(*j),
3187                        _ => Err(crate::error::Error::parse(
3188                            "Join transformation returned non-join expression",
3189                            0,
3190                            0,
3191                            0,
3192                            0,
3193                        )),
3194                    }
3195                })
3196                .collect::<Result<Vec<_>>>()?;
3197            jt.lateral_views = jt
3198                .lateral_views
3199                .into_iter()
3200                .map(|mut lv| {
3201                    lv.this = transform_recursive(lv.this, transform_fn)?;
3202                    Ok(lv)
3203                })
3204                .collect::<Result<Vec<_>>>()?;
3205            Expression::JoinedTable(jt)
3206        }
3207
3208        // Lateral: LATERAL func() - recurse into the function expression
3209        Expression::Lateral(mut lat) => {
3210            *lat.this = transform_recursive(*lat.this, transform_fn)?;
3211            Expression::Lateral(lat)
3212        }
3213
3214        // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
3215        // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
3216        // as a unit together with the WithinGroup wrapper
3217        Expression::WithinGroup(mut wg) => {
3218            wg.order_by = wg
3219                .order_by
3220                .into_iter()
3221                .map(|mut o| {
3222                    let original = o.this.clone();
3223                    o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
3224                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
3225                        Ok(Expression::Ordered(transformed)) => *transformed,
3226                        Ok(_) | Err(_) => o,
3227                    }
3228                })
3229                .collect();
3230            Expression::WithinGroup(wg)
3231        }
3232
3233        // Filter: recurse into both the aggregate and the filter condition
3234        Expression::Filter(mut f) => {
3235            f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
3236            f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
3237            Expression::Filter(f)
3238        }
3239
3240        // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
3241        // filter, order_by, having_max, and limit.
3242        // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
3243        Expression::Sum(f) => recurse_agg!(Sum, f),
3244        Expression::Avg(f) => recurse_agg!(Avg, f),
3245        Expression::Min(f) => recurse_agg!(Min, f),
3246        Expression::Max(f) => recurse_agg!(Max, f),
3247        Expression::CountIf(f) => recurse_agg!(CountIf, f),
3248        Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
3249        Expression::VarPop(f) => recurse_agg!(VarPop, f),
3250        Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
3251        Expression::Median(f) => recurse_agg!(Median, f),
3252        Expression::Mode(f) => recurse_agg!(Mode, f),
3253        Expression::First(f) => recurse_agg!(First, f),
3254        Expression::Last(f) => recurse_agg!(Last, f),
3255        Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
3256        Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
3257        Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
3258        Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
3259        Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
3260        Expression::Skewness(f) => recurse_agg!(Skewness, f),
3261        Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
3262        Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
3263        Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
3264        Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
3265        Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
3266        Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
3267
3268        // Count has its own struct with an Option<Expression> `this` field
3269        Expression::Count(mut c) => {
3270            if let Some(this) = c.this.take() {
3271                c.this = Some(transform_recursive(this, transform_fn)?);
3272            }
3273            if let Some(filter) = c.filter.take() {
3274                c.filter = Some(transform_recursive(filter, transform_fn)?);
3275            }
3276            Expression::Count(c)
3277        }
3278
3279        Expression::PipeOperator(mut pipe) => {
3280            pipe.this = transform_recursive(pipe.this, transform_fn)?;
3281            pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
3282            Expression::PipeOperator(pipe)
3283        }
3284
3285        // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
3286        Expression::ArrayExcept(mut f) => {
3287            f.this = transform_recursive(f.this, transform_fn)?;
3288            f.expression = transform_recursive(f.expression, transform_fn)?;
3289            Expression::ArrayExcept(f)
3290        }
3291        Expression::ArrayContains(mut f) => {
3292            f.this = transform_recursive(f.this, transform_fn)?;
3293            f.expression = transform_recursive(f.expression, transform_fn)?;
3294            Expression::ArrayContains(f)
3295        }
3296        Expression::ArrayDistinct(mut f) => {
3297            f.this = transform_recursive(f.this, transform_fn)?;
3298            Expression::ArrayDistinct(f)
3299        }
3300        Expression::ArrayPosition(mut f) => {
3301            f.this = transform_recursive(f.this, transform_fn)?;
3302            f.expression = transform_recursive(f.expression, transform_fn)?;
3303            Expression::ArrayPosition(f)
3304        }
3305
3306        // Pass through leaf nodes unchanged
3307        other => other,
3308    };
3309
3310    // Then apply the transform function
3311    transform_fn(expr)
3312}
3313
3314/// Returns the tokenizer config, generator config, and expression transform closure
3315/// for a built-in dialect type. This is the shared implementation used by both
3316/// `Dialect::get()` and custom dialect construction.
3317// ---------------------------------------------------------------------------
3318// Cached dialect configurations
3319// ---------------------------------------------------------------------------
3320
3321/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
3322/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
3323struct CachedDialectConfig {
3324    tokenizer_config: TokenizerConfig,
3325    #[cfg(feature = "generate")]
3326    generator_config: Arc<GeneratorConfig>,
3327}
3328
3329struct DialectConfigs {
3330    tokenizer_config: TokenizerConfig,
3331    #[cfg(feature = "generate")]
3332    generator_config: Arc<GeneratorConfig>,
3333    #[cfg(feature = "transpile")]
3334    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3335}
3336
3337/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
3338macro_rules! cached_dialect {
3339    ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
3340        #[cfg(feature = $feature)]
3341        static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3342            let d = $dialect_struct;
3343            CachedDialectConfig {
3344                tokenizer_config: d.tokenizer_config(),
3345                #[cfg(feature = "generate")]
3346                generator_config: Arc::new(d.generator_config()),
3347            }
3348        });
3349    };
3350}
3351
3352static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
3353    let d = GenericDialect;
3354    CachedDialectConfig {
3355        tokenizer_config: d.tokenizer_config(),
3356        #[cfg(feature = "generate")]
3357        generator_config: Arc::new(d.generator_config()),
3358    }
3359});
3360
3361cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
3362cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
3363cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
3364cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
3365cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
3366cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
3367cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
3368cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
3369cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
3370cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
3371cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
3372cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
3373cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
3374cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
3375cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
3376cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
3377cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
3378cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
3379cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
3380cached_dialect!(
3381    CACHED_MATERIALIZE,
3382    MaterializeDialect,
3383    "dialect-materialize"
3384);
3385cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
3386cached_dialect!(
3387    CACHED_SINGLESTORE,
3388    SingleStoreDialect,
3389    "dialect-singlestore"
3390);
3391cached_dialect!(
3392    CACHED_COCKROACHDB,
3393    CockroachDBDialect,
3394    "dialect-cockroachdb"
3395);
3396cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
3397cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
3398cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
3399cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
3400cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
3401cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
3402cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
3403cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
3404cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
3405cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
3406
3407fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
3408    /// Clone configs from a cached static and pair with a fresh transform closure.
3409    macro_rules! from_cache {
3410        ($cache:expr, $dialect_struct:expr) => {{
3411            let c = &*$cache;
3412            DialectConfigs {
3413                tokenizer_config: c.tokenizer_config.clone(),
3414                #[cfg(feature = "generate")]
3415                generator_config: c.generator_config.clone(),
3416                #[cfg(feature = "transpile")]
3417                transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
3418            }
3419        }};
3420    }
3421    match dt {
3422        #[cfg(feature = "dialect-postgresql")]
3423        DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
3424        #[cfg(feature = "dialect-mysql")]
3425        DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
3426        #[cfg(feature = "dialect-bigquery")]
3427        DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
3428        #[cfg(feature = "dialect-snowflake")]
3429        DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
3430        #[cfg(feature = "dialect-duckdb")]
3431        DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
3432        #[cfg(feature = "dialect-tsql")]
3433        DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
3434        #[cfg(feature = "dialect-oracle")]
3435        DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
3436        #[cfg(feature = "dialect-hive")]
3437        DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
3438        #[cfg(feature = "dialect-spark")]
3439        DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
3440        #[cfg(feature = "dialect-sqlite")]
3441        DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
3442        #[cfg(feature = "dialect-presto")]
3443        DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
3444        #[cfg(feature = "dialect-trino")]
3445        DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
3446        #[cfg(feature = "dialect-redshift")]
3447        DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
3448        #[cfg(feature = "dialect-clickhouse")]
3449        DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
3450        #[cfg(feature = "dialect-databricks")]
3451        DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
3452        #[cfg(feature = "dialect-athena")]
3453        DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
3454        #[cfg(feature = "dialect-teradata")]
3455        DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
3456        #[cfg(feature = "dialect-doris")]
3457        DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
3458        #[cfg(feature = "dialect-starrocks")]
3459        DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
3460        #[cfg(feature = "dialect-materialize")]
3461        DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
3462        #[cfg(feature = "dialect-risingwave")]
3463        DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
3464        #[cfg(feature = "dialect-singlestore")]
3465        DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
3466        #[cfg(feature = "dialect-cockroachdb")]
3467        DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
3468        #[cfg(feature = "dialect-tidb")]
3469        DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
3470        #[cfg(feature = "dialect-druid")]
3471        DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
3472        #[cfg(feature = "dialect-solr")]
3473        DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
3474        #[cfg(feature = "dialect-tableau")]
3475        DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
3476        #[cfg(feature = "dialect-dune")]
3477        DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
3478        #[cfg(feature = "dialect-fabric")]
3479        DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
3480        #[cfg(feature = "dialect-drill")]
3481        DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
3482        #[cfg(feature = "dialect-dremio")]
3483        DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
3484        #[cfg(feature = "dialect-exasol")]
3485        DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
3486        #[cfg(feature = "dialect-datafusion")]
3487        DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
3488        _ => from_cache!(CACHED_GENERIC, GenericDialect),
3489    }
3490}
3491
3492// ---------------------------------------------------------------------------
3493// Custom dialect registry
3494// ---------------------------------------------------------------------------
3495
3496static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
3497    LazyLock::new(|| RwLock::new(HashMap::new()));
3498
3499struct CustomDialectConfig {
3500    name: String,
3501    base_dialect: DialectType,
3502    tokenizer_config: TokenizerConfig,
3503    #[cfg(feature = "generate")]
3504    generator_config: GeneratorConfig,
3505    #[cfg(feature = "transpile")]
3506    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3507    #[cfg(feature = "transpile")]
3508    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3509}
3510
3511/// Fluent builder for creating and registering custom SQL dialects.
3512///
3513/// A custom dialect is based on an existing built-in dialect and allows selective
3514/// overrides of tokenizer configuration, generator configuration, and expression
3515/// transforms.
3516///
3517/// # Example
3518///
3519/// ```rust,ignore
3520/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
3521/// use polyglot_sql::generator::NormalizeFunctions;
3522///
3523/// CustomDialectBuilder::new("my_postgres")
3524///     .based_on(DialectType::PostgreSQL)
3525///     .generator_config_modifier(|gc| {
3526///         gc.normalize_functions = NormalizeFunctions::Lower;
3527///     })
3528///     .register()
3529///     .unwrap();
3530///
3531/// let d = Dialect::get_by_name("my_postgres").unwrap();
3532/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
3533/// let sql = d.generate(&exprs[0]).unwrap();
3534/// assert_eq!(sql, "select count(*)");
3535///
3536/// polyglot_sql::unregister_custom_dialect("my_postgres");
3537/// ```
3538pub struct CustomDialectBuilder {
3539    name: String,
3540    base_dialect: DialectType,
3541    tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
3542    #[cfg(feature = "generate")]
3543    generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
3544    #[cfg(feature = "transpile")]
3545    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3546    #[cfg(feature = "transpile")]
3547    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3548}
3549
3550impl CustomDialectBuilder {
3551    /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
3552    pub fn new(name: impl Into<String>) -> Self {
3553        Self {
3554            name: name.into(),
3555            base_dialect: DialectType::Generic,
3556            tokenizer_modifier: None,
3557            #[cfg(feature = "generate")]
3558            generator_modifier: None,
3559            #[cfg(feature = "transpile")]
3560            transform: None,
3561            #[cfg(feature = "transpile")]
3562            preprocess: None,
3563        }
3564    }
3565
3566    /// Set the base built-in dialect to inherit configuration from.
3567    pub fn based_on(mut self, dialect: DialectType) -> Self {
3568        self.base_dialect = dialect;
3569        self
3570    }
3571
3572    /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
3573    pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
3574    where
3575        F: FnOnce(&mut TokenizerConfig) + 'static,
3576    {
3577        self.tokenizer_modifier = Some(Box::new(f));
3578        self
3579    }
3580
3581    /// Provide a closure that modifies the generator configuration inherited from the base dialect.
3582    #[cfg(feature = "generate")]
3583    pub fn generator_config_modifier<F>(mut self, f: F) -> Self
3584    where
3585        F: FnOnce(&mut GeneratorConfig) + 'static,
3586    {
3587        self.generator_modifier = Some(Box::new(f));
3588        self
3589    }
3590
3591    /// Set a custom per-node expression transform function.
3592    ///
3593    /// This replaces the base dialect's transform. It is called on every expression
3594    /// node during the recursive transform pass.
3595    #[cfg(feature = "transpile")]
3596    pub fn transform_fn<F>(mut self, f: F) -> Self
3597    where
3598        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3599    {
3600        self.transform = Some(Arc::new(f));
3601        self
3602    }
3603
3604    /// Set a custom whole-tree preprocessing function.
3605    ///
3606    /// This replaces the base dialect's built-in preprocessing. It is called once
3607    /// on the entire expression tree before the recursive per-node transform.
3608    #[cfg(feature = "transpile")]
3609    pub fn preprocess_fn<F>(mut self, f: F) -> Self
3610    where
3611        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
3612    {
3613        self.preprocess = Some(Arc::new(f));
3614        self
3615    }
3616
3617    /// Build the custom dialect configuration and register it in the global registry.
3618    ///
3619    /// Returns an error if:
3620    /// - The name collides with a built-in dialect name
3621    /// - A custom dialect with the same name is already registered
3622    pub fn register(self) -> Result<()> {
3623        // Reject names that collide with built-in dialects
3624        if DialectType::from_str(&self.name).is_ok() {
3625            return Err(crate::error::Error::parse(
3626                format!(
3627                    "Cannot register custom dialect '{}': name collides with built-in dialect",
3628                    self.name
3629                ),
3630                0,
3631                0,
3632                0,
3633                0,
3634            ));
3635        }
3636
3637        // Get base configs
3638        let base_configs = configs_for_dialect_type(self.base_dialect);
3639        let mut tok_config = base_configs.tokenizer_config;
3640        #[cfg(feature = "generate")]
3641        let mut gen_config = (*base_configs.generator_config).clone();
3642
3643        // Apply modifiers
3644        if let Some(tok_mod) = self.tokenizer_modifier {
3645            tok_mod(&mut tok_config);
3646        }
3647        #[cfg(feature = "generate")]
3648        if let Some(gen_mod) = self.generator_modifier {
3649            gen_mod(&mut gen_config);
3650        }
3651
3652        let config = CustomDialectConfig {
3653            name: self.name.clone(),
3654            base_dialect: self.base_dialect,
3655            tokenizer_config: tok_config,
3656            #[cfg(feature = "generate")]
3657            generator_config: gen_config,
3658            #[cfg(feature = "transpile")]
3659            transform: self.transform,
3660            #[cfg(feature = "transpile")]
3661            preprocess: self.preprocess,
3662        };
3663
3664        register_custom_dialect(config)
3665    }
3666}
3667
3668use std::str::FromStr;
3669
3670fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
3671    let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
3672        crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
3673    })?;
3674
3675    if registry.contains_key(&config.name) {
3676        return Err(crate::error::Error::parse(
3677            format!("Custom dialect '{}' is already registered", config.name),
3678            0,
3679            0,
3680            0,
3681            0,
3682        ));
3683    }
3684
3685    registry.insert(config.name.clone(), Arc::new(config));
3686    Ok(())
3687}
3688
3689/// Remove a custom dialect from the global registry.
3690///
3691/// Returns `true` if a dialect with that name was found and removed,
3692/// `false` if no such custom dialect existed.
3693pub fn unregister_custom_dialect(name: &str) -> bool {
3694    if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
3695        registry.remove(name).is_some()
3696    } else {
3697        false
3698    }
3699}
3700
3701fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
3702    CUSTOM_DIALECT_REGISTRY
3703        .read()
3704        .ok()
3705        .and_then(|registry| registry.get(name).cloned())
3706}
3707
3708/// Main entry point for dialect-specific SQL operations.
3709///
3710/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
3711/// transformer for a specific SQL database engine. It is the high-level API through
3712/// which callers parse, generate, transform, and transpile SQL.
3713///
3714/// # Usage
3715///
3716/// ```rust,ignore
3717/// use polyglot_sql::dialects::{Dialect, DialectType};
3718///
3719/// // Parse PostgreSQL SQL into an AST
3720/// let pg = Dialect::get(DialectType::PostgreSQL);
3721/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
3722///
3723/// // Transpile from PostgreSQL to BigQuery
3724/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
3725/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
3726/// ```
3727///
3728/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
3729/// The struct is `Send + Sync` safe so it can be shared across threads.
3730pub struct Dialect {
3731    dialect_type: DialectType,
3732    tokenizer: Tokenizer,
3733    #[cfg(feature = "generate")]
3734    generator_config: Arc<GeneratorConfig>,
3735    #[cfg(feature = "transpile")]
3736    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3737    /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
3738    #[cfg(feature = "generate")]
3739    generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
3740    /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
3741    #[cfg(feature = "transpile")]
3742    custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
3743}
3744
3745/// Options for [`Dialect::transpile_with`].
3746///
3747/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
3748/// The struct is marked `#[non_exhaustive]` so new fields can be added without
3749/// breaking the API.
3750///
3751/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
3752/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
3753#[cfg(feature = "transpile")]
3754#[derive(Debug, Clone, Serialize, Deserialize)]
3755#[serde(rename_all = "camelCase", default)]
3756#[non_exhaustive]
3757pub struct TranspileOptions {
3758    /// Whether to pretty-print the output SQL.
3759    pub pretty: bool,
3760    /// How unsupported target-dialect constructs should be handled.
3761    ///
3762    /// The default is [`UnsupportedLevel::Warn`], which preserves the current
3763    /// compatibility behavior and continues transpilation.
3764    pub unsupported_level: UnsupportedLevel,
3765    /// Maximum number of unsupported diagnostics to include in raised errors.
3766    pub max_unsupported: usize,
3767    /// Complexity guard limits used while parsing, transforming, and generating.
3768    pub complexity_guard: ComplexityGuardOptions,
3769}
3770
3771#[cfg(feature = "transpile")]
3772impl Default for TranspileOptions {
3773    fn default() -> Self {
3774        Self {
3775            pretty: false,
3776            unsupported_level: UnsupportedLevel::Warn,
3777            max_unsupported: 3,
3778            complexity_guard: ComplexityGuardOptions::default(),
3779        }
3780    }
3781}
3782
3783#[cfg(feature = "transpile")]
3784impl TranspileOptions {
3785    /// Construct options with pretty-printing enabled.
3786    pub fn pretty() -> Self {
3787        Self {
3788            pretty: true,
3789            ..Default::default()
3790        }
3791    }
3792
3793    /// Construct options that raise when known unsupported constructs remain.
3794    pub fn strict() -> Self {
3795        Self {
3796            unsupported_level: UnsupportedLevel::Raise,
3797            ..Default::default()
3798        }
3799    }
3800
3801    /// Set how unsupported target-dialect constructs should be handled.
3802    pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3803        self.unsupported_level = level;
3804        self
3805    }
3806
3807    /// Set the maximum number of unsupported diagnostics to include in raised errors.
3808    pub fn with_max_unsupported(mut self, max: usize) -> Self {
3809        self.max_unsupported = max;
3810        self
3811    }
3812
3813    /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3814    pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3815        self.complexity_guard = guard;
3816        self
3817    }
3818}
3819
3820/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3821/// [`Dialect::transpile_with`].
3822///
3823/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3824/// dialect handle, including custom ones). End users do not normally need to
3825/// implement this trait themselves.
3826#[cfg(feature = "transpile")]
3827pub trait TranspileTarget {
3828    /// Invoke `f` with a reference to the resolved target dialect.
3829    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3830}
3831
3832#[cfg(feature = "transpile")]
3833impl TranspileTarget for DialectType {
3834    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3835        f(&Dialect::get(self))
3836    }
3837}
3838
3839#[cfg(feature = "transpile")]
3840impl TranspileTarget for &Dialect {
3841    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3842        f(self)
3843    }
3844}
3845
3846impl Dialect {
3847    /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3848    ///
3849    /// This is the primary constructor. It initializes the tokenizer, generator config,
3850    /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3851    /// For hybrid dialects like Athena, it also sets up expression-specific generator
3852    /// config routing.
3853    pub fn get(dialect_type: DialectType) -> Self {
3854        let configs = configs_for_dialect_type(dialect_type);
3855        let tokenizer_config = configs.tokenizer_config;
3856        #[cfg(feature = "generate")]
3857        let generator_config = configs.generator_config;
3858        #[cfg(feature = "transpile")]
3859        let transformer = configs.transformer;
3860
3861        // Set up expression-specific generator config for hybrid dialects
3862        #[cfg(feature = "generate")]
3863        let generator_config_for_expr: Option<
3864            Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3865        > = match dialect_type {
3866            #[cfg(feature = "dialect-athena")]
3867            DialectType::Athena => Some(Box::new(|expr| {
3868                AthenaDialect.generator_config_for_expr(expr)
3869            })),
3870            _ => None,
3871        };
3872
3873        Self {
3874            dialect_type,
3875            tokenizer: Tokenizer::new(tokenizer_config),
3876            #[cfg(feature = "generate")]
3877            generator_config,
3878            #[cfg(feature = "transpile")]
3879            transformer,
3880            #[cfg(feature = "generate")]
3881            generator_config_for_expr,
3882            #[cfg(feature = "transpile")]
3883            custom_preprocess: None,
3884        }
3885    }
3886
3887    /// Look up a dialect by string name.
3888    ///
3889    /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3890    /// falls back to the custom dialect registry. Returns `None` if no dialect
3891    /// with the given name exists.
3892    pub fn get_by_name(name: &str) -> Option<Self> {
3893        // Try built-in first
3894        if let Ok(dt) = DialectType::from_str(name) {
3895            return Some(Self::get(dt));
3896        }
3897
3898        // Try custom registry
3899        let config = get_custom_dialect_config(name)?;
3900        Some(Self::from_custom_config(&config))
3901    }
3902
3903    /// Construct a `Dialect` from a custom dialect configuration.
3904    fn from_custom_config(config: &CustomDialectConfig) -> Self {
3905        // Build the transformer: use custom if provided, else use base dialect's
3906        #[cfg(feature = "transpile")]
3907        let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3908            if let Some(ref custom_transform) = config.transform {
3909                let t = Arc::clone(custom_transform);
3910                Box::new(move |e| t(e))
3911            } else {
3912                configs_for_dialect_type(config.base_dialect).transformer
3913            };
3914
3915        // Build the custom preprocess: use custom if provided
3916        #[cfg(feature = "transpile")]
3917        let custom_preprocess: Option<
3918            Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3919        > = config.preprocess.as_ref().map(|p| {
3920            let p = Arc::clone(p);
3921            Box::new(move |e: Expression| p(e))
3922                as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3923        });
3924
3925        Self {
3926            dialect_type: config.base_dialect,
3927            tokenizer: Tokenizer::new(config.tokenizer_config.clone()),
3928            #[cfg(feature = "generate")]
3929            generator_config: Arc::new(config.generator_config.clone()),
3930            #[cfg(feature = "transpile")]
3931            transformer,
3932            #[cfg(feature = "generate")]
3933            generator_config_for_expr: None,
3934            #[cfg(feature = "transpile")]
3935            custom_preprocess,
3936        }
3937    }
3938
3939    /// Get the dialect type
3940    pub fn dialect_type(&self) -> DialectType {
3941        self.dialect_type
3942    }
3943
3944    /// Get the generator configuration
3945    #[cfg(feature = "generate")]
3946    pub fn generator_config(&self) -> &GeneratorConfig {
3947        &self.generator_config
3948    }
3949
3950    /// Parses a SQL string into a list of [`Expression`] AST nodes.
3951    ///
3952    /// The input may contain multiple semicolon-separated statements; each one
3953    /// produces a separate element in the returned vector. Tokenization uses
3954    /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3955    pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3956        self.parse_with_guard(sql, self.default_complexity_guard())
3957    }
3958
3959    fn parse_with_guard(
3960        &self,
3961        sql: &str,
3962        complexity_guard: ComplexityGuardOptions,
3963    ) -> Result<Vec<Expression>> {
3964        enforce_input(sql, &complexity_guard)?;
3965        let tokens = self.tokenizer.tokenize(sql)?;
3966        let config = crate::parser::ParserConfig {
3967            dialect: Some(self.dialect_type),
3968            complexity_guard,
3969            ..Default::default()
3970        };
3971        let mut parser = Parser::with_source(tokens, config, sql.to_string());
3972        parser.parse()
3973    }
3974
3975    fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3976        let mut guard = ComplexityGuardOptions::default();
3977        if matches!(self.dialect_type, DialectType::ClickHouse) {
3978            guard.max_ast_depth = Some(4_096);
3979            guard.max_function_call_depth = Some(512);
3980        }
3981        guard
3982    }
3983
3984    #[cfg(feature = "transpile")]
3985    fn default_transpile_complexity_guard(
3986        &self,
3987        target_dialect: &Dialect,
3988        guard: ComplexityGuardOptions,
3989    ) -> ComplexityGuardOptions {
3990        if guard != ComplexityGuardOptions::default() {
3991            return guard;
3992        }
3993
3994        if matches!(self.dialect_type, DialectType::ClickHouse)
3995            || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3996        {
3997            let mut guard = guard;
3998            guard.max_ast_depth = Some(4_096);
3999            guard.max_function_call_depth = Some(512);
4000            guard
4001        } else {
4002            guard
4003        }
4004    }
4005
4006    /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
4007    ///
4008    /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
4009    /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
4010    pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
4011        let complexity_guard = self.default_complexity_guard();
4012        enforce_input(sql, &complexity_guard)?;
4013        let tokens = self.tokenizer.tokenize(sql)?;
4014        let config = crate::parser::ParserConfig {
4015            dialect: Some(self.dialect_type),
4016            complexity_guard,
4017            ..Default::default()
4018        };
4019        let mut parser = Parser::with_source(tokens, config, sql.to_string());
4020        parser.parse_standalone_data_type()
4021    }
4022
4023    /// Tokenize SQL using this dialect's tokenizer configuration.
4024    pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
4025        self.tokenizer.tokenize(sql)
4026    }
4027
4028    /// Get the generator config for a specific expression (supports hybrid dialects).
4029    /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
4030    #[cfg(feature = "generate")]
4031    fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
4032        if let Some(ref config_fn) = self.generator_config_for_expr {
4033            config_fn(expr)
4034        } else {
4035            (*self.generator_config).clone()
4036        }
4037    }
4038
4039    /// Generates a SQL string from an [`Expression`] AST node.
4040    ///
4041    /// The output uses this dialect's generator configuration for identifier quoting,
4042    /// keyword casing, function name normalization, and syntax style. The result is
4043    /// a single-line (non-pretty) SQL string.
4044    #[cfg(feature = "generate")]
4045    pub fn generate(&self, expr: &Expression) -> Result<String> {
4046        // Fast path: when no per-expression config override, share the Arc cheaply.
4047        if self.generator_config_for_expr.is_none() {
4048            let mut generator = Generator::with_arc_config(self.generator_config.clone());
4049            return generator.generate(expr);
4050        }
4051        let config = self.get_config_for_expr(expr);
4052        let mut generator = Generator::with_config(config);
4053        generator.generate(expr)
4054    }
4055
4056    /// Generate SQL from an expression with pretty printing enabled
4057    #[cfg(feature = "generate")]
4058    pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
4059        let mut config = self.get_config_for_expr(expr);
4060        config.pretty = true;
4061        let mut generator = Generator::with_config(config);
4062        generator.generate(expr)
4063    }
4064
4065    /// Generate SQL from an expression with source dialect info (for transpilation)
4066    #[cfg(feature = "generate")]
4067    pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
4068        let mut config = self.get_config_for_expr(expr);
4069        config.source_dialect = Some(source);
4070        let mut generator = Generator::with_config(config);
4071        generator.generate(expr)
4072    }
4073
4074    /// Generate SQL from an expression with pretty printing and source dialect info
4075    #[cfg(feature = "generate")]
4076    pub fn generate_pretty_with_source(
4077        &self,
4078        expr: &Expression,
4079        source: DialectType,
4080    ) -> Result<String> {
4081        let mut config = self.get_config_for_expr(expr);
4082        config.pretty = true;
4083        config.source_dialect = Some(source);
4084        let mut generator = Generator::with_config(config);
4085        generator.generate(expr)
4086    }
4087
4088    /// Generate SQL from an expression with source dialect and transpile options.
4089    #[cfg(all(feature = "generate", feature = "transpile"))]
4090    fn generate_with_transpile_options(
4091        &self,
4092        expr: &Expression,
4093        source: DialectType,
4094        opts: &TranspileOptions,
4095    ) -> Result<String> {
4096        let mut config = self.get_config_for_expr(expr);
4097        config.source_dialect = Some(source);
4098        config.pretty = opts.pretty;
4099        config.unsupported_level = opts.unsupported_level;
4100        config.max_unsupported = opts.max_unsupported.max(1);
4101        config.complexity_guard = opts.complexity_guard;
4102        let mut generator = Generator::with_config(config);
4103        generator.generate(expr)
4104    }
4105
4106    /// Generate SQL from an expression with forced identifier quoting (identify=True)
4107    #[cfg(feature = "generate")]
4108    pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
4109        let mut config = self.get_config_for_expr(expr);
4110        config.always_quote_identifiers = true;
4111        let mut generator = Generator::with_config(config);
4112        generator.generate(expr)
4113    }
4114
4115    /// Generate SQL from an expression with pretty printing and forced identifier quoting
4116    #[cfg(feature = "generate")]
4117    pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
4118        let mut config = (*self.generator_config).clone();
4119        config.pretty = true;
4120        config.always_quote_identifiers = true;
4121        let mut generator = Generator::with_config(config);
4122        generator.generate(expr)
4123    }
4124
4125    /// Generate SQL from an expression with caller-specified config overrides
4126    #[cfg(feature = "generate")]
4127    pub fn generate_with_overrides(
4128        &self,
4129        expr: &Expression,
4130        overrides: impl FnOnce(&mut GeneratorConfig),
4131    ) -> Result<String> {
4132        let mut config = self.get_config_for_expr(expr);
4133        overrides(&mut config);
4134        let mut generator = Generator::with_config(config);
4135        generator.generate(expr)
4136    }
4137
4138    /// Transforms an expression tree to conform to this dialect's syntax and semantics.
4139    ///
4140    /// The transformation proceeds in two phases:
4141    /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
4142    ///    ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
4143    /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
4144    ///    that applies this dialect's [`DialectImpl::transform_expr`] to every node.
4145    ///
4146    /// This method is used both during transpilation (to rewrite an AST for a target dialect)
4147    /// and for identity transforms (normalizing SQL within the same dialect).
4148    #[cfg(feature = "transpile")]
4149    pub fn transform(&self, expr: Expression) -> Result<Expression> {
4150        self.transform_with_guard(expr, self.default_complexity_guard())
4151    }
4152
4153    #[cfg(feature = "transpile")]
4154    fn transform_with_guard(
4155        &self,
4156        expr: Expression,
4157        complexity_guard: ComplexityGuardOptions,
4158    ) -> Result<Expression> {
4159        enforce_generate_ast(&expr, &complexity_guard)?;
4160        // Apply preprocessing transforms based on dialect
4161        let preprocessed = self.preprocess(expr)?;
4162        // Then apply recursive transformation
4163        transform_recursive(preprocessed, &self.transformer)
4164    }
4165
4166    /// Apply dialect-specific preprocessing transforms
4167    #[cfg(feature = "transpile")]
4168    fn preprocess(&self, expr: Expression) -> Result<Expression> {
4169        // If a custom preprocess function is set, use it instead of the built-in logic
4170        if let Some(ref custom_preprocess) = self.custom_preprocess {
4171            return custom_preprocess(expr);
4172        }
4173
4174        #[cfg(any(
4175            feature = "dialect-mysql",
4176            feature = "dialect-postgresql",
4177            feature = "dialect-bigquery",
4178            feature = "dialect-snowflake",
4179            feature = "dialect-tsql",
4180            feature = "dialect-spark",
4181            feature = "dialect-databricks",
4182            feature = "dialect-hive",
4183            feature = "dialect-sqlite",
4184            feature = "dialect-trino",
4185            feature = "dialect-presto",
4186            feature = "dialect-duckdb",
4187            feature = "dialect-redshift",
4188            feature = "dialect-starrocks",
4189            feature = "dialect-oracle",
4190            feature = "dialect-clickhouse",
4191            feature = "dialect-fabric",
4192        ))]
4193        use crate::transforms;
4194
4195        match self.dialect_type {
4196            // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
4197            // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
4198            #[cfg(feature = "dialect-mysql")]
4199            DialectType::MySQL => {
4200                let expr = transforms::eliminate_qualify(expr)?;
4201                let expr = transforms::eliminate_full_outer_join(expr)?;
4202                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4203                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4204                Ok(expr)
4205            }
4206            // PostgreSQL doesn't support QUALIFY
4207            // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
4208            // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
4209            #[cfg(feature = "dialect-postgresql")]
4210            DialectType::PostgreSQL => {
4211                let expr = transforms::eliminate_qualify(expr)?;
4212                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4213                let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
4214                // Normalize SET ... TO to SET ... = in CREATE FUNCTION
4215                // Only normalize when sqlglot would fully parse (no body) —
4216                // sqlglot falls back to Command for complex function bodies,
4217                // preserving the original text including TO.
4218                let expr = if let Expression::CreateFunction(mut cf) = expr {
4219                    if cf.body.is_none() {
4220                        for opt in &mut cf.set_options {
4221                            if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
4222                                &mut opt.value
4223                            {
4224                                *use_to = false;
4225                            }
4226                        }
4227                    }
4228                    Expression::CreateFunction(cf)
4229                } else {
4230                    expr
4231                };
4232                Ok(expr)
4233            }
4234            // BigQuery doesn't support DISTINCT ON or CTE column aliases
4235            #[cfg(feature = "dialect-bigquery")]
4236            DialectType::BigQuery => {
4237                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4238                let expr = transforms::pushdown_cte_column_names(expr)?;
4239                let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
4240                Ok(expr)
4241            }
4242            // Snowflake
4243            #[cfg(feature = "dialect-snowflake")]
4244            DialectType::Snowflake => {
4245                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4246                let expr = transforms::eliminate_window_clause(expr)?;
4247                let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
4248                Ok(expr)
4249            }
4250            // TSQL doesn't support QUALIFY
4251            // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
4252            // TSQL doesn't support CTEs in subqueries (hoist to top level)
4253            // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
4254            // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
4255            #[cfg(feature = "dialect-tsql")]
4256            DialectType::TSQL => {
4257                let expr = transforms::eliminate_qualify(expr)?;
4258                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
4259                let expr = transforms::ensure_bools(expr)?;
4260                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4261                let expr = transforms::strip_cte_materialization(expr)?;
4262                let expr = transforms::move_ctes_to_top_level(expr)?;
4263                let expr = transforms::qualify_derived_table_outputs(expr)?;
4264                Ok(expr)
4265            }
4266            // Fabric shares T-SQL predicate rules and CTE placement restrictions,
4267            // but keeps Fabric-specific APPLY and derived-table behavior separate.
4268            #[cfg(feature = "dialect-fabric")]
4269            DialectType::Fabric => {
4270                let expr = transforms::ensure_bools(expr)?;
4271                let expr = transforms::strip_cte_materialization(expr)?;
4272                let expr = transforms::move_ctes_to_top_level(expr)?;
4273                Ok(expr)
4274            }
4275            // Spark doesn't support QUALIFY (but Databricks does)
4276            // Spark doesn't support CTEs in subqueries (hoist to top level)
4277            #[cfg(feature = "dialect-spark")]
4278            DialectType::Spark => {
4279                let expr = transforms::eliminate_qualify(expr)?;
4280                let expr = transforms::add_auto_table_alias(expr)?;
4281                let expr = transforms::simplify_nested_paren_values(expr)?;
4282                let expr = transforms::move_ctes_to_top_level(expr)?;
4283                Ok(expr)
4284            }
4285            // Databricks supports QUALIFY natively
4286            // Databricks doesn't support CTEs in subqueries (hoist to top level)
4287            #[cfg(feature = "dialect-databricks")]
4288            DialectType::Databricks => {
4289                let expr = transforms::add_auto_table_alias(expr)?;
4290                let expr = transforms::simplify_nested_paren_values(expr)?;
4291                let expr = transforms::move_ctes_to_top_level(expr)?;
4292                Ok(expr)
4293            }
4294            // Hive doesn't support QUALIFY or CTEs in subqueries
4295            #[cfg(feature = "dialect-hive")]
4296            DialectType::Hive => {
4297                let expr = transforms::eliminate_qualify(expr)?;
4298                let expr = transforms::move_ctes_to_top_level(expr)?;
4299                Ok(expr)
4300            }
4301            // SQLite doesn't support QUALIFY
4302            #[cfg(feature = "dialect-sqlite")]
4303            DialectType::SQLite => {
4304                let expr = transforms::eliminate_qualify(expr)?;
4305                Ok(expr)
4306            }
4307            // Trino doesn't support QUALIFY
4308            #[cfg(feature = "dialect-trino")]
4309            DialectType::Trino => {
4310                let expr = transforms::eliminate_qualify(expr)?;
4311                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
4312                Ok(expr)
4313            }
4314            // Presto doesn't support QUALIFY or WINDOW clause
4315            #[cfg(feature = "dialect-presto")]
4316            DialectType::Presto => {
4317                let expr = transforms::eliminate_qualify(expr)?;
4318                let expr = transforms::eliminate_window_clause(expr)?;
4319                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
4320                Ok(expr)
4321            }
4322            // DuckDB supports QUALIFY - no elimination needed
4323            // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
4324            // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
4325            #[cfg(feature = "dialect-duckdb")]
4326            DialectType::DuckDB => {
4327                let expr = transforms::expand_posexplode_duckdb(expr)?;
4328                let expr = transforms::expand_like_any(expr)?;
4329                Ok(expr)
4330            }
4331            // Redshift doesn't support QUALIFY, WINDOW clause, or GENERATE_DATE_ARRAY
4332            #[cfg(feature = "dialect-redshift")]
4333            DialectType::Redshift => {
4334                let expr = transforms::eliminate_qualify(expr)?;
4335                let expr = transforms::eliminate_window_clause(expr)?;
4336                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4337                Ok(expr)
4338            }
4339            // StarRocks doesn't support BETWEEN in DELETE statements or QUALIFY
4340            #[cfg(feature = "dialect-starrocks")]
4341            DialectType::StarRocks => {
4342                let expr = transforms::eliminate_qualify(expr)?;
4343                let expr = transforms::expand_between_in_delete(expr)?;
4344                let expr = transforms::eliminate_distinct_on_for_dialect(
4345                    expr,
4346                    Some(DialectType::StarRocks),
4347                    Some(DialectType::StarRocks),
4348                )?;
4349                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
4350                Ok(expr)
4351            }
4352            // DataFusion supports QUALIFY and semi/anti joins natively
4353            #[cfg(feature = "dialect-datafusion")]
4354            DialectType::DataFusion => Ok(expr),
4355            // Oracle doesn't support QUALIFY
4356            #[cfg(feature = "dialect-oracle")]
4357            DialectType::Oracle => {
4358                let expr = transforms::eliminate_qualify(expr)?;
4359                Ok(expr)
4360            }
4361            // Drill - no special preprocessing needed
4362            #[cfg(feature = "dialect-drill")]
4363            DialectType::Drill => Ok(expr),
4364            // Teradata - no special preprocessing needed
4365            #[cfg(feature = "dialect-teradata")]
4366            DialectType::Teradata => Ok(expr),
4367            // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
4368            #[cfg(feature = "dialect-clickhouse")]
4369            DialectType::ClickHouse => {
4370                let expr = transforms::no_limit_order_by_union(expr)?;
4371                Ok(expr)
4372            }
4373            // Other dialects - no preprocessing
4374            _ => Ok(expr),
4375        }
4376    }
4377
4378    /// Transpile SQL from this dialect to the given target dialect.
4379    ///
4380    /// The target may be specified as either a built-in [`DialectType`] enum variant
4381    /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
4382    ///
4383    /// ```rust,ignore
4384    /// let pg = Dialect::get(DialectType::PostgreSQL);
4385    /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?;   // enum
4386    /// pg.transpile("SELECT NOW()", &custom_dialect)?;         // handle
4387    /// ```
4388    ///
4389    /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
4390    #[cfg(feature = "transpile")]
4391    pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
4392        self.transpile_with(sql, target, TranspileOptions::default())
4393    }
4394
4395    /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
4396    #[cfg(feature = "transpile")]
4397    pub fn transpile_with<T: TranspileTarget>(
4398        &self,
4399        sql: &str,
4400        target: T,
4401        opts: TranspileOptions,
4402    ) -> Result<Vec<String>> {
4403        target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
4404    }
4405
4406    #[cfg(feature = "transpile")]
4407    fn transpile_inner(
4408        &self,
4409        sql: &str,
4410        target_dialect: &Dialect,
4411        opts: &TranspileOptions,
4412    ) -> Result<Vec<String>> {
4413        let mut effective_opts = opts.clone();
4414        effective_opts.complexity_guard =
4415            self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
4416        let opts = &effective_opts;
4417        let target = target_dialect.dialect_type;
4418        if matches!(self.dialect_type, DialectType::PostgreSQL)
4419            && matches!(target, DialectType::SQLite)
4420        {
4421            self.reject_pgvector_distance_operators_for_sqlite(sql)?;
4422        }
4423        let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
4424        let generic_identity =
4425            self.dialect_type == DialectType::Generic && target == DialectType::Generic;
4426
4427        if generic_identity {
4428            return expressions
4429                .into_iter()
4430                .map(|expr| {
4431                    Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
4432                    target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
4433                })
4434                .collect();
4435        }
4436
4437        expressions
4438            .into_iter()
4439            .map(|expr| {
4440                // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
4441                // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
4442                // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
4443                let expr = if matches!(self.dialect_type, DialectType::DuckDB) {
4444                    use crate::expressions::DataType as DT;
4445                    transform_recursive(expr, &|e| match e {
4446                        Expression::DataType(DT::VarChar { .. }) => {
4447                            Ok(Expression::DataType(DT::Text))
4448                        }
4449                        Expression::DataType(DT::Char { .. }) => Ok(Expression::DataType(DT::Text)),
4450                        _ => Ok(e),
4451                    })?
4452                } else {
4453                    expr
4454                };
4455
4456                Self::reject_postgres_tsql_strict_regex_predicates(
4457                    &expr,
4458                    self.dialect_type,
4459                    target,
4460                    opts,
4461                )?;
4462
4463                // When source and target differ, first normalize the source dialect's
4464                // AST constructs to standard SQL, so that the target dialect can handle them.
4465                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
4466                let normalized =
4467                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
4468                        self.transform_with_guard(expr, opts.complexity_guard)?
4469                    } else {
4470                        expr
4471                    };
4472
4473                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
4474                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
4475                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
4476                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
4477                let normalized =
4478                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4479                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
4480                    {
4481                        transform_recursive(normalized, &|e| {
4482                            if let Expression::Function(ref f) = e {
4483                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
4484                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
4485                                    if let (
4486                                        Expression::Function(ref jq),
4487                                        Expression::Function(ref jv),
4488                                    ) = (&f.args[0], &f.args[1])
4489                                    {
4490                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
4491                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
4492                                        {
4493                                            // Unwrap: return just JSON_QUERY(...)
4494                                            return Ok(f.args[0].clone());
4495                                        }
4496                                    }
4497                                }
4498                            }
4499                            Ok(e)
4500                        })?
4501                    } else {
4502                        normalized
4503                    };
4504
4505                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME
4506                // Snowflake's CURRENT_TIME is equivalent to LOCALTIME in other dialects.
4507                // Python sqlglot parses Snowflake's CURRENT_TIME as Localtime expression.
4508                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4509                    && !matches!(target, DialectType::Snowflake)
4510                {
4511                    transform_recursive(normalized, &|e| {
4512                        if let Expression::Function(ref f) = e {
4513                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") {
4514                                return Ok(Expression::Localtime(Box::new(
4515                                    crate::expressions::Localtime { this: None },
4516                                )));
4517                            }
4518                        }
4519                        Ok(e)
4520                    })?
4521                } else {
4522                    normalized
4523                };
4524
4525                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
4526                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
4527                // transform. DuckDB requires the count argument to be BIGINT.
4528                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4529                    && matches!(target, DialectType::DuckDB)
4530                {
4531                    transform_recursive(normalized, &|e| {
4532                        if let Expression::Function(ref f) = e {
4533                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
4534                                // Check if first arg is space string literal
4535                                if let Expression::Literal(ref lit) = f.args[0] {
4536                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
4537                                    {
4538                                        if s == " " {
4539                                            // Wrap second arg in CAST(... AS BIGINT) if not already
4540                                            if !matches!(f.args[1], Expression::Cast(_)) {
4541                                                let mut new_args = f.args.clone();
4542                                                new_args[1] = Expression::Cast(Box::new(
4543                                                    crate::expressions::Cast {
4544                                                        this: new_args[1].clone(),
4545                                                        to: crate::expressions::DataType::BigInt {
4546                                                            length: None,
4547                                                        },
4548                                                        trailing_comments: Vec::new(),
4549                                                        double_colon_syntax: false,
4550                                                        format: None,
4551                                                        default: None,
4552                                                        inferred_type: None,
4553                                                    },
4554                                                ));
4555                                                return Ok(Expression::Function(Box::new(
4556                                                    crate::expressions::Function {
4557                                                        name: f.name.clone(),
4558                                                        args: new_args,
4559                                                        distinct: f.distinct,
4560                                                        trailing_comments: f
4561                                                            .trailing_comments
4562                                                            .clone(),
4563                                                        use_bracket_syntax: f.use_bracket_syntax,
4564                                                        no_parens: f.no_parens,
4565                                                        quoted: f.quoted,
4566                                                        span: None,
4567                                                        inferred_type: None,
4568                                                    },
4569                                                )));
4570                                            }
4571                                        }
4572                                    }
4573                                }
4574                            }
4575                        }
4576                        Ok(e)
4577                    })?
4578                } else {
4579                    normalized
4580                };
4581
4582                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
4583                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
4584                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4585                    && !matches!(target, DialectType::BigQuery)
4586                {
4587                    crate::transforms::propagate_struct_field_names(normalized)?
4588                } else {
4589                    normalized
4590                };
4591
4592                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
4593                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
4594                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
4595                // functions handle their generator args differently (as float seeds).
4596                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
4597                    && matches!(target, DialectType::DuckDB)
4598                {
4599                    fn make_scaled_random() -> Expression {
4600                        let lower =
4601                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
4602                                "-9.223372036854776E+18".to_string(),
4603                            )));
4604                        let upper =
4605                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
4606                                "9.223372036854776e+18".to_string(),
4607                            )));
4608                        let random_call = Expression::Random(crate::expressions::Random);
4609                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
4610                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
4611                                left: upper,
4612                                right: lower.clone(),
4613                                left_comments: vec![],
4614                                operator_comments: vec![],
4615                                trailing_comments: vec![],
4616                                inferred_type: None,
4617                            })),
4618                            trailing_comments: vec![],
4619                        }));
4620                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
4621                            left: random_call,
4622                            right: range_size,
4623                            left_comments: vec![],
4624                            operator_comments: vec![],
4625                            trailing_comments: vec![],
4626                            inferred_type: None,
4627                        }));
4628                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
4629                            left: lower,
4630                            right: scaled,
4631                            left_comments: vec![],
4632                            operator_comments: vec![],
4633                            trailing_comments: vec![],
4634                            inferred_type: None,
4635                        }));
4636                        Expression::Cast(Box::new(crate::expressions::Cast {
4637                            this: shifted,
4638                            to: crate::expressions::DataType::BigInt { length: None },
4639                            trailing_comments: vec![],
4640                            double_colon_syntax: false,
4641                            format: None,
4642                            default: None,
4643                            inferred_type: None,
4644                        }))
4645                    }
4646
4647                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
4648                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
4649                    // This prevents transform_recursive (which is bottom-up) from expanding
4650                    // seeded RANDOM into make_scaled_random() and losing the seed value.
4651                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4652                    // and then un-expanded back to Expression::Random by the code below.
4653                    let normalized = transform_recursive(normalized, &|e| {
4654                        if let Expression::Function(ref f) = e {
4655                            let n = f.name.to_ascii_uppercase();
4656                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4657                                if let Expression::Function(mut f) = e {
4658                                    for arg in f.args.iter_mut() {
4659                                        if let Expression::Rand(ref r) = arg {
4660                                            if r.lower.is_none() && r.upper.is_none() {
4661                                                if let Some(ref seed) = r.seed {
4662                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4663                                                    // so it won't be expanded by the RANDOM expansion below
4664                                                    *arg = Expression::Function(Box::new(
4665                                                        crate::expressions::Function::new(
4666                                                            "RANDOM".to_string(),
4667                                                            vec![*seed.clone()],
4668                                                        ),
4669                                                    ));
4670                                                }
4671                                            }
4672                                        }
4673                                    }
4674                                    return Ok(Expression::Function(f));
4675                                }
4676                            }
4677                        }
4678                        Ok(e)
4679                    })?;
4680
4681                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4682                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4683                    // we see the parent. We detect this and undo the expansion by replacing
4684                    // the expanded pattern back with Expression::Random.
4685                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4686                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4687                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4688                    transform_recursive(normalized, &|e| {
4689                        if let Expression::Function(ref f) = e {
4690                            let n = f.name.to_ascii_uppercase();
4691                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4692                                if let Expression::Function(mut f) = e {
4693                                    for arg in f.args.iter_mut() {
4694                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4695                                        if let Expression::Cast(ref cast) = arg {
4696                                            if matches!(
4697                                                cast.to,
4698                                                crate::expressions::DataType::BigInt { .. }
4699                                            ) {
4700                                                if let Expression::Add(ref add) = cast.this {
4701                                                    if let Expression::Literal(ref lit) = add.left {
4702                                                        if let crate::expressions::Literal::Number(
4703                                                            ref num,
4704                                                        ) = lit.as_ref()
4705                                                        {
4706                                                            if num == "-9.223372036854776E+18" {
4707                                                                *arg = Expression::Random(
4708                                                                    crate::expressions::Random,
4709                                                                );
4710                                                            }
4711                                                        }
4712                                                    }
4713                                                }
4714                                            }
4715                                        }
4716                                    }
4717                                    return Ok(Expression::Function(f));
4718                                }
4719                                return Ok(e);
4720                            }
4721                        }
4722                        match e {
4723                            Expression::Random(_) => Ok(make_scaled_random()),
4724                            // Rand(seed) with no bounds: drop seed and expand
4725                            // (DuckDB RANDOM doesn't support seeds)
4726                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4727                                Ok(make_scaled_random())
4728                            }
4729                            _ => Ok(e),
4730                        }
4731                    })?
4732                } else {
4733                    normalized
4734                };
4735
4736                // Apply cross-dialect semantic normalizations
4737                let normalized =
4738                    Self::cross_dialect_normalize(normalized, self.dialect_type, target)?;
4739
4740                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4741                    Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
4742                } else {
4743                    normalized
4744                };
4745
4746                let normalized =
4747                    if matches!(
4748                        self.dialect_type,
4749                        DialectType::PostgreSQL | DialectType::CockroachDB
4750                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4751                    {
4752                        Self::normalize_postgres_type_function_casts(normalized)?
4753                    } else {
4754                        normalized
4755                    };
4756
4757                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4758                    && !matches!(target, DialectType::SQLite)
4759                {
4760                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
4761                } else {
4762                    normalized
4763                };
4764
4765                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4766                    && matches!(target, DialectType::SQLite)
4767                {
4768                    Self::normalize_postgres_to_sqlite_types(normalized)?
4769                } else {
4770                    normalized
4771                };
4772
4773                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4774                    && matches!(target, DialectType::Fabric)
4775                {
4776                    Self::normalize_postgres_to_fabric_types(normalized)?
4777                } else {
4778                    normalized
4779                };
4780
4781                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4782                // (SELECT UNNEST(..., max_depth => 2)) subquery
4783                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4784                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4785                    && matches!(target, DialectType::DuckDB)
4786                {
4787                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4788                } else {
4789                    normalized
4790                };
4791
4792                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4793                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4794                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4795                    && matches!(
4796                        target,
4797                        DialectType::DuckDB
4798                            | DialectType::Presto
4799                            | DialectType::Trino
4800                            | DialectType::Athena
4801                            | DialectType::Spark
4802                            | DialectType::Databricks
4803                    ) {
4804                    crate::transforms::unnest_alias_to_column_alias(normalized)?
4805                } else if matches!(self.dialect_type, DialectType::BigQuery)
4806                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4807                {
4808                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4809                    // but don't convert alias format (no _t0 wrapper)
4810                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4811                    // For Redshift: strip UNNEST when arg is a column reference path
4812                    if matches!(target, DialectType::Redshift) {
4813                        crate::transforms::strip_unnest_column_refs(result)?
4814                    } else {
4815                        result
4816                    }
4817                } else {
4818                    normalized
4819                };
4820
4821                // For Presto/Trino targets from PostgreSQL/Redshift source:
4822                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4823                let normalized = if matches!(
4824                    self.dialect_type,
4825                    DialectType::PostgreSQL | DialectType::Redshift
4826                ) && matches!(
4827                    target,
4828                    DialectType::Presto | DialectType::Trino | DialectType::Athena
4829                ) {
4830                    crate::transforms::wrap_unnest_join_aliases(normalized)?
4831                } else {
4832                    normalized
4833                };
4834
4835                // Eliminate DISTINCT ON with target-dialect awareness
4836                // This must happen after source transform (which may produce DISTINCT ON)
4837                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4838                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4839                    normalized,
4840                    Some(target),
4841                    Some(self.dialect_type),
4842                )?;
4843
4844                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4845                let normalized = if matches!(target, DialectType::Snowflake) {
4846                    Self::transform_generate_date_array_snowflake(normalized)?
4847                } else {
4848                    normalized
4849                };
4850
4851                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4852                let normalized = if matches!(
4853                    target,
4854                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
4855                ) {
4856                    crate::transforms::unnest_to_explode_select(normalized)?
4857                } else {
4858                    normalized
4859                };
4860
4861                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4862                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4863                    crate::transforms::no_limit_order_by_union(normalized)?
4864                } else {
4865                    normalized
4866                };
4867
4868                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4869                // Python sqlglot does this in the TSQL generator, but we can't do it there
4870                // because it would break TSQL -> TSQL identity
4871                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4872                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4873                {
4874                    transform_recursive(normalized, &|e| {
4875                        if let Expression::Count(ref c) = e {
4876                            // Build COUNT_BIG(...) as an AggregateFunction
4877                            let args = if c.star {
4878                                vec![Expression::Star(crate::expressions::Star {
4879                                    table: None,
4880                                    except: None,
4881                                    replace: None,
4882                                    rename: None,
4883                                    trailing_comments: Vec::new(),
4884                                    span: None,
4885                                })]
4886                            } else if let Some(ref this) = c.this {
4887                                vec![this.clone()]
4888                            } else {
4889                                vec![]
4890                            };
4891                            Ok(Expression::AggregateFunction(Box::new(
4892                                crate::expressions::AggregateFunction {
4893                                    name: "COUNT_BIG".to_string(),
4894                                    args,
4895                                    distinct: c.distinct,
4896                                    filter: c.filter.clone(),
4897                                    order_by: Vec::new(),
4898                                    limit: None,
4899                                    ignore_nulls: None,
4900                                    inferred_type: None,
4901                                },
4902                            )))
4903                        } else {
4904                            Ok(e)
4905                        }
4906                    })?
4907                } else {
4908                    normalized
4909                };
4910
4911                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4912                // contexts intact, but materialize boolean-valued expressions used
4913                // as values before target transforms add ORDER BY null sort keys.
4914                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4915                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4916                {
4917                    Self::rewrite_boolean_values_for_tsql(normalized)?
4918                } else {
4919                    normalized
4920                };
4921
4922                let normalized = if matches!(
4923                    self.dialect_type,
4924                    DialectType::PostgreSQL | DialectType::CockroachDB
4925                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4926                {
4927                    Self::rewrite_postgres_format_for_tsql(normalized, target)?
4928                } else {
4929                    normalized
4930                };
4931
4932                let transformed =
4933                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4934
4935                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4936                // remaining filters after target transforms so special aggregate rewrites
4937                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4938                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4939                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4940                } else {
4941                    transformed
4942                };
4943
4944                let transformed = if matches!(
4945                    self.dialect_type,
4946                    DialectType::PostgreSQL | DialectType::CockroachDB
4947                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4948                {
4949                    crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4950                } else {
4951                    transformed
4952                };
4953
4954                let transformed = if matches!(
4955                    self.dialect_type,
4956                    DialectType::PostgreSQL | DialectType::CockroachDB
4957                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4958                {
4959                    Self::normalize_postgres_trim_for_tsql(transformed)?
4960                } else {
4961                    transformed
4962                };
4963
4964                let transformed = if matches!(
4965                    self.dialect_type,
4966                    DialectType::PostgreSQL | DialectType::CockroachDB
4967                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4968                {
4969                    Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4970                } else {
4971                    transformed
4972                };
4973
4974                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4975                let transformed = if matches!(target, DialectType::DuckDB) {
4976                    Self::seq_rownum_to_range(transformed)?
4977                } else {
4978                    transformed
4979                };
4980
4981                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4982                    Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4983                }
4984
4985                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4986                    Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4987                } else {
4988                    transformed
4989                };
4990
4991                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4992
4993                let mut sql = target_dialect.generate_with_transpile_options(
4994                    &transformed,
4995                    self.dialect_type,
4996                    opts,
4997                )?;
4998
4999                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
5000                if opts.pretty && target == DialectType::Snowflake {
5001                    sql = Self::normalize_snowflake_pretty(sql);
5002                }
5003
5004                Ok(sql)
5005            })
5006            .collect()
5007    }
5008}
5009
5010// Transpile-only methods: cross-dialect normalization and helpers
5011#[cfg(feature = "transpile")]
5012impl Dialect {
5013    fn reject_strict_unsupported(
5014        expr: &Expression,
5015        source: DialectType,
5016        target: DialectType,
5017        opts: &TranspileOptions,
5018    ) -> Result<()> {
5019        if !matches!(
5020            opts.unsupported_level,
5021            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5022        ) {
5023            return Ok(());
5024        }
5025
5026        let mut diagnostics = Vec::new();
5027
5028        for node in expr.dfs() {
5029            if matches!(target, DialectType::Fabric | DialectType::Hive)
5030                && Self::node_has_recursive_with(node)
5031            {
5032                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
5033            }
5034
5035            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5036                && Self::node_has_lateral(node)
5037            {
5038                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
5039            }
5040
5041            if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
5042                Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
5043            }
5044
5045            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
5046                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
5047            }
5048
5049            if !Self::target_supports_remaining_explode(target) && Self::node_is_explode(node) {
5050                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
5051            }
5052
5053            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
5054                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
5055            }
5056
5057            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5058                && Self::node_is_regex_predicate(node)
5059            {
5060                Self::push_unsupported_diagnostic(
5061                    &mut diagnostics,
5062                    "regular expression predicates",
5063                );
5064            }
5065
5066            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5067                && Self::node_is_non_subquery_any(node)
5068            {
5069                Self::push_unsupported_diagnostic(
5070                    &mut diagnostics,
5071                    "ANY over non-subquery expressions",
5072                );
5073            }
5074
5075            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5076                && Self::node_is_row_value_subquery_comparison(node)
5077            {
5078                Self::push_unsupported_diagnostic(
5079                    &mut diagnostics,
5080                    "row-value subquery comparisons",
5081                );
5082            }
5083
5084            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5085                && Self::node_has_fetch_with_ties(node)
5086            {
5087                Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
5088            }
5089
5090            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5091                && Self::node_is_overlaps(node)
5092            {
5093                Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
5094            }
5095
5096            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5097                && Self::node_is_date_bin(node)
5098            {
5099                Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
5100            }
5101
5102            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5103                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
5104            {
5105                if Self::node_is_postgres_json_build_object(node)
5106                    && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
5107                        && Self::postgres_json_build_object_can_lower_to_json_object(node))
5108                {
5109                    Self::push_unsupported_diagnostic(
5110                        &mut diagnostics,
5111                        "PostgreSQL JSON_BUILD_OBJECT",
5112                    );
5113                }
5114                if Self::node_is_function_named(node, "TO_TSVECTOR") {
5115                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5116                }
5117                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5118                    if let Some(array_semantics) =
5119                        Self::postgres_tsql_unsupported_array_semantics(node)
5120                    {
5121                        Self::push_unsupported_diagnostic(
5122                            &mut diagnostics,
5123                            &format!("PostgreSQL {array_semantics}"),
5124                        );
5125                    }
5126                    if let Some(function_name) = Self::postgres_tsql_unsupported_function_name(node)
5127                    {
5128                        Self::push_unsupported_diagnostic(
5129                            &mut diagnostics,
5130                            &format!("PostgreSQL {function_name}"),
5131                        );
5132                    }
5133                }
5134                if matches!(target, DialectType::TSQL | DialectType::Fabric)
5135                    && Self::node_is_postgres_type_function_cast(node)
5136                {
5137                    Self::push_unsupported_diagnostic(
5138                        &mut diagnostics,
5139                        "PostgreSQL type-name function casts",
5140                    );
5141                }
5142            }
5143
5144            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5145                break;
5146            }
5147        }
5148
5149        if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5150            Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
5151        }
5152
5153        if diagnostics.is_empty() {
5154            return Ok(());
5155        }
5156
5157        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5158            1
5159        } else {
5160            opts.max_unsupported.max(1)
5161        };
5162        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5163        if diagnostics.len() > limit {
5164            messages.push(format!("... and {} more", diagnostics.len() - limit));
5165        }
5166
5167        Err(crate::error::Error::unsupported(
5168            messages.join("; "),
5169            target.to_string(),
5170        ))
5171    }
5172
5173    fn reject_postgres_tsql_strict_regex_predicates(
5174        expr: &Expression,
5175        source: DialectType,
5176        target: DialectType,
5177        opts: &TranspileOptions,
5178    ) -> Result<()> {
5179        if !matches!(
5180            opts.unsupported_level,
5181            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5182        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5183            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5184        {
5185            return Ok(());
5186        }
5187
5188        if expr.dfs().any(Self::node_is_regex_predicate) {
5189            return Err(crate::error::Error::unsupported(
5190                "regular expression predicates",
5191                target.to_string(),
5192            ));
5193        }
5194
5195        Ok(())
5196    }
5197
5198    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5199        if !diagnostics.iter().any(|existing| existing == message) {
5200            diagnostics.push(message.to_string());
5201        }
5202    }
5203
5204    fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5205        match expr {
5206            Expression::WindowFunction(window) => {
5207                if let Expression::WithinGroup(within_group) = &window.this {
5208                    if Self::within_group_is_mode(within_group) {
5209                        Self::push_unsupported_diagnostic(
5210                            diagnostics,
5211                            "MODE ordered-set aggregates",
5212                        );
5213                        return;
5214                    }
5215
5216                    if Self::within_group_is_percentile(within_group) {
5217                        if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5218                            Self::push_unsupported_diagnostic(
5219                                diagnostics,
5220                                "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5221                            );
5222                        }
5223                        return;
5224                    }
5225                }
5226            }
5227            Expression::WithinGroup(within_group) => {
5228                if Self::within_group_is_mode(within_group) {
5229                    Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5230                    return;
5231                }
5232
5233                if Self::within_group_is_percentile(within_group) {
5234                    Self::push_unsupported_diagnostic(
5235                        diagnostics,
5236                        "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5237                    );
5238                    return;
5239                }
5240            }
5241            _ => {}
5242        }
5243
5244        for child in expr.children() {
5245            Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5246        }
5247    }
5248
5249    fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5250        match &within_group.this {
5251            Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5252            Expression::AggregateFunction(function) => {
5253                Self::is_percentile_ordered_set_name(&function.name)
5254            }
5255            Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5256            _ => false,
5257        }
5258    }
5259
5260    fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5261        match &within_group.this {
5262            Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5263            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5264            Expression::Mode(_) => true,
5265            _ => false,
5266        }
5267    }
5268
5269    fn is_percentile_ordered_set_name(name: &str) -> bool {
5270        name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5271    }
5272
5273    fn target_supports_distinct_on(target: DialectType) -> bool {
5274        matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5275    }
5276
5277    fn node_has_distinct_on(expr: &Expression) -> bool {
5278        matches!(
5279            expr,
5280            Expression::Select(select)
5281                if select
5282                    .distinct_on
5283                    .as_ref()
5284                    .is_some_and(|distinct_on| !distinct_on.is_empty())
5285        )
5286    }
5287
5288    fn node_has_recursive_with(expr: &Expression) -> bool {
5289        fn recursive(with: &Option<With>) -> bool {
5290            with.as_ref().is_some_and(|with| with.recursive)
5291        }
5292
5293        match expr {
5294            Expression::With(with) => with.recursive,
5295            Expression::Select(select) => recursive(&select.with),
5296            Expression::Union(union) => recursive(&union.with),
5297            Expression::Intersect(intersect) => recursive(&intersect.with),
5298            Expression::Except(except) => recursive(&except.with),
5299            Expression::Pivot(pivot) => recursive(&pivot.with),
5300            Expression::Insert(insert) => recursive(&insert.with),
5301            Expression::Update(update) => recursive(&update.with),
5302            Expression::Delete(delete) => recursive(&delete.with),
5303            _ => false,
5304        }
5305    }
5306
5307    fn node_has_lateral(expr: &Expression) -> bool {
5308        fn join_has_lateral(join: &Join) -> bool {
5309            matches!(
5310                join.kind,
5311                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5312            ) || Dialect::node_has_lateral(&join.this)
5313                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5314                || join
5315                    .match_condition
5316                    .as_ref()
5317                    .is_some_and(Dialect::node_has_lateral)
5318                || join.pivots.iter().any(Dialect::node_has_lateral)
5319        }
5320
5321        fn joins_have_lateral(joins: &[Join]) -> bool {
5322            joins.iter().any(join_has_lateral)
5323        }
5324
5325        match expr {
5326            Expression::Subquery(subquery) => {
5327                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5328            }
5329            Expression::Lateral(_) | Expression::LateralView(_) => true,
5330            Expression::Join(join) => join_has_lateral(join),
5331            Expression::Select(select) => {
5332                !select.lateral_views.is_empty()
5333                    || joins_have_lateral(&select.joins)
5334                    || select
5335                        .from
5336                        .as_ref()
5337                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5338            }
5339            Expression::JoinedTable(joined) => {
5340                !joined.lateral_views.is_empty()
5341                    || Dialect::node_has_lateral(&joined.left)
5342                    || joins_have_lateral(&joined.joins)
5343            }
5344            Expression::Update(update) => {
5345                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5346            }
5347            _ => false,
5348        }
5349    }
5350
5351    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5352        matches!(
5353            target,
5354            DialectType::PostgreSQL
5355                | DialectType::BigQuery
5356                | DialectType::DuckDB
5357                | DialectType::Presto
5358                | DialectType::Trino
5359                | DialectType::Athena
5360        )
5361    }
5362
5363    fn target_supports_remaining_explode(target: DialectType) -> bool {
5364        matches!(
5365            target,
5366            DialectType::Spark | DialectType::Databricks | DialectType::Hive
5367        )
5368    }
5369
5370    fn target_lacks_array_agg(target: DialectType) -> bool {
5371        matches!(
5372            target,
5373            DialectType::Fabric
5374                | DialectType::TSQL
5375                | DialectType::MySQL
5376                | DialectType::SQLite
5377                | DialectType::Oracle
5378        )
5379    }
5380
5381    fn node_is_unnest(expr: &Expression) -> bool {
5382        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
5383    }
5384
5385    fn node_is_explode(expr: &Expression) -> bool {
5386        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
5387            || Self::node_is_function_named(expr, "EXPLODE")
5388            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
5389    }
5390
5391    fn node_is_array_agg(expr: &Expression) -> bool {
5392        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
5393    }
5394
5395    fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
5396        match expr {
5397            Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
5398            Expression::Subscript(_) => Some("array subscripts"),
5399            Expression::ArraySlice(_) => Some("array slices"),
5400            Expression::DataType(DataType::Array { .. }) => Some("array data types"),
5401            Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
5402            Expression::Cardinality(_) => Some("CARDINALITY"),
5403            Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
5404            Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
5405            Expression::ArrayContains(_)
5406            | Expression::ArrayPosition(_)
5407            | Expression::ArrayAppend(_)
5408            | Expression::ArrayPrepend(_)
5409            | Expression::ArrayConcat(_)
5410            | Expression::ArraySort(_)
5411            | Expression::ArrayReverse(_)
5412            | Expression::ArrayDistinct(_)
5413            | Expression::ArrayFilter(_)
5414            | Expression::ArrayTransform(_)
5415            | Expression::ArrayFlatten(_)
5416            | Expression::ArrayCompact(_)
5417            | Expression::ArrayIntersect(_)
5418            | Expression::ArrayUnion(_)
5419            | Expression::ArrayExcept(_)
5420            | Expression::ArrayRemove(_)
5421            | Expression::ArrayZip(_)
5422            | Expression::ArrayAll(_)
5423            | Expression::ArrayAny(_)
5424            | Expression::ArrayConstructCompact(_)
5425            | Expression::ArraySum(_) => Some("array functions"),
5426            Expression::ArrayContainsAll(_)
5427            | Expression::ArrayContainedBy(_)
5428            | Expression::ArrayOverlaps(_) => Some("array operators"),
5429            Expression::Function(function) => {
5430                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5431            }
5432            Expression::AggregateFunction(function) => {
5433                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
5434            }
5435            _ => None,
5436        }
5437    }
5438
5439    fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
5440        if name.eq_ignore_ascii_case("ARRAY") {
5441            Some("array literals")
5442        } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
5443            || name.eq_ignore_ascii_case("ARRAY_SIZE")
5444        {
5445            Some("ARRAY_LENGTH")
5446        } else if name.eq_ignore_ascii_case("CARDINALITY") {
5447            Some("CARDINALITY")
5448        } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
5449            || name.eq_ignore_ascii_case("ARRAY_JOIN")
5450        {
5451            Some("ARRAY_TO_STRING")
5452        } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
5453            Some("STRING_TO_ARRAY")
5454        } else {
5455            None
5456        }
5457    }
5458
5459    fn node_is_regex_predicate(expr: &Expression) -> bool {
5460        matches!(
5461            expr,
5462            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
5463        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
5464            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
5465            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
5466    }
5467
5468    fn node_is_non_subquery_any(expr: &Expression) -> bool {
5469        matches!(
5470            expr,
5471            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
5472        )
5473    }
5474
5475    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
5476        match expr {
5477            Expression::Select(_) | Expression::Subquery(_) => true,
5478            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
5479            _ => false,
5480        }
5481    }
5482
5483    fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
5484        match expr {
5485            Expression::In(in_expr) => {
5486                Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
5487            }
5488            Expression::Eq(op) | Expression::Neq(op) => {
5489                (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
5490                    || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
5491            }
5492            _ => false,
5493        }
5494    }
5495
5496    fn expr_is_row_value(expr: &Expression) -> bool {
5497        match expr {
5498            Expression::Tuple(tuple) => tuple.expressions.len() > 1,
5499            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
5500                function.args.len() > 1
5501            }
5502            Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
5503            _ => false,
5504        }
5505    }
5506
5507    fn expr_is_subquery_like(expr: &Expression) -> bool {
5508        match expr {
5509            Expression::Select(_) | Expression::Subquery(_) => true,
5510            Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
5511            _ => false,
5512        }
5513    }
5514
5515    fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
5516        if in_expr
5517            .query
5518            .as_ref()
5519            .is_some_and(Self::expr_is_subquery_like)
5520        {
5521            return true;
5522        }
5523
5524        in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
5525    }
5526
5527    fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
5528        transform_recursive(expr, &|e| match e {
5529            Expression::Select(mut select) => {
5530                if select.top.is_none() && select.offset.is_none() {
5531                    if let Some(fetch) = select.fetch.take() {
5532                        if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
5533                            select.top = Some(top);
5534                        } else {
5535                            select.fetch = Some(fetch);
5536                        }
5537                    }
5538                }
5539                Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
5540                Ok(Expression::Select(select))
5541            }
5542            Expression::DateBin(date_bin) => {
5543                let date_bin = *date_bin;
5544                if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
5545                    Ok(rewritten)
5546                } else {
5547                    Ok(Expression::DateBin(Box::new(date_bin)))
5548                }
5549            }
5550            Expression::Function(function) => {
5551                let function = *function;
5552                if function.name.eq_ignore_ascii_case("DATE_BIN") {
5553                    if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
5554                        Ok(rewritten)
5555                    } else {
5556                        Ok(Expression::Function(Box::new(function)))
5557                    }
5558                } else {
5559                    Ok(Expression::Function(Box::new(function)))
5560                }
5561            }
5562            _ => Ok(e),
5563        })
5564    }
5565
5566    fn rewrite_tsql_overlaps_in_select_predicates(
5567        select: &mut crate::expressions::Select,
5568    ) -> Result<()> {
5569        if let Some(where_clause) = &mut select.where_clause {
5570            where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
5571        }
5572        if let Some(having) = &mut select.having {
5573            having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
5574        }
5575        if let Some(qualify) = &mut select.qualify {
5576            qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
5577        }
5578        for join in &mut select.joins {
5579            if let Some(on) = join.on.take() {
5580                join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
5581            }
5582            if let Some(match_condition) = join.match_condition.take() {
5583                join.match_condition =
5584                    Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
5585            }
5586        }
5587        Ok(())
5588    }
5589
5590    fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
5591        transform_recursive(expr, &|e| match e {
5592            Expression::Overlaps(overlaps) => {
5593                let overlaps = *overlaps;
5594                if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
5595                    Ok(rewritten)
5596                } else {
5597                    Ok(Expression::Overlaps(Box::new(overlaps)))
5598                }
5599            }
5600            _ => Ok(e),
5601        })
5602    }
5603
5604    fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
5605        if !fetch.with_ties {
5606            return None;
5607        }
5608
5609        fetch.count.map(|count| Top {
5610            this: count,
5611            percent: fetch.percent,
5612            with_ties: true,
5613            parenthesized: true,
5614        })
5615    }
5616
5617    fn rewrite_full_overlaps_for_tsql(
5618        overlaps: &crate::expressions::OverlapsExpr,
5619    ) -> Option<Expression> {
5620        let (left_start, left_end, right_start, right_end) =
5621            if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
5622                overlaps.left_start.as_ref(),
5623                overlaps.left_end.as_ref(),
5624                overlaps.right_start.as_ref(),
5625                overlaps.right_end.as_ref(),
5626            ) {
5627                (left_start, left_end, right_start, right_end)
5628            } else if let (
5629                Some(Expression::Tuple(left_tuple)),
5630                Some(Expression::Tuple(right_tuple)),
5631            ) = (&overlaps.this, &overlaps.expression)
5632            {
5633                if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
5634                    return None;
5635                }
5636                (
5637                    &left_tuple.expressions[0],
5638                    &left_tuple.expressions[1],
5639                    &right_tuple.expressions[0],
5640                    &right_tuple.expressions[1],
5641                )
5642            } else {
5643                return None;
5644            };
5645
5646        let left_min = Self::case_min(left_start.clone(), left_end.clone());
5647        let left_max = Self::case_max(left_start.clone(), left_end.clone());
5648        let right_min = Self::case_min(right_start.clone(), right_end.clone());
5649        let right_max = Self::case_max(right_start.clone(), right_end.clone());
5650
5651        Some(Expression::And(Box::new(BinaryOp::new(
5652            Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
5653            Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
5654        ))))
5655    }
5656
5657    fn case_min(left: Expression, right: Expression) -> Expression {
5658        Expression::Case(Box::new(Case {
5659            operand: None,
5660            whens: vec![(
5661                Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
5662                left,
5663            )],
5664            else_: Some(right),
5665            comments: Vec::new(),
5666            inferred_type: None,
5667        }))
5668    }
5669
5670    fn case_max(left: Expression, right: Expression) -> Expression {
5671        Expression::Case(Box::new(Case {
5672            operand: None,
5673            whens: vec![(
5674                Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
5675                left,
5676            )],
5677            else_: Some(right),
5678            comments: Vec::new(),
5679            inferred_type: None,
5680        }))
5681    }
5682
5683    fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
5684        if date_bin.unit.is_some() || date_bin.zone.is_some() {
5685            return None;
5686        }
5687
5688        let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
5689        let mut args = vec![
5690            Self::date_bucket_datepart(datepart),
5691            number,
5692            *date_bin.expression,
5693        ];
5694        if let Some(origin) = date_bin.origin {
5695            args.push(*origin);
5696        }
5697
5698        Some(Expression::Function(Box::new(Function::new(
5699            "DATE_BUCKET".to_string(),
5700            args,
5701        ))))
5702    }
5703
5704    fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
5705        if !(2..=3).contains(&function.args.len()) {
5706            return None;
5707        }
5708
5709        let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
5710        let mut args = vec![
5711            Self::date_bucket_datepart(datepart),
5712            number,
5713            function.args[1].clone(),
5714        ];
5715        if let Some(origin) = function.args.get(2) {
5716            args.push(origin.clone());
5717        }
5718
5719        Some(Expression::Function(Box::new(Function::new(
5720            "DATE_BUCKET".to_string(),
5721            args,
5722        ))))
5723    }
5724
5725    fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
5726        match stride {
5727            Expression::Literal(lit) => match lit.as_ref() {
5728                Literal::String(value) => Self::date_bucket_parts_from_string(value),
5729                _ => None,
5730            },
5731            Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
5732            _ => None,
5733        }
5734    }
5735
5736    fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
5737        match &interval.unit {
5738            Some(IntervalUnitSpec::Simple { unit, .. }) => {
5739                let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
5740                let amount = interval
5741                    .this
5742                    .as_ref()
5743                    .and_then(Self::date_bucket_amount_expr)?;
5744                Some((datepart, amount))
5745            }
5746            None => interval.this.as_ref().and_then(|expr| match expr {
5747                Expression::Literal(lit) => match lit.as_ref() {
5748                    Literal::String(value) => Self::date_bucket_parts_from_string(value),
5749                    _ => None,
5750                },
5751                _ => None,
5752            }),
5753            _ => None,
5754        }
5755    }
5756
5757    fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
5758        let mut parts = value.split_whitespace();
5759        let amount = parts.next()?;
5760        let unit = parts.next()?;
5761        if parts.next().is_some() {
5762            return None;
5763        }
5764
5765        Some((
5766            Self::date_bucket_datepart_from_name(unit)?,
5767            Self::positive_integer_expr(amount)?,
5768        ))
5769    }
5770
5771    fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
5772        match expr {
5773            Expression::Literal(lit) => match lit.as_ref() {
5774                Literal::Number(value) => Self::positive_integer_expr(value),
5775                Literal::String(value) => Self::positive_integer_expr(value),
5776                _ => None,
5777            },
5778            _ => Some(expr.clone()),
5779        }
5780    }
5781
5782    fn positive_integer_expr(value: &str) -> Option<Expression> {
5783        let parsed = value.trim().parse::<i64>().ok()?;
5784        (parsed > 0).then(|| Expression::number(parsed))
5785    }
5786
5787    fn date_bucket_datepart(datepart: &str) -> Expression {
5788        Expression::Var(Box::new(Var {
5789            this: datepart.to_string(),
5790        }))
5791    }
5792
5793    fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
5794        match unit {
5795            IntervalUnit::Week => Some("WEEK"),
5796            IntervalUnit::Day => Some("DAY"),
5797            IntervalUnit::Hour => Some("HOUR"),
5798            IntervalUnit::Minute => Some("MINUTE"),
5799            IntervalUnit::Second => Some("SECOND"),
5800            IntervalUnit::Millisecond => Some("MILLISECOND"),
5801            _ => None,
5802        }
5803    }
5804
5805    fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
5806        match unit.trim().to_ascii_uppercase().as_str() {
5807            "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
5808            "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
5809            "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
5810            "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
5811            "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
5812            "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
5813                Some("MILLISECOND")
5814            }
5815            _ => None,
5816        }
5817    }
5818
5819    fn node_has_fetch_with_ties(expr: &Expression) -> bool {
5820        matches!(
5821            expr,
5822            Expression::Select(select)
5823                if select
5824                    .fetch
5825                    .as_ref()
5826                    .is_some_and(|fetch| fetch.with_ties)
5827        )
5828    }
5829
5830    fn node_is_overlaps(expr: &Expression) -> bool {
5831        matches!(expr, Expression::Overlaps(_))
5832    }
5833
5834    fn node_is_date_bin(expr: &Expression) -> bool {
5835        matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
5836    }
5837
5838    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
5839        match expr {
5840            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
5841            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
5842            _ => false,
5843        }
5844    }
5845
5846    fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
5847        match expr {
5848            Expression::Function(function) => {
5849                function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
5850                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
5851            }
5852            _ => false,
5853        }
5854    }
5855
5856    fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
5857        matches!(
5858            expr,
5859            Expression::Function(function)
5860                if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
5861                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
5862                    && !function.distinct
5863                    && function.args.len() % 2 == 0
5864        )
5865    }
5866
5867    fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
5868        matches!(
5869            expr,
5870            Expression::Function(function)
5871                if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
5872                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
5873                    || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
5874                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
5875        )
5876    }
5877
5878    fn postgres_tsql_unsupported_function_name(expr: &Expression) -> Option<&'static str> {
5879        match expr {
5880            Expression::Lpad(_) => Some("LPAD"),
5881            Expression::Rpad(_) => Some("RPAD"),
5882            Expression::SplitPart(_) => Some("SPLIT_PART"),
5883            Expression::Initcap(_) => Some("INITCAP"),
5884            Expression::ToJson(_) => Some("TO_JSON"),
5885            Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
5886            Expression::ToNumber(_) => Some("TO_NUMBER"),
5887            Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
5888            Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
5889            Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
5890            Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
5891            Expression::Corr(_) => Some("CORR"),
5892            Expression::CovarPop(_) => Some("COVAR_POP"),
5893            Expression::CovarSamp(_) => Some("COVAR_SAMP"),
5894            Expression::RegrAvgx(_) => Some("REGR_AVGX"),
5895            Expression::RegrAvgy(_) => Some("REGR_AVGY"),
5896            Expression::RegrCount(_) => Some("REGR_COUNT"),
5897            Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
5898            Expression::RegrR2(_) => Some("REGR_R2"),
5899            Expression::RegrSlope(_) => Some("REGR_SLOPE"),
5900            Expression::RegrSxx(_) => Some("REGR_SXX"),
5901            Expression::RegrSxy(_) => Some("REGR_SXY"),
5902            Expression::RegrSyy(_) => Some("REGR_SYY"),
5903            Expression::Function(function) => {
5904                Self::postgres_tsql_unsupported_function_name_str(&function.name)
5905            }
5906            Expression::AggregateFunction(function) => {
5907                Self::postgres_tsql_unsupported_function_name_str(&function.name)
5908            }
5909            _ => None,
5910        }
5911    }
5912
5913    fn postgres_tsql_unsupported_function_name_str(name: &str) -> Option<&'static str> {
5914        if name.eq_ignore_ascii_case("LPAD") {
5915            Some("LPAD")
5916        } else if name.eq_ignore_ascii_case("RPAD") {
5917            Some("RPAD")
5918        } else if name.eq_ignore_ascii_case("SPLIT_PART") {
5919            Some("SPLIT_PART")
5920        } else if name.eq_ignore_ascii_case("INITCAP") {
5921            Some("INITCAP")
5922        } else if name.eq_ignore_ascii_case("TO_JSON") {
5923            Some("TO_JSON")
5924        } else if name.eq_ignore_ascii_case("TO_JSONB") {
5925            Some("TO_JSONB")
5926        } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
5927            Some("JSONB_OBJECT_AGG")
5928        } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
5929            Some("ROW_TO_JSON")
5930        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
5931            Some("JSON_ARRAY_ELEMENTS")
5932        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
5933            Some("JSONB_ARRAY_ELEMENTS")
5934        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
5935            Some("JSON_ARRAY_ELEMENTS_TEXT")
5936        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
5937            Some("JSONB_ARRAY_ELEMENTS_TEXT")
5938        } else if name.eq_ignore_ascii_case("ENCODE") {
5939            Some("ENCODE")
5940        } else if name.eq_ignore_ascii_case("AGE") {
5941            Some("AGE")
5942        } else if name.eq_ignore_ascii_case("ERF") {
5943            Some("ERF")
5944        } else if name.eq_ignore_ascii_case("GCD") {
5945            Some("GCD")
5946        } else if name.eq_ignore_ascii_case("LCM") {
5947            Some("LCM")
5948        } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
5949            Some("QUOTE_LITERAL")
5950        } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
5951            Some("WIDTH_BUCKET")
5952        } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
5953            Some("PG_TYPEOF")
5954        } else if name.eq_ignore_ascii_case("BIT_AND") {
5955            Some("BIT_AND")
5956        } else if name.eq_ignore_ascii_case("BIT_OR") {
5957            Some("BIT_OR")
5958        } else if name.eq_ignore_ascii_case("BIT_XOR") {
5959            Some("BIT_XOR")
5960        } else if name.eq_ignore_ascii_case("CORR") {
5961            Some("CORR")
5962        } else if name.eq_ignore_ascii_case("COVAR_POP") {
5963            Some("COVAR_POP")
5964        } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
5965            Some("COVAR_SAMP")
5966        } else if name.eq_ignore_ascii_case("REGR_AVGX") {
5967            Some("REGR_AVGX")
5968        } else if name.eq_ignore_ascii_case("REGR_AVGY") {
5969            Some("REGR_AVGY")
5970        } else if name.eq_ignore_ascii_case("REGR_COUNT") {
5971            Some("REGR_COUNT")
5972        } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
5973            Some("REGR_INTERCEPT")
5974        } else if name.eq_ignore_ascii_case("REGR_R2") {
5975            Some("REGR_R2")
5976        } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
5977            Some("REGR_SLOPE")
5978        } else if name.eq_ignore_ascii_case("REGR_SXX") {
5979            Some("REGR_SXX")
5980        } else if name.eq_ignore_ascii_case("REGR_SXY") {
5981            Some("REGR_SXY")
5982        } else if name.eq_ignore_ascii_case("REGR_SYY") {
5983            Some("REGR_SYY")
5984        } else {
5985            None
5986        }
5987    }
5988
5989    fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
5990        transform_recursive(expr, &|e| match e {
5991            Expression::Trim(trim) => {
5992                let mut trim = *trim;
5993                match trim.position {
5994                    crate::expressions::TrimPosition::Both
5995                        if trim.position_explicit && trim.characters.is_some() =>
5996                    {
5997                        trim.position_explicit = false;
5998                        trim.sql_standard_syntax = true;
5999                        Ok(Expression::Trim(Box::new(trim)))
6000                    }
6001                    crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6002                        let characters = trim.characters.take().expect("checked above");
6003                        Ok(Expression::Function(Box::new(Function::new(
6004                            "LTRIM",
6005                            vec![trim.this, characters],
6006                        ))))
6007                    }
6008                    crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6009                        let characters = trim.characters.take().expect("checked above");
6010                        Ok(Expression::Function(Box::new(Function::new(
6011                            "RTRIM",
6012                            vec![trim.this, characters],
6013                        ))))
6014                    }
6015                    _ => Ok(Expression::Trim(Box::new(trim))),
6016                }
6017            }
6018            other => Ok(other),
6019        })
6020    }
6021
6022    fn rewrite_postgres_json_array_elements_select_for_tsql(
6023        expr: Expression,
6024    ) -> Result<Expression> {
6025        let Expression::Select(select) = expr else {
6026            return Ok(expr);
6027        };
6028        let mut select = *select;
6029        if !Self::is_plain_single_projection_select(&select) {
6030            return Ok(Expression::Select(Box::new(select)));
6031        }
6032
6033        let Some(json_arg) =
6034            Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
6035        else {
6036            return Ok(Expression::Select(Box::new(select)));
6037        };
6038
6039        select.expressions = vec![Expression::column("value")];
6040        select.from = Some(From {
6041            expressions: vec![Expression::OpenJSON(Box::new(
6042                crate::expressions::OpenJSON {
6043                    this: Box::new(json_arg),
6044                    path: None,
6045                    expressions: Vec::new(),
6046                },
6047            ))],
6048        });
6049
6050        Ok(Expression::Select(Box::new(select)))
6051    }
6052
6053    fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
6054        select.expressions.len() == 1
6055            && select.from.is_none()
6056            && select.joins.is_empty()
6057            && select.lateral_views.is_empty()
6058            && select.prewhere.is_none()
6059            && select.where_clause.is_none()
6060            && select.group_by.is_none()
6061            && select.having.is_none()
6062            && select.qualify.is_none()
6063            && select.order_by.is_none()
6064            && select.distribute_by.is_none()
6065            && select.cluster_by.is_none()
6066            && select.sort_by.is_none()
6067            && select.limit.is_none()
6068            && select.offset.is_none()
6069            && select.limit_by.is_none()
6070            && select.fetch.is_none()
6071            && !select.distinct
6072            && select.distinct_on.is_none()
6073            && select.top.is_none()
6074            && select.with.is_none()
6075            && select.sample.is_none()
6076            && select.into.is_none()
6077            && select.locks.is_empty()
6078            && select.for_xml.is_empty()
6079            && select.for_json.is_empty()
6080            && select.exclude.is_none()
6081    }
6082
6083    fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
6084        match expr {
6085            Expression::Function(function)
6086                if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
6087            {
6088                Some(function.args[0].clone())
6089            }
6090            Expression::Alias(alias) => {
6091                Self::postgres_json_array_elements_projection_arg(&alias.this)
6092            }
6093            _ => None,
6094        }
6095    }
6096
6097    fn normalize_postgres_type_function_casts(expr: Expression) -> Result<Expression> {
6098        transform_recursive(expr, &|e| match e {
6099            Expression::Function(function) => {
6100                let mut function = *function;
6101                if function.args.len() == 1
6102                    && !function.distinct
6103                    && !function.quoted
6104                    && !function.use_bracket_syntax
6105                    && !function.name.contains('.')
6106                {
6107                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
6108                        let this = function.args.remove(0);
6109                        return Ok(Expression::Cast(Box::new(Cast {
6110                            this,
6111                            to,
6112                            trailing_comments: function.trailing_comments,
6113                            double_colon_syntax: false,
6114                            format: None,
6115                            default: None,
6116                            inferred_type: function.inferred_type,
6117                        })));
6118                    }
6119                }
6120                Ok(Expression::Function(Box::new(function)))
6121            }
6122            _ => Ok(e),
6123        })
6124    }
6125
6126    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
6127        matches!(
6128            expr,
6129            Expression::Function(function)
6130                if !function.quoted
6131                    && !function.use_bracket_syntax
6132                    && !function.name.contains('.')
6133                    && Self::postgres_type_function_data_type(&function.name).is_some()
6134        )
6135    }
6136
6137    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
6138        match name.to_ascii_uppercase().as_str() {
6139            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
6140                precision: None,
6141                scale: None,
6142            }),
6143            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
6144            "INT4" | "INT" => Some(DataType::Int {
6145                length: None,
6146                integer_spelling: false,
6147            }),
6148            "INTEGER" => Some(DataType::Int {
6149                length: None,
6150                integer_spelling: true,
6151            }),
6152            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
6153            "FLOAT4" | "REAL" => Some(DataType::Float {
6154                precision: None,
6155                scale: None,
6156                real_spelling: true,
6157            }),
6158            "FLOAT8" => Some(DataType::Double {
6159                precision: None,
6160                scale: None,
6161            }),
6162            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
6163            "TEXT" => Some(DataType::Text),
6164            "VARCHAR" => Some(DataType::VarChar {
6165                length: None,
6166                parenthesized_length: false,
6167            }),
6168            "UUID" => Some(DataType::Uuid),
6169            _ => None,
6170        }
6171    }
6172
6173    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
6174        match expr {
6175            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6176            Expression::Subquery(mut subquery) => {
6177                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6178                Ok(Expression::Subquery(subquery))
6179            }
6180            Expression::Union(mut union) => {
6181                let left = std::mem::replace(&mut union.left, Expression::null());
6182                let right = std::mem::replace(&mut union.right, Expression::null());
6183                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
6184                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
6185                if let Some(mut with) = union.with.take() {
6186                    with.ctes = with
6187                        .ctes
6188                        .into_iter()
6189                        .map(|mut cte| {
6190                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
6191                            Ok(cte)
6192                        })
6193                        .collect::<Result<Vec<_>>>()?;
6194                    union.with = Some(with);
6195                }
6196                Ok(Expression::Union(union))
6197            }
6198            Expression::Intersect(mut intersect) => {
6199                let left = std::mem::replace(&mut intersect.left, Expression::null());
6200                let right = std::mem::replace(&mut intersect.right, Expression::null());
6201                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
6202                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
6203                Ok(Expression::Intersect(intersect))
6204            }
6205            Expression::Except(mut except) => {
6206                let left = std::mem::replace(&mut except.left, Expression::null());
6207                let right = std::mem::replace(&mut except.right, Expression::null());
6208                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
6209                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
6210                Ok(Expression::Except(except))
6211            }
6212            other => Self::rewrite_tsql_boolean_embedded_queries(other),
6213        }
6214    }
6215
6216    fn rewrite_postgres_format_for_tsql(
6217        expr: Expression,
6218        target: DialectType,
6219    ) -> Result<Expression> {
6220        transform_recursive(expr, &|e| match e {
6221            Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
6222                Self::postgres_format_function_to_tsql(*f, target)
6223            }
6224            other => Ok(other),
6225        })
6226    }
6227
6228    fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
6229        let Some(format_expr) = f.args.first() else {
6230            return Err(Self::unsupported_postgres_format_for_tsql(
6231                target,
6232                "missing format string",
6233            ));
6234        };
6235
6236        let format = match format_expr {
6237            Expression::Literal(lit) if lit.is_string() => lit.value_str(),
6238            _ => {
6239                return Err(Self::unsupported_postgres_format_for_tsql(
6240                    target,
6241                    "dynamic format strings",
6242                ))
6243            }
6244        };
6245
6246        let value_args = &f.args[1..];
6247        let mut arg_index = 0usize;
6248        let mut literal = String::new();
6249        let mut segments = Vec::new();
6250        let mut chars = format.chars();
6251
6252        while let Some(ch) = chars.next() {
6253            if ch != '%' {
6254                literal.push(ch);
6255                continue;
6256            }
6257
6258            let Some(specifier) = chars.next() else {
6259                return Err(Self::unsupported_postgres_format_for_tsql(
6260                    target,
6261                    "unterminated format specifier",
6262                ));
6263            };
6264
6265            match specifier {
6266                '%' => literal.push('%'),
6267                's' => {
6268                    if !literal.is_empty() {
6269                        segments.push(Expression::string(std::mem::take(&mut literal)));
6270                    }
6271                    let Some(arg) = value_args.get(arg_index) else {
6272                        return Err(Self::unsupported_postgres_format_for_tsql(
6273                            target,
6274                            "not enough arguments",
6275                        ));
6276                    };
6277                    segments.push(arg.clone());
6278                    arg_index += 1;
6279                }
6280                other => {
6281                    return Err(Self::unsupported_postgres_format_for_tsql(
6282                        target,
6283                        format!("unsupported format specifier %{other}"),
6284                    ))
6285                }
6286            }
6287        }
6288
6289        if !literal.is_empty() {
6290            segments.push(Expression::string(literal));
6291        }
6292
6293        if arg_index != value_args.len() {
6294            return Err(Self::unsupported_postgres_format_for_tsql(
6295                target,
6296                "unused format arguments",
6297            ));
6298        }
6299
6300        Ok(Self::postgres_format_segments_to_tsql_concat(segments))
6301    }
6302
6303    fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
6304        if segments.is_empty() {
6305            return Expression::string("");
6306        }
6307
6308        if segments.len() == 1 {
6309            let only = segments.pop().expect("one segment");
6310            if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
6311                return only;
6312            }
6313
6314            return Expression::Function(Box::new(Function::new(
6315                "CONCAT".to_string(),
6316                vec![only, Expression::string("")],
6317            )));
6318        }
6319
6320        Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
6321    }
6322
6323    fn unsupported_postgres_format_for_tsql(
6324        target: DialectType,
6325        reason: impl Into<String>,
6326    ) -> crate::error::Error {
6327        crate::error::Error::unsupported(
6328            format!("PostgreSQL format() ({})", reason.into()),
6329            target.to_string(),
6330        )
6331    }
6332
6333    fn rewrite_boolean_values_in_tsql_select(
6334        mut select: Box<crate::expressions::Select>,
6335    ) -> Result<Expression> {
6336        if let Some(mut with) = select.with.take() {
6337            with.ctes = with
6338                .ctes
6339                .into_iter()
6340                .map(|mut cte| {
6341                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
6342                    Ok(cte)
6343                })
6344                .collect::<Result<Vec<_>>>()?;
6345            select.with = Some(with);
6346        }
6347
6348        select.expressions = select
6349            .expressions
6350            .into_iter()
6351            .map(Self::rewrite_tsql_boolean_scalar_value)
6352            .collect::<Result<Vec<_>>>()?;
6353
6354        if let Some(mut from) = select.from.take() {
6355            from.expressions = from
6356                .expressions
6357                .into_iter()
6358                .map(Self::rewrite_tsql_boolean_embedded_queries)
6359                .collect::<Result<Vec<_>>>()?;
6360            select.from = Some(from);
6361        }
6362
6363        select.joins = select
6364            .joins
6365            .into_iter()
6366            .map(|mut join| {
6367                join.this = Self::rewrite_tsql_boolean_embedded_queries(join.this)?;
6368                if let Some(on) = join.on.take() {
6369                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
6370                }
6371                if let Some(match_condition) = join.match_condition.take() {
6372                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
6373                        match_condition,
6374                    )?);
6375                }
6376                join.pivots = join
6377                    .pivots
6378                    .into_iter()
6379                    .map(Self::rewrite_tsql_boolean_embedded_queries)
6380                    .collect::<Result<Vec<_>>>()?;
6381                Ok(join)
6382            })
6383            .collect::<Result<Vec<_>>>()?;
6384
6385        select.lateral_views = select
6386            .lateral_views
6387            .into_iter()
6388            .map(|mut lateral_view| {
6389                lateral_view.this = Self::rewrite_tsql_boolean_embedded_queries(lateral_view.this)?;
6390                Ok(lateral_view)
6391            })
6392            .collect::<Result<Vec<_>>>()?;
6393
6394        if let Some(prewhere) = select.prewhere.take() {
6395            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
6396        }
6397
6398        if let Some(mut where_clause) = select.where_clause.take() {
6399            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
6400            select.where_clause = Some(where_clause);
6401        }
6402
6403        if let Some(mut group_by) = select.group_by.take() {
6404            group_by.expressions = group_by
6405                .expressions
6406                .into_iter()
6407                .map(Self::rewrite_tsql_boolean_scalar_value)
6408                .collect::<Result<Vec<_>>>()?;
6409            select.group_by = Some(group_by);
6410        }
6411
6412        if let Some(mut having) = select.having.take() {
6413            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
6414            select.having = Some(having);
6415        }
6416
6417        if let Some(mut qualify) = select.qualify.take() {
6418            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
6419            select.qualify = Some(qualify);
6420        }
6421
6422        if let Some(mut order_by) = select.order_by.take() {
6423            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
6424            select.order_by = Some(order_by);
6425        }
6426
6427        if let Some(mut distribute_by) = select.distribute_by.take() {
6428            distribute_by.expressions = distribute_by
6429                .expressions
6430                .into_iter()
6431                .map(Self::rewrite_tsql_boolean_scalar_value)
6432                .collect::<Result<Vec<_>>>()?;
6433            select.distribute_by = Some(distribute_by);
6434        }
6435
6436        if let Some(mut cluster_by) = select.cluster_by.take() {
6437            cluster_by.expressions =
6438                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
6439            select.cluster_by = Some(cluster_by);
6440        }
6441
6442        if let Some(mut sort_by) = select.sort_by.take() {
6443            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
6444            select.sort_by = Some(sort_by);
6445        }
6446
6447        if let Some(limit_by) = select.limit_by.take() {
6448            select.limit_by = Some(
6449                limit_by
6450                    .into_iter()
6451                    .map(Self::rewrite_tsql_boolean_scalar_value)
6452                    .collect::<Result<Vec<_>>>()?,
6453            );
6454        }
6455
6456        if let Some(distinct_on) = select.distinct_on.take() {
6457            select.distinct_on = Some(
6458                distinct_on
6459                    .into_iter()
6460                    .map(Self::rewrite_tsql_boolean_scalar_value)
6461                    .collect::<Result<Vec<_>>>()?,
6462            );
6463        }
6464
6465        if let Some(mut sample) = select.sample.take() {
6466            sample.size = Self::rewrite_tsql_boolean_embedded_queries(sample.size)?;
6467            if let Some(offset) = sample.offset.take() {
6468                sample.offset = Some(Self::rewrite_tsql_boolean_embedded_queries(offset)?);
6469            }
6470            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
6471                sample.bucket_numerator = Some(Box::new(
6472                    Self::rewrite_tsql_boolean_embedded_queries(*bucket_numerator)?,
6473                ));
6474            }
6475            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
6476                sample.bucket_denominator = Some(Box::new(
6477                    Self::rewrite_tsql_boolean_embedded_queries(*bucket_denominator)?,
6478                ));
6479            }
6480            if let Some(bucket_field) = sample.bucket_field.take() {
6481                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6482                    *bucket_field,
6483                )?));
6484            }
6485            select.sample = Some(sample);
6486        }
6487
6488        if let Some(settings) = select.settings.take() {
6489            select.settings = Some(
6490                settings
6491                    .into_iter()
6492                    .map(Self::rewrite_tsql_boolean_embedded_queries)
6493                    .collect::<Result<Vec<_>>>()?,
6494            );
6495        }
6496
6497        if let Some(format) = select.format.take() {
6498            select.format = Some(Self::rewrite_tsql_boolean_embedded_queries(format)?);
6499        }
6500
6501        if let Some(mut windows) = select.windows.take() {
6502            for window in windows.iter_mut() {
6503                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
6504            }
6505            select.windows = Some(windows);
6506        }
6507
6508        Ok(Expression::Select(select))
6509    }
6510
6511    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
6512        if Self::is_tsql_boolean_value_expression(&expr) {
6513            return Ok(Self::tsql_boolean_value_case(expr));
6514        }
6515
6516        match expr {
6517            Expression::Alias(mut alias) => {
6518                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
6519                Ok(Expression::Alias(alias))
6520            }
6521            Expression::Paren(mut paren) => {
6522                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
6523                Ok(Expression::Paren(paren))
6524            }
6525            Expression::Cast(mut cast) => {
6526                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6527                if let Some(format) = cast.format.take() {
6528                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6529                        *format,
6530                    )?));
6531                }
6532                if let Some(default) = cast.default.take() {
6533                    cast.default =
6534                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6535                }
6536                Ok(Expression::Cast(cast))
6537            }
6538            Expression::TryCast(mut cast) => {
6539                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6540                if let Some(format) = cast.format.take() {
6541                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6542                        *format,
6543                    )?));
6544                }
6545                if let Some(default) = cast.default.take() {
6546                    cast.default =
6547                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6548                }
6549                Ok(Expression::TryCast(cast))
6550            }
6551            Expression::SafeCast(mut cast) => {
6552                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
6553                if let Some(format) = cast.format.take() {
6554                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_embedded_queries(
6555                        *format,
6556                    )?));
6557                }
6558                if let Some(default) = cast.default.take() {
6559                    cast.default =
6560                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
6561                }
6562                Ok(Expression::SafeCast(cast))
6563            }
6564            Expression::Case(mut case) => {
6565                if let Some(operand) = case.operand.take() {
6566                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
6567                }
6568                case.whens = case
6569                    .whens
6570                    .into_iter()
6571                    .map(|(condition, result)| {
6572                        Ok((
6573                            Self::rewrite_tsql_boolean_predicate_context(condition)?,
6574                            Self::rewrite_tsql_boolean_scalar_value(result)?,
6575                        ))
6576                    })
6577                    .collect::<Result<Vec<_>>>()?;
6578                if let Some(else_) = case.else_.take() {
6579                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
6580                }
6581                Ok(Expression::Case(case))
6582            }
6583            Expression::IfFunc(mut if_func) => {
6584                if_func.condition =
6585                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
6586                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
6587                if let Some(false_value) = if_func.false_value.take() {
6588                    if_func.false_value =
6589                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
6590                }
6591                Ok(Expression::IfFunc(if_func))
6592            }
6593            Expression::WindowFunction(mut window_function) => {
6594                window_function.this =
6595                    Self::rewrite_tsql_boolean_embedded_queries(window_function.this)?;
6596                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
6597                if let Some(mut keep) = window_function.keep.take() {
6598                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
6599                    window_function.keep = Some(keep);
6600                }
6601                Ok(Expression::WindowFunction(window_function))
6602            }
6603            Expression::WithinGroup(mut within_group) => {
6604                within_group.this = Self::rewrite_tsql_boolean_embedded_queries(within_group.this)?;
6605                within_group.order_by =
6606                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
6607                Ok(Expression::WithinGroup(within_group))
6608            }
6609            Expression::Subquery(mut subquery) => {
6610                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6611                Ok(Expression::Subquery(subquery))
6612            }
6613            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6614            other => Self::rewrite_tsql_boolean_embedded_queries(other),
6615        }
6616    }
6617
6618    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
6619        Self::rewrite_tsql_boolean_embedded_queries(expr)
6620    }
6621
6622    fn rewrite_tsql_boolean_embedded_queries(expr: Expression) -> Result<Expression> {
6623        transform_recursive(expr, &|e| match e {
6624            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
6625            Expression::Subquery(mut subquery) => {
6626                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
6627                Ok(Expression::Subquery(subquery))
6628            }
6629            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
6630                Self::rewrite_boolean_values_for_tsql(e)
6631            }
6632            other => Ok(other),
6633        })
6634    }
6635
6636    fn rewrite_tsql_boolean_ordered_values(
6637        ordered: Vec<crate::expressions::Ordered>,
6638    ) -> Result<Vec<crate::expressions::Ordered>> {
6639        ordered
6640            .into_iter()
6641            .map(|mut ordered| {
6642                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
6643                if let Some(with_fill) = ordered.with_fill.take() {
6644                    ordered.with_fill = Some(Box::new(
6645                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
6646                    ));
6647                }
6648                Ok(ordered)
6649            })
6650            .collect()
6651    }
6652
6653    fn rewrite_tsql_boolean_with_fill_values(
6654        mut with_fill: crate::expressions::WithFill,
6655    ) -> Result<crate::expressions::WithFill> {
6656        if let Some(from) = with_fill.from_.take() {
6657            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
6658        }
6659        if let Some(to) = with_fill.to.take() {
6660            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
6661        }
6662        if let Some(step) = with_fill.step.take() {
6663            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
6664        }
6665        if let Some(staleness) = with_fill.staleness.take() {
6666            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
6667                *staleness,
6668            )?));
6669        }
6670        if let Some(interpolate) = with_fill.interpolate.take() {
6671            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
6672                *interpolate,
6673            )?));
6674        }
6675        Ok(with_fill)
6676    }
6677
6678    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
6679        over.partition_by = std::mem::take(&mut over.partition_by)
6680            .into_iter()
6681            .map(Self::rewrite_tsql_boolean_scalar_value)
6682            .collect::<Result<Vec<_>>>()?;
6683        over.order_by =
6684            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
6685        Ok(())
6686    }
6687
6688    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
6689        match expr {
6690            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
6691            Expression::Eq(_)
6692            | Expression::Neq(_)
6693            | Expression::Lt(_)
6694            | Expression::Lte(_)
6695            | Expression::Gt(_)
6696            | Expression::Gte(_)
6697            | Expression::Is(_)
6698            | Expression::IsNull(_)
6699            | Expression::IsTrue(_)
6700            | Expression::IsFalse(_)
6701            | Expression::Like(_)
6702            | Expression::ILike(_)
6703            | Expression::StartsWith(_)
6704            | Expression::SimilarTo(_)
6705            | Expression::Glob(_)
6706            | Expression::RegexpLike(_)
6707            | Expression::In(_)
6708            | Expression::Between(_)
6709            | Expression::Exists(_)
6710            | Expression::And(_)
6711            | Expression::Or(_)
6712            | Expression::Not(_)
6713            | Expression::Any(_)
6714            | Expression::All(_)
6715            | Expression::NullSafeEq(_)
6716            | Expression::NullSafeNeq(_)
6717            | Expression::EqualNull(_) => true,
6718            _ => false,
6719        }
6720    }
6721
6722    fn tsql_boolean_value_case(predicate: Expression) -> Expression {
6723        let case = Expression::Case(Box::new(crate::expressions::Case {
6724            operand: None,
6725            whens: vec![(predicate, Expression::number(1))],
6726            else_: Some(Expression::number(0)),
6727            comments: Vec::new(),
6728            inferred_type: None,
6729        }));
6730
6731        Expression::Cast(Box::new(Cast {
6732            this: case,
6733            to: DataType::Boolean,
6734            trailing_comments: Vec::new(),
6735            double_colon_syntax: false,
6736            format: None,
6737            default: None,
6738            inferred_type: None,
6739        }))
6740    }
6741
6742    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
6743        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
6744    }
6745
6746    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
6747        macro_rules! rewrite_agg_filter {
6748            ($variant:ident, $agg:expr) => {{
6749                let mut agg = $agg;
6750                if let Some(filter) = agg.filter.take() {
6751                    let this = std::mem::replace(&mut agg.this, Expression::null());
6752                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6753                }
6754                Ok(Expression::$variant(agg))
6755            }};
6756        }
6757
6758        match expr {
6759            Expression::Filter(filter) => {
6760                let condition = match *filter.expression {
6761                    Expression::Where(where_) => where_.this,
6762                    other => other,
6763                };
6764                Ok(Self::push_filter_into_tsql_aggregate(
6765                    *filter.this,
6766                    condition,
6767                ))
6768            }
6769            Expression::AggregateFunction(mut agg) => {
6770                if let Some(filter) = agg.filter.take() {
6771                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
6772                }
6773                Ok(Expression::AggregateFunction(agg))
6774            }
6775            Expression::Count(mut count) => {
6776                if let Some(filter) = count.filter.take() {
6777                    let value = if count.star {
6778                        Expression::number(1)
6779                    } else {
6780                        count.this.take().unwrap_or_else(|| Expression::number(1))
6781                    };
6782                    count.star = false;
6783                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
6784                }
6785                Ok(Expression::Count(count))
6786            }
6787            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
6788            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
6789            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
6790            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
6791            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
6792            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
6793            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
6794            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
6795            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
6796            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
6797            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
6798            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
6799            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
6800            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
6801            Expression::First(agg) => rewrite_agg_filter!(First, agg),
6802            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
6803            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
6804            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
6805            Expression::ApproxCountDistinct(agg) => {
6806                rewrite_agg_filter!(ApproxCountDistinct, agg)
6807            }
6808            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
6809            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
6810            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
6811            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
6812            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
6813            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
6814            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
6815            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
6816            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
6817            Expression::StringAgg(mut agg) => {
6818                if let Some(filter) = agg.filter.take() {
6819                    let this = std::mem::replace(&mut agg.this, Expression::null());
6820                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6821                }
6822                Ok(Expression::StringAgg(agg))
6823            }
6824            Expression::GroupConcat(mut agg) => {
6825                if let Some(filter) = agg.filter.take() {
6826                    let this = std::mem::replace(&mut agg.this, Expression::null());
6827                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6828                }
6829                Ok(Expression::GroupConcat(agg))
6830            }
6831            Expression::ListAgg(mut agg) => {
6832                if let Some(filter) = agg.filter.take() {
6833                    let this = std::mem::replace(&mut agg.this, Expression::null());
6834                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6835                }
6836                Ok(Expression::ListAgg(agg))
6837            }
6838            Expression::WithinGroup(mut within_group) => {
6839                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
6840                Ok(Expression::WithinGroup(within_group))
6841            }
6842            other => Ok(other),
6843        }
6844    }
6845
6846    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
6847        macro_rules! push_agg_filter {
6848            ($variant:ident, $agg:expr) => {{
6849                let mut agg = $agg;
6850                let this = std::mem::replace(&mut agg.this, Expression::null());
6851                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6852                agg.filter = None;
6853                Expression::$variant(agg)
6854            }};
6855        }
6856
6857        match expr {
6858            Expression::AggregateFunction(mut agg) => {
6859                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
6860                Expression::AggregateFunction(agg)
6861            }
6862            Expression::Count(mut count) => {
6863                let value = if count.star {
6864                    Expression::number(1)
6865                } else {
6866                    count.this.take().unwrap_or_else(|| Expression::number(1))
6867                };
6868                count.star = false;
6869                count.filter = None;
6870                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
6871                Expression::Count(count)
6872            }
6873            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
6874            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
6875            Expression::Min(agg) => push_agg_filter!(Min, agg),
6876            Expression::Max(agg) => push_agg_filter!(Max, agg),
6877            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
6878            Expression::CountIf(mut agg) => {
6879                agg.filter = Some(filter);
6880                Expression::CountIf(agg)
6881            }
6882            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
6883            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
6884            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
6885            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
6886            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
6887            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
6888            Expression::Median(agg) => push_agg_filter!(Median, agg),
6889            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
6890            Expression::First(agg) => push_agg_filter!(First, agg),
6891            Expression::Last(agg) => push_agg_filter!(Last, agg),
6892            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
6893            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
6894            Expression::ApproxCountDistinct(agg) => {
6895                push_agg_filter!(ApproxCountDistinct, agg)
6896            }
6897            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
6898            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
6899            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
6900            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
6901            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
6902            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
6903            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
6904            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
6905            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
6906            Expression::StringAgg(mut agg) => {
6907                let this = std::mem::replace(&mut agg.this, Expression::null());
6908                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6909                agg.filter = None;
6910                Expression::StringAgg(agg)
6911            }
6912            Expression::GroupConcat(mut agg) => {
6913                let this = std::mem::replace(&mut agg.this, Expression::null());
6914                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6915                agg.filter = None;
6916                Expression::GroupConcat(agg)
6917            }
6918            Expression::ListAgg(mut agg) => {
6919                let this = std::mem::replace(&mut agg.this, Expression::null());
6920                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
6921                agg.filter = None;
6922                Expression::ListAgg(agg)
6923            }
6924            Expression::WithinGroup(mut within_group) => {
6925                within_group.this =
6926                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
6927                Expression::WithinGroup(within_group)
6928            }
6929            other => Expression::Filter(Box::new(crate::expressions::Filter {
6930                this: Box::new(other),
6931                expression: Box::new(filter),
6932            })),
6933        }
6934    }
6935
6936    fn rewrite_generic_aggregate_filter_for_tsql(
6937        agg: &mut crate::expressions::AggregateFunction,
6938        filter: Expression,
6939    ) {
6940        let is_count =
6941            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
6942        let is_count_star = is_count
6943            && (agg.args.is_empty()
6944                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
6945
6946        if is_count_star {
6947            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
6948                filter,
6949                Expression::number(1),
6950            )];
6951        } else if !agg.args.is_empty() {
6952            agg.args = agg
6953                .args
6954                .drain(..)
6955                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
6956                .collect();
6957        } else {
6958            agg.filter = Some(filter);
6959        }
6960    }
6961
6962    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
6963        Expression::Case(Box::new(crate::expressions::Case {
6964            operand: None,
6965            whens: vec![(filter, value)],
6966            else_: None,
6967            comments: Vec::new(),
6968            inferred_type: None,
6969        }))
6970    }
6971
6972    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
6973        let tokens = self.tokenize(sql)?;
6974        for (i, token) in tokens.iter().enumerate() {
6975            if token.token_type == TokenType::NullsafeEq {
6976                return Err(crate::error::Error::unsupported(
6977                    "PostgreSQL pgvector cosine distance operator <=>",
6978                    "SQLite",
6979                ));
6980            }
6981            if token.token_type == TokenType::Lt
6982                && tokens
6983                    .get(i + 1)
6984                    .is_some_and(|token| token.token_type == TokenType::Tilde)
6985                && tokens
6986                    .get(i + 2)
6987                    .is_some_and(|token| token.token_type == TokenType::Gt)
6988            {
6989                return Err(crate::error::Error::unsupported(
6990                    "PostgreSQL pgvector Hamming distance operator <~>",
6991                    "SQLite",
6992                ));
6993            }
6994        }
6995        Ok(())
6996    }
6997
6998    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
6999        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
7000            transform_recursive(expr, &|e| match e {
7001                Expression::Column(col)
7002                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
7003                {
7004                    Ok(Expression::Literal(Box::new(Literal::String(
7005                        col.name.name,
7006                    ))))
7007                }
7008                Expression::Identifier(id) if id.quoted => {
7009                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
7010                }
7011                _ => Ok(e),
7012            })
7013        }
7014
7015        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
7016            if let Some(default) = col.default.take() {
7017                col.default = Some(normalize_default_expr(default)?);
7018            }
7019
7020            for constraint in &mut col.constraints {
7021                if let ColumnConstraint::Default(default) = constraint {
7022                    *default = normalize_default_expr(default.clone())?;
7023                }
7024            }
7025
7026            Ok(())
7027        }
7028
7029        transform_recursive(expr, &|e| match e {
7030            Expression::CreateTable(mut ct) => {
7031                for column in &mut ct.columns {
7032                    normalize_column_default(column)?;
7033                }
7034                Ok(Expression::CreateTable(ct))
7035            }
7036            Expression::ColumnDef(mut col) => {
7037                normalize_column_default(&mut col)?;
7038                Ok(Expression::ColumnDef(col))
7039            }
7040            _ => Ok(e),
7041        })
7042    }
7043
7044    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
7045        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
7046            use crate::expressions::DataType;
7047
7048            match dt {
7049                DataType::Bit { .. } => DataType::Int {
7050                    length: None,
7051                    integer_spelling: true,
7052                },
7053                DataType::TextWithLength { .. } => DataType::Text,
7054                DataType::VarChar { .. } => DataType::Text,
7055                DataType::Char { .. } => DataType::Text,
7056                DataType::Timestamp { timezone: true, .. } => DataType::Text,
7057                DataType::Custom { name } => {
7058                    let base = name
7059                        .split_once('(')
7060                        .map_or(name.as_str(), |(base, _)| base)
7061                        .trim();
7062                    if base.eq_ignore_ascii_case("TSVECTOR")
7063                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
7064                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
7065                        || base.eq_ignore_ascii_case("NVARCHAR")
7066                        || base.eq_ignore_ascii_case("NCHAR")
7067                    {
7068                        DataType::Text
7069                    } else {
7070                        DataType::Custom { name }
7071                    }
7072                }
7073                _ => dt,
7074            }
7075        }
7076
7077        transform_recursive(expr, &|e| match e {
7078            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
7079            Expression::CreateTable(mut ct) => {
7080                for column in &mut ct.columns {
7081                    column.data_type = sqlite_type(column.data_type.clone());
7082                }
7083                Ok(Expression::CreateTable(ct))
7084            }
7085            _ => Ok(e),
7086        })
7087    }
7088
7089    fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
7090        fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
7091            use crate::expressions::DataType;
7092
7093            match dt {
7094                DataType::Decimal {
7095                    precision: None,
7096                    scale: None,
7097                } => DataType::Decimal {
7098                    precision: Some(38),
7099                    scale: Some(10),
7100                },
7101                DataType::Json | DataType::JsonB => DataType::Custom {
7102                    name: "VARCHAR(MAX)".to_string(),
7103                },
7104                _ => dt,
7105            }
7106        }
7107
7108        transform_recursive(expr, &|e| match e {
7109            Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
7110            Expression::CreateTable(mut ct) => {
7111                for column in &mut ct.columns {
7112                    column.data_type = fabric_type(column.data_type.clone());
7113                }
7114                Ok(Expression::CreateTable(ct))
7115            }
7116            Expression::ColumnDef(mut col) => {
7117                col.data_type = fabric_type(col.data_type);
7118                Ok(Expression::ColumnDef(col))
7119            }
7120            _ => Ok(e),
7121        })
7122    }
7123
7124    /// For DuckDB target: when FROM clause contains RANGE(n), replace
7125    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
7126    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
7127    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
7128        if let Expression::Select(mut select) = expr {
7129            // Check if FROM contains a RANGE function
7130            let has_range_from = if let Some(ref from) = select.from {
7131                from.expressions.iter().any(|e| {
7132                    // Check for direct RANGE(...) or aliased RANGE(...)
7133                    match e {
7134                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
7135                        Expression::Alias(a) => {
7136                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
7137                        }
7138                        _ => false,
7139                    }
7140                })
7141            } else {
7142                false
7143            };
7144
7145            if has_range_from {
7146                // Replace the ROW_NUMBER pattern in select expressions
7147                select.expressions = select
7148                    .expressions
7149                    .into_iter()
7150                    .map(|e| Self::replace_rownum_with_range(e))
7151                    .collect();
7152            }
7153
7154            Ok(Expression::Select(select))
7155        } else {
7156            Ok(expr)
7157        }
7158    }
7159
7160    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
7161    fn replace_rownum_with_range(expr: Expression) -> Expression {
7162        match expr {
7163            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
7164            Expression::Mod(op) => {
7165                let new_left = Self::try_replace_rownum_paren(&op.left);
7166                Expression::Mod(Box::new(crate::expressions::BinaryOp {
7167                    left: new_left,
7168                    right: op.right,
7169                    left_comments: op.left_comments,
7170                    operator_comments: op.operator_comments,
7171                    trailing_comments: op.trailing_comments,
7172                    inferred_type: op.inferred_type,
7173                }))
7174            }
7175            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
7176            Expression::Paren(p) => {
7177                let inner = Self::replace_rownum_with_range(p.this);
7178                Expression::Paren(Box::new(crate::expressions::Paren {
7179                    this: inner,
7180                    trailing_comments: p.trailing_comments,
7181                }))
7182            }
7183            Expression::Case(mut c) => {
7184                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
7185                c.whens = c
7186                    .whens
7187                    .into_iter()
7188                    .map(|(cond, then)| {
7189                        (
7190                            Self::replace_rownum_with_range(cond),
7191                            Self::replace_rownum_with_range(then),
7192                        )
7193                    })
7194                    .collect();
7195                if let Some(else_) = c.else_ {
7196                    c.else_ = Some(Self::replace_rownum_with_range(else_));
7197                }
7198                Expression::Case(c)
7199            }
7200            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
7201                left: Self::replace_rownum_with_range(op.left),
7202                right: op.right,
7203                left_comments: op.left_comments,
7204                operator_comments: op.operator_comments,
7205                trailing_comments: op.trailing_comments,
7206                inferred_type: op.inferred_type,
7207            })),
7208            Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
7209                left: Self::replace_rownum_with_range(op.left),
7210                right: op.right,
7211                left_comments: op.left_comments,
7212                operator_comments: op.operator_comments,
7213                trailing_comments: op.trailing_comments,
7214                inferred_type: op.inferred_type,
7215            })),
7216            Expression::Alias(mut a) => {
7217                a.this = Self::replace_rownum_with_range(a.this);
7218                Expression::Alias(a)
7219            }
7220            other => other,
7221        }
7222    }
7223
7224    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
7225    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
7226        if let Expression::Paren(ref p) = expr {
7227            if let Expression::Sub(ref sub) = p.this {
7228                if let Expression::WindowFunction(ref wf) = sub.left {
7229                    if let Expression::Function(ref f) = wf.this {
7230                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
7231                            if let Expression::Literal(ref lit) = sub.right {
7232                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
7233                                    if n == "1" {
7234                                        return Expression::column("range");
7235                                    }
7236                                }
7237                            }
7238                        }
7239                    }
7240                }
7241            }
7242        }
7243        expr.clone()
7244    }
7245
7246    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
7247    /// Converts:
7248    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
7249    /// To:
7250    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
7251    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
7252    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
7253        use crate::expressions::*;
7254        transform_recursive(expr, &|e| {
7255            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
7256            if let Expression::ArraySize(ref af) = e {
7257                if let Expression::Function(ref f) = af.this {
7258                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
7259                        let result = Self::convert_array_size_gda_snowflake(f)?;
7260                        return Ok(result);
7261                    }
7262                }
7263            }
7264
7265            let Expression::Select(mut sel) = e else {
7266                return Ok(e);
7267            };
7268
7269            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
7270            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
7271            let mut gda_join_idx: Option<usize> = None;
7272
7273            for (idx, join) in sel.joins.iter().enumerate() {
7274                // The join.this may be:
7275                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
7276                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
7277                let (unnest_ref, alias_name) = match &join.this {
7278                    Expression::Unnest(ref unnest) => {
7279                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
7280                        (Some(unnest.as_ref()), alias)
7281                    }
7282                    Expression::Alias(ref a) => {
7283                        if let Expression::Unnest(ref unnest) = a.this {
7284                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
7285                        } else {
7286                            (None, None)
7287                        }
7288                    }
7289                    _ => (None, None),
7290                };
7291
7292                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
7293                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
7294                    if let Expression::Function(ref f) = unnest.this {
7295                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
7296                            let start_expr = f.args[0].clone();
7297                            let end_expr = f.args[1].clone();
7298                            let step = f.args.get(2).cloned();
7299
7300                            // Extract unit from step interval
7301                            let unit = if let Some(Expression::Interval(ref iv)) = step {
7302                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
7303                                    Some(format!("{:?}", unit).to_ascii_uppercase())
7304                                } else if let Some(ref this) = iv.this {
7305                                    // The interval may be stored as a string like "1 MONTH"
7306                                    if let Expression::Literal(lit) = this {
7307                                        if let Literal::String(ref s) = lit.as_ref() {
7308                                            let parts: Vec<&str> = s.split_whitespace().collect();
7309                                            if parts.len() == 2 {
7310                                                Some(parts[1].to_ascii_uppercase())
7311                                            } else if parts.len() == 1 {
7312                                                // Single word like "MONTH" or just "1"
7313                                                let upper = parts[0].to_ascii_uppercase();
7314                                                if matches!(
7315                                                    upper.as_str(),
7316                                                    "YEAR"
7317                                                        | "QUARTER"
7318                                                        | "MONTH"
7319                                                        | "WEEK"
7320                                                        | "DAY"
7321                                                        | "HOUR"
7322                                                        | "MINUTE"
7323                                                        | "SECOND"
7324                                                ) {
7325                                                    Some(upper)
7326                                                } else {
7327                                                    None
7328                                                }
7329                                            } else {
7330                                                None
7331                                            }
7332                                        } else {
7333                                            None
7334                                        }
7335                                    } else {
7336                                        None
7337                                    }
7338                                } else {
7339                                    None
7340                                }
7341                            } else {
7342                                None
7343                            };
7344
7345                            if let Some(unit_str) = unit {
7346                                gda_info = Some((alias, start_expr, end_expr, unit_str));
7347                                gda_join_idx = Some(idx);
7348                            }
7349                        }
7350                    }
7351                }
7352                if gda_info.is_some() {
7353                    break;
7354                }
7355            }
7356
7357            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
7358                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
7359                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
7360                let result = Self::try_transform_from_gda_snowflake(sel);
7361                return result;
7362            };
7363            let join_idx = gda_join_idx.unwrap();
7364
7365            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
7366            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
7367            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
7368            let datediff = Expression::Function(Box::new(Function::new(
7369                "DATEDIFF".to_string(),
7370                vec![
7371                    Expression::boxed_column(Column {
7372                        name: Identifier::new(&unit_str),
7373                        table: None,
7374                        join_mark: false,
7375                        trailing_comments: vec![],
7376                        span: None,
7377                        inferred_type: None,
7378                    }),
7379                    start_expr.clone(),
7380                    end_expr.clone(),
7381                ],
7382            )));
7383            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7384                left: datediff,
7385                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7386                left_comments: vec![],
7387                operator_comments: vec![],
7388                trailing_comments: vec![],
7389                inferred_type: None,
7390            }));
7391
7392            let array_gen_range = Expression::Function(Box::new(Function::new(
7393                "ARRAY_GENERATE_RANGE".to_string(),
7394                vec![
7395                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7396                    datediff_plus_one,
7397                ],
7398            )));
7399
7400            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
7401            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7402                name: Identifier::new("INPUT"),
7403                value: array_gen_range,
7404                separator: crate::expressions::NamedArgSeparator::DArrow,
7405            }));
7406            let flatten = Expression::Function(Box::new(Function::new(
7407                "FLATTEN".to_string(),
7408                vec![flatten_input],
7409            )));
7410
7411            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
7412            let alias_table = Alias {
7413                this: flatten,
7414                alias: Identifier::new("_t0"),
7415                column_aliases: vec![
7416                    Identifier::new("seq"),
7417                    Identifier::new("key"),
7418                    Identifier::new("path"),
7419                    Identifier::new("index"),
7420                    Identifier::new(&alias_name),
7421                    Identifier::new("this"),
7422                ],
7423                alias_explicit_as: false,
7424                alias_keyword: None,
7425                pre_alias_comments: vec![],
7426                trailing_comments: vec![],
7427                inferred_type: None,
7428            };
7429            let lateral_expr = Expression::Lateral(Box::new(Lateral {
7430                this: Box::new(Expression::Alias(Box::new(alias_table))),
7431                view: None,
7432                outer: None,
7433                alias: None,
7434                alias_quoted: false,
7435                cross_apply: None,
7436                ordinality: None,
7437                column_aliases: vec![],
7438            }));
7439
7440            // Remove the original join and add to FROM expressions
7441            sel.joins.remove(join_idx);
7442            if let Some(ref mut from) = sel.from {
7443                from.expressions.push(lateral_expr);
7444            }
7445
7446            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
7447            let dateadd_expr = Expression::Function(Box::new(Function::new(
7448                "DATEADD".to_string(),
7449                vec![
7450                    Expression::boxed_column(Column {
7451                        name: Identifier::new(&unit_str),
7452                        table: None,
7453                        join_mark: false,
7454                        trailing_comments: vec![],
7455                        span: None,
7456                        inferred_type: None,
7457                    }),
7458                    Expression::Cast(Box::new(Cast {
7459                        this: Expression::boxed_column(Column {
7460                            name: Identifier::new(&alias_name),
7461                            table: None,
7462                            join_mark: false,
7463                            trailing_comments: vec![],
7464                            span: None,
7465                            inferred_type: None,
7466                        }),
7467                        to: DataType::Int {
7468                            length: None,
7469                            integer_spelling: false,
7470                        },
7471                        trailing_comments: vec![],
7472                        double_colon_syntax: false,
7473                        format: None,
7474                        default: None,
7475                        inferred_type: None,
7476                    })),
7477                    Expression::Cast(Box::new(Cast {
7478                        this: start_expr.clone(),
7479                        to: DataType::Date,
7480                        trailing_comments: vec![],
7481                        double_colon_syntax: false,
7482                        format: None,
7483                        default: None,
7484                        inferred_type: None,
7485                    })),
7486                ],
7487            )));
7488
7489            // Replace references to the alias in the SELECT list
7490            let new_exprs: Vec<Expression> = sel
7491                .expressions
7492                .iter()
7493                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
7494                .collect();
7495            sel.expressions = new_exprs;
7496
7497            Ok(Expression::Select(sel))
7498        })
7499    }
7500
7501    /// Helper: replace column references to `alias_name` with dateadd expression
7502    fn replace_column_ref_with_dateadd(
7503        expr: &Expression,
7504        alias_name: &str,
7505        dateadd: &Expression,
7506    ) -> Expression {
7507        use crate::expressions::*;
7508        match expr {
7509            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
7510                // Plain column reference -> DATEADD(...) AS alias_name
7511                Expression::Alias(Box::new(Alias {
7512                    this: dateadd.clone(),
7513                    alias: Identifier::new(alias_name),
7514                    column_aliases: vec![],
7515                    alias_explicit_as: false,
7516                    alias_keyword: None,
7517                    pre_alias_comments: vec![],
7518                    trailing_comments: vec![],
7519                    inferred_type: None,
7520                }))
7521            }
7522            Expression::Alias(a) => {
7523                // Check if the inner expression references the alias
7524                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
7525                Expression::Alias(Box::new(Alias {
7526                    this: new_this,
7527                    alias: a.alias.clone(),
7528                    column_aliases: a.column_aliases.clone(),
7529                    alias_explicit_as: false,
7530                    alias_keyword: None,
7531                    pre_alias_comments: a.pre_alias_comments.clone(),
7532                    trailing_comments: a.trailing_comments.clone(),
7533                    inferred_type: None,
7534                }))
7535            }
7536            _ => expr.clone(),
7537        }
7538    }
7539
7540    /// Helper: replace column references in inner expression (not top-level)
7541    fn replace_column_ref_inner(
7542        expr: &Expression,
7543        alias_name: &str,
7544        dateadd: &Expression,
7545    ) -> Expression {
7546        use crate::expressions::*;
7547        match expr {
7548            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
7549                dateadd.clone()
7550            }
7551            Expression::Add(op) => {
7552                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7553                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7554                Expression::Add(Box::new(BinaryOp {
7555                    left,
7556                    right,
7557                    left_comments: op.left_comments.clone(),
7558                    operator_comments: op.operator_comments.clone(),
7559                    trailing_comments: op.trailing_comments.clone(),
7560                    inferred_type: None,
7561                }))
7562            }
7563            Expression::Sub(op) => {
7564                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7565                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7566                Expression::Sub(Box::new(BinaryOp {
7567                    left,
7568                    right,
7569                    left_comments: op.left_comments.clone(),
7570                    operator_comments: op.operator_comments.clone(),
7571                    trailing_comments: op.trailing_comments.clone(),
7572                    inferred_type: None,
7573                }))
7574            }
7575            Expression::Mul(op) => {
7576                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
7577                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
7578                Expression::Mul(Box::new(BinaryOp {
7579                    left,
7580                    right,
7581                    left_comments: op.left_comments.clone(),
7582                    operator_comments: op.operator_comments.clone(),
7583                    trailing_comments: op.trailing_comments.clone(),
7584                    inferred_type: None,
7585                }))
7586            }
7587            _ => expr.clone(),
7588        }
7589    }
7590
7591    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
7592    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
7593    fn try_transform_from_gda_snowflake(
7594        mut sel: Box<crate::expressions::Select>,
7595    ) -> Result<Expression> {
7596        use crate::expressions::*;
7597
7598        // Extract GDA info from FROM clause
7599        let mut gda_info: Option<(
7600            usize,
7601            String,
7602            Expression,
7603            Expression,
7604            String,
7605            Option<(String, Vec<Identifier>)>,
7606        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
7607
7608        if let Some(ref from) = sel.from {
7609            for (idx, table_expr) in from.expressions.iter().enumerate() {
7610                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
7611                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
7612                let (unnest_opt, outer_alias_info) = match table_expr {
7613                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
7614                    Expression::Alias(ref a) => {
7615                        if let Expression::Unnest(ref unnest) = a.this {
7616                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
7617                            (Some(unnest.as_ref()), Some(alias_info))
7618                        } else {
7619                            (None, None)
7620                        }
7621                    }
7622                    _ => (None, None),
7623                };
7624
7625                if let Some(unnest) = unnest_opt {
7626                    // Check for GENERATE_DATE_ARRAY function
7627                    let func_opt = match &unnest.this {
7628                        Expression::Function(ref f)
7629                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
7630                                && f.args.len() >= 2 =>
7631                        {
7632                            Some(f)
7633                        }
7634                        // Also check for GenerateSeries (from earlier normalization)
7635                        _ => None,
7636                    };
7637
7638                    if let Some(f) = func_opt {
7639                        let start_expr = f.args[0].clone();
7640                        let end_expr = f.args[1].clone();
7641                        let step = f.args.get(2).cloned();
7642
7643                        // Extract unit and column name
7644                        let unit = Self::extract_interval_unit_str(&step);
7645                        let col_name = outer_alias_info
7646                            .as_ref()
7647                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
7648                            .unwrap_or_else(|| "value".to_string());
7649
7650                        if let Some(unit_str) = unit {
7651                            gda_info = Some((
7652                                idx,
7653                                col_name,
7654                                start_expr,
7655                                end_expr,
7656                                unit_str,
7657                                outer_alias_info,
7658                            ));
7659                            break;
7660                        }
7661                    }
7662                }
7663            }
7664        }
7665
7666        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
7667        else {
7668            return Ok(Expression::Select(sel));
7669        };
7670
7671        // Build the Snowflake subquery:
7672        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
7673        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
7674
7675        // DATEDIFF(unit, start, end)
7676        let datediff = Expression::Function(Box::new(Function::new(
7677            "DATEDIFF".to_string(),
7678            vec![
7679                Expression::boxed_column(Column {
7680                    name: Identifier::new(&unit_str),
7681                    table: None,
7682                    join_mark: false,
7683                    trailing_comments: vec![],
7684                    span: None,
7685                    inferred_type: None,
7686                }),
7687                start_expr.clone(),
7688                end_expr.clone(),
7689            ],
7690        )));
7691        // DATEDIFF(...) + 1
7692        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7693            left: datediff,
7694            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7695            left_comments: vec![],
7696            operator_comments: vec![],
7697            trailing_comments: vec![],
7698            inferred_type: None,
7699        }));
7700
7701        let array_gen_range = Expression::Function(Box::new(Function::new(
7702            "ARRAY_GENERATE_RANGE".to_string(),
7703            vec![
7704                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7705                datediff_plus_one,
7706            ],
7707        )));
7708
7709        // TABLE(FLATTEN(INPUT => ...))
7710        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7711            name: Identifier::new("INPUT"),
7712            value: array_gen_range,
7713            separator: crate::expressions::NamedArgSeparator::DArrow,
7714        }));
7715        let flatten = Expression::Function(Box::new(Function::new(
7716            "FLATTEN".to_string(),
7717            vec![flatten_input],
7718        )));
7719
7720        // Determine alias name for the table: use outer alias or _t0
7721        let table_alias_name = outer_alias_info
7722            .as_ref()
7723            .map(|(name, _)| name.clone())
7724            .unwrap_or_else(|| "_t0".to_string());
7725
7726        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
7727        let table_func =
7728            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
7729        let flatten_aliased = Expression::Alias(Box::new(Alias {
7730            this: table_func,
7731            alias: Identifier::new(&table_alias_name),
7732            column_aliases: vec![
7733                Identifier::new("seq"),
7734                Identifier::new("key"),
7735                Identifier::new("path"),
7736                Identifier::new("index"),
7737                Identifier::new(&col_name),
7738                Identifier::new("this"),
7739            ],
7740            alias_explicit_as: false,
7741            alias_keyword: None,
7742            pre_alias_comments: vec![],
7743            trailing_comments: vec![],
7744            inferred_type: None,
7745        }));
7746
7747        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
7748        let dateadd_expr = Expression::Function(Box::new(Function::new(
7749            "DATEADD".to_string(),
7750            vec![
7751                Expression::boxed_column(Column {
7752                    name: Identifier::new(&unit_str),
7753                    table: None,
7754                    join_mark: false,
7755                    trailing_comments: vec![],
7756                    span: None,
7757                    inferred_type: None,
7758                }),
7759                Expression::Cast(Box::new(Cast {
7760                    this: Expression::boxed_column(Column {
7761                        name: Identifier::new(&col_name),
7762                        table: None,
7763                        join_mark: false,
7764                        trailing_comments: vec![],
7765                        span: None,
7766                        inferred_type: None,
7767                    }),
7768                    to: DataType::Int {
7769                        length: None,
7770                        integer_spelling: false,
7771                    },
7772                    trailing_comments: vec![],
7773                    double_colon_syntax: false,
7774                    format: None,
7775                    default: None,
7776                    inferred_type: None,
7777                })),
7778                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
7779                start_expr.clone(),
7780            ],
7781        )));
7782        let dateadd_aliased = Expression::Alias(Box::new(Alias {
7783            this: dateadd_expr,
7784            alias: Identifier::new(&col_name),
7785            column_aliases: vec![],
7786            alias_explicit_as: false,
7787            alias_keyword: None,
7788            pre_alias_comments: vec![],
7789            trailing_comments: vec![],
7790            inferred_type: None,
7791        }));
7792
7793        // Build inner SELECT
7794        let mut inner_select = Select::new();
7795        inner_select.expressions = vec![dateadd_aliased];
7796        inner_select.from = Some(From {
7797            expressions: vec![flatten_aliased],
7798        });
7799
7800        let inner_select_expr = Expression::Select(Box::new(inner_select));
7801        let subquery = Expression::Subquery(Box::new(Subquery {
7802            this: inner_select_expr,
7803            alias: None,
7804            column_aliases: vec![],
7805            alias_explicit_as: false,
7806            alias_keyword: None,
7807            order_by: None,
7808            limit: None,
7809            offset: None,
7810            distribute_by: None,
7811            sort_by: None,
7812            cluster_by: None,
7813            lateral: false,
7814            modifiers_inside: false,
7815            trailing_comments: vec![],
7816            inferred_type: None,
7817        }));
7818
7819        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
7820        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
7821            Expression::Alias(Box::new(Alias {
7822                this: subquery,
7823                alias: Identifier::new(&alias_name),
7824                column_aliases: col_aliases,
7825                alias_explicit_as: false,
7826                alias_keyword: None,
7827                pre_alias_comments: vec![],
7828                trailing_comments: vec![],
7829                inferred_type: None,
7830            }))
7831        } else {
7832            subquery
7833        };
7834
7835        // Replace the FROM expression
7836        if let Some(ref mut from) = sel.from {
7837            from.expressions[from_idx] = replacement;
7838        }
7839
7840        Ok(Expression::Select(sel))
7841    }
7842
7843    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
7844    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
7845    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
7846    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
7847        use crate::expressions::*;
7848
7849        let start_expr = f.args[0].clone();
7850        let end_expr = f.args[1].clone();
7851        let step = f.args.get(2).cloned();
7852        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
7853        let col_name = "value";
7854
7855        // Build the inner subquery: same as try_transform_from_gda_snowflake
7856        let datediff = Expression::Function(Box::new(Function::new(
7857            "DATEDIFF".to_string(),
7858            vec![
7859                Expression::boxed_column(Column {
7860                    name: Identifier::new(&unit_str),
7861                    table: None,
7862                    join_mark: false,
7863                    trailing_comments: vec![],
7864                    span: None,
7865                    inferred_type: None,
7866                }),
7867                start_expr.clone(),
7868                end_expr.clone(),
7869            ],
7870        )));
7871        // DATEDIFF(...) + 1
7872        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
7873            left: datediff,
7874            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
7875            left_comments: vec![],
7876            operator_comments: vec![],
7877            trailing_comments: vec![],
7878            inferred_type: None,
7879        }));
7880
7881        let array_gen_range = Expression::Function(Box::new(Function::new(
7882            "ARRAY_GENERATE_RANGE".to_string(),
7883            vec![
7884                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
7885                datediff_plus_one,
7886            ],
7887        )));
7888
7889        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
7890            name: Identifier::new("INPUT"),
7891            value: array_gen_range,
7892            separator: crate::expressions::NamedArgSeparator::DArrow,
7893        }));
7894        let flatten = Expression::Function(Box::new(Function::new(
7895            "FLATTEN".to_string(),
7896            vec![flatten_input],
7897        )));
7898
7899        let table_func =
7900            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
7901        let flatten_aliased = Expression::Alias(Box::new(Alias {
7902            this: table_func,
7903            alias: Identifier::new("_t0"),
7904            column_aliases: vec![
7905                Identifier::new("seq"),
7906                Identifier::new("key"),
7907                Identifier::new("path"),
7908                Identifier::new("index"),
7909                Identifier::new(col_name),
7910                Identifier::new("this"),
7911            ],
7912            alias_explicit_as: false,
7913            alias_keyword: None,
7914            pre_alias_comments: vec![],
7915            trailing_comments: vec![],
7916            inferred_type: None,
7917        }));
7918
7919        let dateadd_expr = Expression::Function(Box::new(Function::new(
7920            "DATEADD".to_string(),
7921            vec![
7922                Expression::boxed_column(Column {
7923                    name: Identifier::new(&unit_str),
7924                    table: None,
7925                    join_mark: false,
7926                    trailing_comments: vec![],
7927                    span: None,
7928                    inferred_type: None,
7929                }),
7930                Expression::Cast(Box::new(Cast {
7931                    this: Expression::boxed_column(Column {
7932                        name: Identifier::new(col_name),
7933                        table: None,
7934                        join_mark: false,
7935                        trailing_comments: vec![],
7936                        span: None,
7937                        inferred_type: None,
7938                    }),
7939                    to: DataType::Int {
7940                        length: None,
7941                        integer_spelling: false,
7942                    },
7943                    trailing_comments: vec![],
7944                    double_colon_syntax: false,
7945                    format: None,
7946                    default: None,
7947                    inferred_type: None,
7948                })),
7949                start_expr.clone(),
7950            ],
7951        )));
7952        let dateadd_aliased = Expression::Alias(Box::new(Alias {
7953            this: dateadd_expr,
7954            alias: Identifier::new(col_name),
7955            column_aliases: vec![],
7956            alias_explicit_as: false,
7957            alias_keyword: None,
7958            pre_alias_comments: vec![],
7959            trailing_comments: vec![],
7960            inferred_type: None,
7961        }));
7962
7963        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
7964        let mut inner_select = Select::new();
7965        inner_select.expressions = vec![dateadd_aliased];
7966        inner_select.from = Some(From {
7967            expressions: vec![flatten_aliased],
7968        });
7969
7970        // Wrap in subquery for the inner part
7971        let inner_subquery = Expression::Subquery(Box::new(Subquery {
7972            this: Expression::Select(Box::new(inner_select)),
7973            alias: None,
7974            column_aliases: vec![],
7975            alias_explicit_as: false,
7976            alias_keyword: None,
7977            order_by: None,
7978            limit: None,
7979            offset: None,
7980            distribute_by: None,
7981            sort_by: None,
7982            cluster_by: None,
7983            lateral: false,
7984            modifiers_inside: false,
7985            trailing_comments: vec![],
7986            inferred_type: None,
7987        }));
7988
7989        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
7990        let star = Expression::Star(Star {
7991            table: None,
7992            except: None,
7993            replace: None,
7994            rename: None,
7995            trailing_comments: vec![],
7996            span: None,
7997        });
7998        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
7999            this: star,
8000            distinct: false,
8001            filter: None,
8002            order_by: vec![],
8003            name: Some("ARRAY_AGG".to_string()),
8004            ignore_nulls: None,
8005            having_max: None,
8006            limit: None,
8007            inferred_type: None,
8008        }));
8009
8010        let mut outer_select = Select::new();
8011        outer_select.expressions = vec![array_agg];
8012        outer_select.from = Some(From {
8013            expressions: vec![inner_subquery],
8014        });
8015
8016        // Wrap in a subquery
8017        let outer_subquery = Expression::Subquery(Box::new(Subquery {
8018            this: Expression::Select(Box::new(outer_select)),
8019            alias: None,
8020            column_aliases: vec![],
8021            alias_explicit_as: false,
8022            alias_keyword: None,
8023            order_by: None,
8024            limit: None,
8025            offset: None,
8026            distribute_by: None,
8027            sort_by: None,
8028            cluster_by: None,
8029            lateral: false,
8030            modifiers_inside: false,
8031            trailing_comments: vec![],
8032            inferred_type: None,
8033        }));
8034
8035        // ARRAY_SIZE(subquery)
8036        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
8037            outer_subquery,
8038        ))))
8039    }
8040
8041    /// Extract interval unit string from an optional step expression.
8042    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
8043        use crate::expressions::*;
8044        if let Some(Expression::Interval(ref iv)) = step {
8045            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
8046                return Some(format!("{:?}", unit).to_ascii_uppercase());
8047            }
8048            if let Some(ref this) = iv.this {
8049                if let Expression::Literal(lit) = this {
8050                    if let Literal::String(ref s) = lit.as_ref() {
8051                        let parts: Vec<&str> = s.split_whitespace().collect();
8052                        if parts.len() == 2 {
8053                            return Some(parts[1].to_ascii_uppercase());
8054                        } else if parts.len() == 1 {
8055                            let upper = parts[0].to_ascii_uppercase();
8056                            if matches!(
8057                                upper.as_str(),
8058                                "YEAR"
8059                                    | "QUARTER"
8060                                    | "MONTH"
8061                                    | "WEEK"
8062                                    | "DAY"
8063                                    | "HOUR"
8064                                    | "MINUTE"
8065                                    | "SECOND"
8066                            ) {
8067                                return Some(upper);
8068                            }
8069                        }
8070                    }
8071                }
8072            }
8073        }
8074        // Default to DAY if no step or no interval
8075        if step.is_none() {
8076            return Some("DAY".to_string());
8077        }
8078        None
8079    }
8080
8081    fn normalize_snowflake_pretty(mut sql: String) -> String {
8082        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
8083            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
8084        {
8085            sql = sql.replace(
8086                "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')",
8087                "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    )",
8088            );
8089
8090            sql = sql.replace(
8091                "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)",
8092                "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)",
8093            );
8094
8095            sql = sql.replace(
8096                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
8097                "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  )",
8098            );
8099        }
8100
8101        sql
8102    }
8103
8104    #[cfg(feature = "transpile")]
8105    fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
8106        match expr {
8107            Expression::Values(values) => Self::tsql_values_as_select(*values),
8108            Expression::Union(mut union) => {
8109                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
8110                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
8111                union.left = Self::wrap_tsql_values_set_operand(left);
8112                union.right = Self::wrap_tsql_values_set_operand(right);
8113                Expression::Union(union)
8114            }
8115            Expression::Intersect(mut intersect) => {
8116                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
8117                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
8118                intersect.left = Self::wrap_tsql_values_set_operand(left);
8119                intersect.right = Self::wrap_tsql_values_set_operand(right);
8120                Expression::Intersect(intersect)
8121            }
8122            Expression::Except(mut except) => {
8123                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
8124                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
8125                except.left = Self::wrap_tsql_values_set_operand(left);
8126                except.right = Self::wrap_tsql_values_set_operand(right);
8127                Expression::Except(except)
8128            }
8129            other => other,
8130        }
8131    }
8132
8133    #[cfg(feature = "transpile")]
8134    fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
8135        match expr {
8136            Expression::Values(values) => Self::tsql_values_as_select(*values),
8137            Expression::Union(mut union) => {
8138                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
8139                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
8140                union.left = Self::wrap_tsql_values_set_operand(left);
8141                union.right = Self::wrap_tsql_values_set_operand(right);
8142                Expression::Union(union)
8143            }
8144            Expression::Intersect(mut intersect) => {
8145                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
8146                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
8147                intersect.left = Self::wrap_tsql_values_set_operand(left);
8148                intersect.right = Self::wrap_tsql_values_set_operand(right);
8149                Expression::Intersect(intersect)
8150            }
8151            Expression::Except(mut except) => {
8152                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
8153                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
8154                except.left = Self::wrap_tsql_values_set_operand(left);
8155                except.right = Self::wrap_tsql_values_set_operand(right);
8156                Expression::Except(except)
8157            }
8158            other => other,
8159        }
8160    }
8161
8162    #[cfg(feature = "transpile")]
8163    fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
8164        let column_aliases = if values.column_aliases.is_empty() {
8165            let column_count = values
8166                .expressions
8167                .first()
8168                .map(|row| row.expressions.len())
8169                .unwrap_or(0);
8170            (1..=column_count)
8171                .map(|index| Identifier::new(format!("column{index}")))
8172                .collect()
8173        } else {
8174            std::mem::take(&mut values.column_aliases)
8175        };
8176
8177        values.alias = None;
8178
8179        let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
8180            this: Expression::Values(Box::new(values)),
8181            alias: Some(Identifier::new("_v")),
8182            column_aliases,
8183            alias_explicit_as: false,
8184            alias_keyword: None,
8185            order_by: None,
8186            limit: None,
8187            offset: None,
8188            distribute_by: None,
8189            sort_by: None,
8190            cluster_by: None,
8191            lateral: false,
8192            modifiers_inside: false,
8193            trailing_comments: Vec::new(),
8194            inferred_type: None,
8195        }));
8196
8197        let mut select = crate::expressions::Select::new();
8198        select.expressions = vec![Expression::star()];
8199        select.from = Some(From {
8200            expressions: vec![values_subquery],
8201        });
8202
8203        Expression::Select(Box::new(select))
8204    }
8205
8206    /// Apply cross-dialect semantic normalizations that depend on knowing both source and target.
8207    /// This handles cases where the same syntax has different semantics across dialects.
8208    fn cross_dialect_normalize(
8209        expr: Expression,
8210        source: DialectType,
8211        target: DialectType,
8212    ) -> Result<Expression> {
8213        use crate::expressions::{
8214            AggFunc, BinaryOp, Case, Cast, ConvertTimezone, DataType, DateTimeField, DateTruncFunc,
8215            Function, Identifier, IsNull, Literal, Null, Paren,
8216        };
8217
8218        // Helper to tag which kind of transform to apply
8219        #[derive(Debug)]
8220        enum Action {
8221            None,
8222            GreatestLeastNull,
8223            ArrayGenerateRange,
8224            Div0TypedDivision,
8225            ArrayAggCollectList,
8226            ArrayAggWithinGroupFilter,
8227            ArrayAggFilter,
8228            CastTimestampToDatetime,
8229            DateTruncWrapCast,
8230            ToDateToCast,
8231            ConvertTimezoneToExpr,
8232            SetToVariable,
8233            RegexpReplaceSnowflakeToDuckDB,
8234            BigQueryFunctionNormalize,
8235            BigQuerySafeDivide,
8236            BigQueryCastType,
8237            BigQueryToHexBare,        // _BQ_TO_HEX(x) with no LOWER/UPPER wrapper
8238            BigQueryToHexLower,       // LOWER(_BQ_TO_HEX(x))
8239            BigQueryToHexUpper,       // UPPER(_BQ_TO_HEX(x))
8240            BigQueryLastDayStripUnit, // LAST_DAY(date, MONTH) -> LAST_DAY(date)
8241            BigQueryCastFormat, // CAST(x AS type FORMAT 'fmt') -> PARSE_DATE/PARSE_TIMESTAMP etc.
8242            BigQueryAnyValueHaving, // ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
8243            BigQueryApproxQuantiles, // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
8244            GenericFunctionNormalize, // Cross-dialect function renaming (non-BigQuery sources)
8245            RegexpLikeToDuckDB,      // RegexpLike -> REGEXP_MATCHES for DuckDB target
8246            RegexpLikeToTsqlPatindex, // RegexpLike/RegexpILike -> PATINDEX(...) > 0 for TSQL/Fabric
8247            SimilarToToTsqlLike,     // SimilarTo -> LIKE for TSQL/Fabric-compatible patterns
8248            PostgresJsonBuildObjectToJsonObject, // json[b]_build_object -> JSON_OBJECT
8249            PostgresJsonAggToJsonArrayAgg, // json[b]_agg -> JSON_ARRAYAGG
8250            EpochConvert,            // Expression::Epoch -> target-specific epoch function
8251            EpochMsConvert,          // Expression::EpochMs -> target-specific epoch ms function
8252            TSQLTypeNormalize, // TSQL types (MONEY, SMALLMONEY, REAL, DATETIME2) -> standard types
8253            MySQLSafeDivide,   // MySQL a/b -> a / NULLIF(b, 0) with optional CAST
8254            NullsOrdering,     // Add NULLS FIRST/LAST for ORDER BY
8255            AlterTableRenameStripSchema, // ALTER TABLE db.t1 RENAME TO db.t2 -> ALTER TABLE db.t1 RENAME TO t2
8256            StringAggConvert,            // STRING_AGG/WITHIN GROUP -> target-specific aggregate
8257            GroupConcatConvert,          // GROUP_CONCAT -> target-specific aggregate
8258            TempTableHash,               // TSQL #table -> temp table normalization
8259            ArrayLengthConvert,          // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific
8260            DatePartUnquote, // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
8261            NvlClearOriginal, // Clear NVL original_name for cross-dialect transpilation
8262            HiveCastToTryCast, // Hive/Spark CAST -> TRY_CAST for targets that support it
8263            XorExpand,       // MySQL XOR -> (a AND NOT b) OR (NOT a AND b) for non-XOR targets
8264            CastTimestampStripTz, // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark
8265            JsonExtractToGetJsonObject, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
8266            JsonExtractScalarToGetJsonObject, // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
8267            JsonQueryValueConvert, // JsonQuery/JsonValue -> target-specific (ISNULL wrapper for TSQL, GET_JSON_OBJECT for Spark, etc.)
8268            JsonLiteralToJsonParse, // JSON 'x' -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake; also DuckDB CAST(x AS JSON)
8269            DuckDBCastJsonToVariant, // DuckDB CAST(x AS JSON) -> CAST(x AS VARIANT) for Snowflake
8270            DuckDBTryCastJsonToTryJsonParse, // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
8271            DuckDBJsonFuncToJsonParse, // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
8272            DuckDBJsonValidToIsJson,   // DuckDB json_valid(x) -> x IS JSON for Trino/Presto/Athena
8273            ArraySyntaxConvert,        // ARRAY[x] -> ARRAY(x) for Spark, [x] for BigQuery/DuckDB
8274            AtTimeZoneConvert, // AT TIME ZONE -> AT_TIMEZONE (Presto) / FROM_UTC_TIMESTAMP (Spark)
8275            DayOfWeekConvert,  // DAY_OF_WEEK -> dialect-specific
8276            MaxByMinByConvert, // MAX_BY/MIN_BY -> argMax/argMin for ClickHouse
8277            ArrayAggToCollectList, // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
8278            ArrayAggToGroupConcat, // ARRAY_AGG(x) -> GROUP_CONCAT(x) for MySQL-like targets
8279            ElementAtConvert, // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
8280            CurrentUserParens, // CURRENT_USER -> CURRENT_USER() for Snowflake
8281            CastToJsonForSpark, // CAST(x AS JSON) -> TO_JSON(x) for Spark
8282            CastJsonToFromJson, // CAST(JSON_PARSE(literal) AS ARRAY/MAP) -> FROM_JSON(literal, type_string)
8283            ToJsonConvert,      // TO_JSON(x) -> JSON_FORMAT(CAST(x AS JSON)) for Presto etc.
8284            ArrayAggNullFilter, // ARRAY_AGG(x) FILTER(WHERE cond) -> add AND NOT x IS NULL for DuckDB
8285            ArrayAggIgnoreNullsDuckDB, // ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, ...) for DuckDB
8286            BigQueryPercentileContToDuckDB, // PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
8287            BigQueryArraySelectAsStructToSnowflake, // ARRAY(SELECT AS STRUCT ...) -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT(...)))
8288            CountDistinctMultiArg, // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END)
8289            VarianceToClickHouse,  // Expression::Variance -> varSamp for ClickHouse
8290            StddevToClickHouse,    // Expression::Stddev -> stddevSamp for ClickHouse
8291            ApproxQuantileConvert, // Expression::ApproxQuantile -> APPROX_PERCENTILE for Snowflake
8292            ArrayIndexConvert,     // array[1] -> array[0] for BigQuery (1-based to 0-based)
8293            DollarParamConvert,    // $foo -> @foo for BigQuery
8294            TablesampleReservoir, // TABLESAMPLE (n ROWS) -> TABLESAMPLE RESERVOIR (n ROWS) for DuckDB
8295            BitAggFloatCast, // BIT_OR/BIT_AND/BIT_XOR float arg -> CAST(ROUND(CAST(arg)) AS INT) for DuckDB
8296            BitAggSnowflakeRename, // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG etc. for Snowflake
8297            StrftimeCastTimestamp, // CAST TIMESTAMP -> TIMESTAMP_NTZ for Spark in STRFTIME
8298            AnyValueIgnoreNulls, // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
8299            CreateTableStripComment, // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
8300            EscapeStringNormalize,   // e'Hello\nworld' literal newline -> \n
8301            AnyToExists,             // PostgreSQL x <op> ANY(array) -> EXISTS(array, x -> ...)
8302            ArrayConcatBracketConvert, // [1,2] -> ARRAY[1,2] for PostgreSQL in ARRAY_CAT
8303            SnowflakeIntervalFormat, // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
8304            AlterTableToSpRename,    // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
8305            StraightJoinCase,        // STRAIGHT_JOIN -> straight_join for DuckDB
8306            RespectNullsConvert,     // RESPECT NULLS window function handling
8307            MysqlNullsOrdering,      // MySQL doesn't support NULLS ordering
8308            MysqlNullsLastRewrite, // Add CASE WHEN to ORDER BY for DuckDB -> MySQL (NULLS LAST simulation)
8309            BigQueryNullsOrdering, // BigQuery doesn't support NULLS FIRST/LAST - strip
8310            SnowflakeFloatProtect, // Protect FLOAT from being converted to DOUBLE by Snowflake target transform
8311            JsonToGetPath,         // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
8312            FilterToIff,           // FILTER(WHERE) -> IFF wrapping for Snowflake
8313            AggFilterToIff, // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
8314            StructToRow,    // DuckDB struct -> Presto ROW / BigQuery STRUCT
8315            SparkStructConvert, // Spark STRUCT(x AS col1, ...) -> ROW/DuckDB struct
8316            DecimalDefaultPrecision, // DECIMAL -> DECIMAL(18, 3) for Snowflake in BIT agg
8317            ApproxCountDistinctToApproxDistinct, // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
8318            CollectListToArrayAgg,               // COLLECT_LIST -> ARRAY_AGG for Presto/DuckDB
8319            CollectSetConvert, // COLLECT_SET -> SET_AGG/ARRAY_AGG(DISTINCT)/ARRAY_UNIQUE_AGG
8320            PercentileConvert, // PERCENTILE -> QUANTILE/APPROX_PERCENTILE
8321            CorrIsnanWrap, // CORR(a,b) -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END
8322            TruncToDateTrunc, // TRUNC(ts, unit) -> DATE_TRUNC(unit, ts)
8323            ArrayContainsConvert, // ARRAY_CONTAINS -> CONTAINS/target-specific
8324            StrPositionExpand, // StrPosition with position -> complex STRPOS expansion for Presto/DuckDB
8325            TablesampleSnowflakeStrip, // Strip method and PERCENT for Snowflake target
8326            FirstToAnyValue,   // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
8327            MonthsBetweenConvert, // Expression::MonthsBetween -> target-specific
8328            CurrentUserSparkParens, // CURRENT_USER -> CURRENT_USER() for Spark
8329            SparkDateFuncCast, // MONTH/YEAR/DAY('str') -> MONTH/YEAR/DAY(CAST('str' AS DATE)) from Spark
8330            MapFromArraysConvert, // Expression::MapFromArrays -> MAP/OBJECT_CONSTRUCT/MAP_FROM_ARRAYS
8331            AddMonthsConvert,     // Expression::AddMonths -> target-specific DATEADD/DATE_ADD
8332            PercentileContConvert, // PERCENTILE_CONT/DISC WITHIN GROUP -> APPROX_PERCENTILE/PERCENTILE_APPROX
8333            GenerateSeriesConvert, // GENERATE_SERIES -> SEQUENCE/UNNEST(SEQUENCE)/EXPLODE(SEQUENCE)
8334            ConcatCoalesceWrap, // CONCAT(a, b) -> CONCAT(COALESCE(CAST(a), ''), ...) for Presto/ClickHouse
8335            PipeConcatToConcat, // a || b -> CONCAT(CAST(a), CAST(b)) for Presto
8336            DivFuncConvert,     // DIV(a, b) -> target-specific integer division
8337            CbrtToPower,        // CBRT(x) -> POWER(CAST(x AS FLOAT), 1.0 / 3.0)
8338            JsonObjectAggConvert, // JSON_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
8339            JsonbExistsConvert, // JSONB_EXISTS -> JSON_EXISTS for DuckDB
8340            DateBinConvert,     // DATE_BIN -> TIME_BUCKET for DuckDB
8341            MysqlCastCharToText, // MySQL CAST(x AS CHAR) -> CAST(x AS TEXT/VARCHAR/STRING) for targets
8342            SparkCastVarcharToString, // Spark CAST(x AS VARCHAR/CHAR) -> CAST(x AS STRING) for Spark targets
8343            JsonExtractToArrow,       // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB
8344            JsonExtractToTsql, // JSON operators/functions -> T-SQL/Fabric JSON_QUERY/JSON_VALUE
8345            JsonExtractToClickHouse, // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
8346            JsonExtractScalarConvert, // JSON_EXTRACT_SCALAR -> target-specific (PostgreSQL, Snowflake, SQLite)
8347            JsonPathNormalize, // Normalize JSON path format (brackets, wildcards, quotes) for various dialects
8348            MinMaxToLeastGreatest, // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
8349            ClickHouseUniqToApproxCountDistinct, // uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
8350            ClickHouseAnyToAnyValue, // any(x) -> ANY_VALUE(x) for non-ClickHouse targets
8351            OracleVarchar2ToVarchar, // VARCHAR2(N CHAR/BYTE) -> VARCHAR(N) for non-Oracle targets
8352            Nvl2Expand,              // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END
8353            IfnullToCoalesce,        // IFNULL(a, b) -> COALESCE(a, b)
8354            IsAsciiConvert,          // IS_ASCII(x) -> dialect-specific ASCII check
8355            StrPositionConvert,      // STR_POSITION(haystack, needle[, pos]) -> dialect-specific
8356            DecodeSimplify,          // DECODE with null-safe -> simple = comparison
8357            ArraySumConvert,         // ARRAY_SUM -> target-specific
8358            ArraySizeConvert,        // ARRAY_SIZE -> target-specific
8359            ArrayAnyConvert,         // ARRAY_ANY -> target-specific
8360            CastTimestamptzToFunc,   // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) for MySQL/StarRocks
8361            TsOrDsToDateConvert,     // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific
8362            TsOrDsToDateStrConvert,  // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
8363            DateStrToDateConvert,    // DATE_STR_TO_DATE(x) -> CAST(x AS DATE)
8364            TimeStrToDateConvert,    // TIME_STR_TO_DATE(x) -> CAST(x AS DATE)
8365            TimeStrToTimeConvert,    // TIME_STR_TO_TIME(x) -> CAST(x AS TIMESTAMP)
8366            DateToDateStrConvert,    // DATE_TO_DATE_STR(x) -> CAST(x AS TEXT/VARCHAR/STRING)
8367            DateToDiConvert, // DATE_TO_DI(x) -> dialect-specific (CAST date to YYYYMMDD integer)
8368            DiToDateConvert, // DI_TO_DATE(x) -> dialect-specific (integer YYYYMMDD to date)
8369            TsOrDiToDiConvert, // TS_OR_DI_TO_DI(x) -> dialect-specific
8370            UnixToStrConvert, // UNIX_TO_STR(x, fmt) -> dialect-specific
8371            UnixToTimeConvert, // UNIX_TO_TIME(x) -> dialect-specific
8372            UnixToTimeStrConvert, // UNIX_TO_TIME_STR(x) -> dialect-specific
8373            TimeToUnixConvert, // TIME_TO_UNIX(x) -> dialect-specific
8374            TimeToStrConvert, // TIME_TO_STR(x, fmt) -> dialect-specific
8375            StrToUnixConvert, // STR_TO_UNIX(x, fmt) -> dialect-specific
8376            DateTruncSwapArgs, // DATE_TRUNC('unit', x) -> DATE_TRUNC(x, unit) / TRUNC(x, unit)
8377            TimestampTruncConvert, // TIMESTAMP_TRUNC(x, UNIT[, tz]) -> dialect-specific
8378            StrToDateConvert, // STR_TO_DATE(x, fmt) from Generic -> CAST(StrToTime(x,fmt) AS DATE)
8379            TsOrDsAddConvert, // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> DATE_ADD per dialect
8380            DateFromUnixDateConvert, // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
8381            TimeStrToUnixConvert, // TIME_STR_TO_UNIX(x) -> dialect-specific
8382            TimeToTimeStrConvert, // TIME_TO_TIME_STR(x) -> CAST(x AS type)
8383            CreateTableLikeToCtas, // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
8384            CreateTableLikeToSelectInto, // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
8385            CreateTableLikeToAs, // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
8386            ArrayRemoveConvert, // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
8387            ArrayReverseConvert, // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
8388            JsonKeysConvert,    // JSON_KEYS -> JSON_OBJECT_KEYS/OBJECT_KEYS
8389            ParseJsonStrip,     // PARSE_JSON(x) -> x (strip wrapper)
8390            ArraySizeDrill,     // ARRAY_SIZE -> REPEATED_COUNT for Drill
8391            WeekOfYearToWeekIso, // WEEKOFYEAR -> WEEKISO for Snowflake cross-dialect
8392            RegexpSubstrSnowflakeToDuckDB, // REGEXP_SUBSTR(s, p, ...) -> REGEXP_EXTRACT variants for DuckDB
8393            RegexpSubstrSnowflakeIdentity, // REGEXP_SUBSTR/REGEXP_SUBSTR_ALL strip trailing group=0 for Snowflake identity
8394            RegexpSubstrAllSnowflakeToDuckDB, // REGEXP_SUBSTR_ALL(s, p, ...) -> REGEXP_EXTRACT_ALL variants for DuckDB
8395            RegexpCountSnowflakeToDuckDB, // REGEXP_COUNT(s, p, ...) -> LENGTH(REGEXP_EXTRACT_ALL(...)) for DuckDB
8396            RegexpInstrSnowflakeToDuckDB, // REGEXP_INSTR(s, p, ...) -> complex CASE expression for DuckDB
8397            RegexpReplacePositionSnowflakeToDuckDB, // REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
8398            RlikeSnowflakeToDuckDB, // RLIKE(a, b[, flags]) -> REGEXP_FULL_MATCH(a, b[, flags]) for DuckDB
8399            RegexpExtractAllToSnowflake, // BigQuery REGEXP_EXTRACT_ALL -> REGEXP_SUBSTR_ALL for Snowflake
8400            ArrayExceptConvert, // ARRAY_EXCEPT -> DuckDB complex CASE / Snowflake ARRAY_EXCEPT / Presto ARRAY_EXCEPT
8401            ArrayPositionSnowflakeSwap, // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
8402            RegexpLikeExasolAnchor, // RegexpLike -> Exasol REGEXP_LIKE with .*pattern.* anchoring
8403            ArrayDistinctConvert,   // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
8404            ArrayDistinctClickHouse, // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
8405            ArrayContainsDuckDBConvert, // ARRAY_CONTAINS -> DuckDB CASE with NULL-aware check
8406            SnowflakeWindowFrameStrip, // Strip default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for Snowflake target
8407            SnowflakeWindowFrameAdd, // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING for non-Snowflake target
8408            SnowflakeArrayPositionToDuckDB, // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB
8409        }
8410
8411        // Handle SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake/etc.
8412        let expr = if matches!(source, DialectType::TSQL | DialectType::Fabric) {
8413            Self::transform_select_into(expr, source, target)
8414        } else {
8415            expr
8416        };
8417
8418        // Strip OFFSET ROWS for non-TSQL/Oracle targets
8419        let expr = if !matches!(
8420            target,
8421            DialectType::TSQL | DialectType::Oracle | DialectType::Fabric
8422        ) {
8423            if let Expression::Select(mut select) = expr {
8424                if let Some(ref mut offset) = select.offset {
8425                    offset.rows = None;
8426                }
8427                Expression::Select(select)
8428            } else {
8429                expr
8430            }
8431        } else {
8432            expr
8433        };
8434
8435        // Oracle: LIMIT -> FETCH FIRST, OFFSET -> OFFSET ROWS
8436        let expr = if matches!(target, DialectType::Oracle) {
8437            if let Expression::Select(mut select) = expr {
8438                if let Some(limit) = select.limit.take() {
8439                    // Convert LIMIT to FETCH FIRST n ROWS ONLY
8440                    select.fetch = Some(crate::expressions::Fetch {
8441                        direction: "FIRST".to_string(),
8442                        count: Some(limit.this),
8443                        percent: false,
8444                        rows: true,
8445                        with_ties: false,
8446                    });
8447                }
8448                // Add ROWS to OFFSET if present
8449                if let Some(ref mut offset) = select.offset {
8450                    offset.rows = Some(true);
8451                }
8452                Expression::Select(select)
8453            } else {
8454                expr
8455            }
8456        } else {
8457            expr
8458        };
8459
8460        // Handle CreateTable WITH properties transformation before recursive transforms
8461        let expr = if let Expression::CreateTable(mut ct) = expr {
8462            Self::transform_create_table_properties(&mut ct, source, target);
8463
8464            // Handle Hive-style PARTITIONED BY (col_name type, ...) -> target-specific
8465            // When the PARTITIONED BY clause contains column definitions, merge them into the
8466            // main column list and adjust the PARTITIONED BY clause for the target dialect.
8467            if matches!(
8468                source,
8469                DialectType::Hive | DialectType::Spark | DialectType::Databricks
8470            ) {
8471                let mut partition_col_names: Vec<String> = Vec::new();
8472                let mut partition_col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
8473                let mut has_col_def_partitions = false;
8474
8475                // Check if any PARTITIONED BY property contains ColumnDef expressions
8476                for prop in &ct.properties {
8477                    if let Expression::PartitionedByProperty(ref pbp) = prop {
8478                        if let Expression::Tuple(ref tuple) = *pbp.this {
8479                            for expr in &tuple.expressions {
8480                                if let Expression::ColumnDef(ref cd) = expr {
8481                                    has_col_def_partitions = true;
8482                                    partition_col_names.push(cd.name.name.clone());
8483                                    partition_col_defs.push(*cd.clone());
8484                                }
8485                            }
8486                        }
8487                    }
8488                }
8489
8490                if has_col_def_partitions && !matches!(target, DialectType::Hive) {
8491                    // Merge partition columns into main column list
8492                    for cd in partition_col_defs {
8493                        ct.columns.push(cd);
8494                    }
8495
8496                    // Replace PARTITIONED BY property with column-name-only version
8497                    ct.properties
8498                        .retain(|p| !matches!(p, Expression::PartitionedByProperty(_)));
8499
8500                    if matches!(
8501                        target,
8502                        DialectType::Presto | DialectType::Trino | DialectType::Athena
8503                    ) {
8504                        // Presto: WITH (PARTITIONED_BY=ARRAY['y', 'z'])
8505                        let array_elements: Vec<String> = partition_col_names
8506                            .iter()
8507                            .map(|n| format!("'{}'", n))
8508                            .collect();
8509                        let array_value = format!("ARRAY[{}]", array_elements.join(", "));
8510                        ct.with_properties
8511                            .push(("PARTITIONED_BY".to_string(), array_value));
8512                    } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
8513                        // Spark: PARTITIONED BY (y, z) - just column names
8514                        let name_exprs: Vec<Expression> = partition_col_names
8515                            .iter()
8516                            .map(|n| {
8517                                Expression::Column(Box::new(crate::expressions::Column {
8518                                    name: crate::expressions::Identifier::new(n.clone()),
8519                                    table: None,
8520                                    join_mark: false,
8521                                    trailing_comments: Vec::new(),
8522                                    span: None,
8523                                    inferred_type: None,
8524                                }))
8525                            })
8526                            .collect();
8527                        ct.properties.insert(
8528                            0,
8529                            Expression::PartitionedByProperty(Box::new(
8530                                crate::expressions::PartitionedByProperty {
8531                                    this: Box::new(Expression::Tuple(Box::new(
8532                                        crate::expressions::Tuple {
8533                                            expressions: name_exprs,
8534                                        },
8535                                    ))),
8536                                },
8537                            )),
8538                        );
8539                    }
8540                    // For DuckDB and other targets, just drop the PARTITIONED BY (already retained above)
8541                }
8542
8543                // Note: Non-ColumnDef partitions (e.g., function expressions like MONTHS(y))
8544                // are handled by transform_create_table_properties which runs first
8545            }
8546
8547            // Strip LOCATION property for Presto/Trino (not supported)
8548            if matches!(
8549                target,
8550                DialectType::Presto | DialectType::Trino | DialectType::Athena
8551            ) {
8552                ct.properties
8553                    .retain(|p| !matches!(p, Expression::LocationProperty(_)));
8554            }
8555
8556            // Strip table-level constraints for Spark/Hive/Databricks
8557            // Keep PRIMARY KEY and LIKE constraints but strip TSQL-specific modifiers; remove all others
8558            if matches!(
8559                target,
8560                DialectType::Spark | DialectType::Databricks | DialectType::Hive
8561            ) {
8562                ct.constraints.retain(|c| {
8563                    matches!(
8564                        c,
8565                        crate::expressions::TableConstraint::PrimaryKey { .. }
8566                            | crate::expressions::TableConstraint::Like { .. }
8567                    )
8568                });
8569                for constraint in &mut ct.constraints {
8570                    if let crate::expressions::TableConstraint::PrimaryKey {
8571                        columns,
8572                        modifiers,
8573                        ..
8574                    } = constraint
8575                    {
8576                        // Strip ASC/DESC from column names
8577                        for col in columns.iter_mut() {
8578                            if col.name.ends_with(" ASC") {
8579                                col.name = col.name[..col.name.len() - 4].to_string();
8580                            } else if col.name.ends_with(" DESC") {
8581                                col.name = col.name[..col.name.len() - 5].to_string();
8582                            }
8583                        }
8584                        // Strip TSQL-specific modifiers
8585                        modifiers.clustered = None;
8586                        modifiers.with_options.clear();
8587                        modifiers.on_filegroup = None;
8588                    }
8589                }
8590            }
8591
8592            // Databricks: IDENTITY columns with INT/INTEGER -> BIGINT
8593            if matches!(target, DialectType::Databricks) {
8594                for col in &mut ct.columns {
8595                    if col.auto_increment {
8596                        if matches!(col.data_type, crate::expressions::DataType::Int { .. }) {
8597                            col.data_type = crate::expressions::DataType::BigInt { length: None };
8598                        }
8599                    }
8600                }
8601            }
8602
8603            // Spark/Databricks: INTEGER -> INT in column definitions
8604            // Python sqlglot always outputs INT for Spark/Databricks
8605            if matches!(target, DialectType::Spark | DialectType::Databricks) {
8606                for col in &mut ct.columns {
8607                    if let crate::expressions::DataType::Int {
8608                        integer_spelling, ..
8609                    } = &mut col.data_type
8610                    {
8611                        *integer_spelling = false;
8612                    }
8613                }
8614            }
8615
8616            // Strip explicit NULL constraints for Hive/Spark (B INTEGER NULL -> B INTEGER)
8617            if matches!(target, DialectType::Hive | DialectType::Spark) {
8618                for col in &mut ct.columns {
8619                    // If nullable is explicitly true (NULL), change to None (omit it)
8620                    if col.nullable == Some(true) {
8621                        col.nullable = None;
8622                    }
8623                    // Also remove from constraints if stored there
8624                    col.constraints
8625                        .retain(|c| !matches!(c, crate::expressions::ColumnConstraint::Null));
8626                }
8627            }
8628
8629            // Strip TSQL ON filegroup for non-TSQL/Fabric targets
8630            if ct.on_property.is_some()
8631                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
8632            {
8633                ct.on_property = None;
8634            }
8635
8636            // Snowflake: strip ARRAY type parameters (ARRAY<INT> -> ARRAY, ARRAY<ARRAY<INT>> -> ARRAY)
8637            // Snowflake doesn't support typed arrays in DDL
8638            if matches!(target, DialectType::Snowflake) {
8639                fn strip_array_type_params(dt: &mut crate::expressions::DataType) {
8640                    if let crate::expressions::DataType::Array { .. } = dt {
8641                        *dt = crate::expressions::DataType::Custom {
8642                            name: "ARRAY".to_string(),
8643                        };
8644                    }
8645                }
8646                for col in &mut ct.columns {
8647                    strip_array_type_params(&mut col.data_type);
8648                }
8649            }
8650
8651            // PostgreSQL target: ensure IDENTITY columns have NOT NULL
8652            // If NOT NULL was explicit in source (present in constraint_order), preserve original order.
8653            // If NOT NULL was not explicit, add it after IDENTITY (GENERATED BY DEFAULT AS IDENTITY NOT NULL).
8654            if matches!(target, DialectType::PostgreSQL) {
8655                for col in &mut ct.columns {
8656                    if col.auto_increment && !col.constraint_order.is_empty() {
8657                        use crate::expressions::ConstraintType;
8658                        let has_explicit_not_null = col
8659                            .constraint_order
8660                            .iter()
8661                            .any(|ct| *ct == ConstraintType::NotNull);
8662
8663                        if has_explicit_not_null {
8664                            // Source had explicit NOT NULL - preserve original order
8665                            // Just ensure nullable is set
8666                            if col.nullable != Some(false) {
8667                                col.nullable = Some(false);
8668                            }
8669                        } else {
8670                            // Source didn't have explicit NOT NULL - build order with
8671                            // AutoIncrement + NotNull first, then remaining constraints
8672                            let mut new_order = Vec::new();
8673                            // Put AutoIncrement (IDENTITY) first, followed by synthetic NotNull
8674                            new_order.push(ConstraintType::AutoIncrement);
8675                            new_order.push(ConstraintType::NotNull);
8676                            // Add remaining constraints in original order (except AutoIncrement)
8677                            for ct_type in &col.constraint_order {
8678                                if *ct_type != ConstraintType::AutoIncrement {
8679                                    new_order.push(ct_type.clone());
8680                                }
8681                            }
8682                            col.constraint_order = new_order;
8683                            col.nullable = Some(false);
8684                        }
8685                    }
8686                }
8687            }
8688
8689            Expression::CreateTable(ct)
8690        } else {
8691            expr
8692        };
8693
8694        // Handle CreateView column stripping for Presto/Trino target
8695        let expr = if let Expression::CreateView(mut cv) = expr {
8696            // Presto/Trino: drop column list when view has a SELECT body
8697            if matches!(target, DialectType::Presto | DialectType::Trino) && !cv.columns.is_empty()
8698            {
8699                if !matches!(&cv.query, Expression::Null(_)) {
8700                    cv.columns.clear();
8701                }
8702            }
8703            Expression::CreateView(cv)
8704        } else {
8705            expr
8706        };
8707
8708        // Wrap bare VALUES in CTE bodies with SELECT * FROM (...) AS _values for generic/non-Presto targets
8709        let expr = if !matches!(
8710            target,
8711            DialectType::Presto | DialectType::Trino | DialectType::Athena
8712        ) {
8713            if let Expression::Select(mut select) = expr {
8714                if let Some(ref mut with) = select.with {
8715                    for cte in &mut with.ctes {
8716                        if let Expression::Values(ref vals) = cte.this {
8717                            // Build: SELECT * FROM (VALUES ...) AS _values
8718                            let values_subquery =
8719                                Expression::Subquery(Box::new(crate::expressions::Subquery {
8720                                    this: Expression::Values(vals.clone()),
8721                                    alias: Some(Identifier::new("_values".to_string())),
8722                                    column_aliases: Vec::new(),
8723                                    alias_explicit_as: false,
8724                                    alias_keyword: None,
8725                                    order_by: None,
8726                                    limit: None,
8727                                    offset: None,
8728                                    distribute_by: None,
8729                                    sort_by: None,
8730                                    cluster_by: None,
8731                                    lateral: false,
8732                                    modifiers_inside: false,
8733                                    trailing_comments: Vec::new(),
8734                                    inferred_type: None,
8735                                }));
8736                            let mut new_select = crate::expressions::Select::new();
8737                            new_select.expressions =
8738                                vec![Expression::Star(crate::expressions::Star {
8739                                    table: None,
8740                                    except: None,
8741                                    replace: None,
8742                                    rename: None,
8743                                    trailing_comments: Vec::new(),
8744                                    span: None,
8745                                })];
8746                            new_select.from = Some(crate::expressions::From {
8747                                expressions: vec![values_subquery],
8748                            });
8749                            cte.this = Expression::Select(Box::new(new_select));
8750                        }
8751                    }
8752                }
8753                Expression::Select(select)
8754            } else {
8755                expr
8756            }
8757        } else {
8758            expr
8759        };
8760
8761        let expr = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
8762            Self::wrap_tsql_top_level_values(expr)
8763        } else {
8764            expr
8765        };
8766
8767        // PostgreSQL CREATE INDEX: add NULLS FIRST to index columns that don't have nulls ordering
8768        let expr = if matches!(target, DialectType::PostgreSQL) {
8769            if let Expression::CreateIndex(mut ci) = expr {
8770                for col in &mut ci.columns {
8771                    if col.nulls_first.is_none() {
8772                        col.nulls_first = Some(true);
8773                    }
8774                }
8775                Expression::CreateIndex(ci)
8776            } else {
8777                expr
8778            }
8779        } else {
8780            expr
8781        };
8782
8783        transform_recursive(expr, &|e| {
8784            if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
8785                && matches!(target, DialectType::TSQL | DialectType::Fabric)
8786            {
8787                if let Expression::Round(mut f) = e {
8788                    if f.decimals.is_none() {
8789                        f.decimals = Some(Expression::number(0));
8790                    }
8791                    return Ok(Expression::Round(f));
8792                }
8793
8794                if let Expression::Function(f) = &e {
8795                    if f.name.eq_ignore_ascii_case("ROUND") && f.args.len() == 1 {
8796                        let mut f = f.clone();
8797                        f.args.push(Expression::number(0));
8798                        return Ok(Expression::Function(f));
8799                    }
8800                }
8801
8802                if let Expression::Log(f) = e {
8803                    if f.base.is_none() {
8804                        return Ok(Expression::Function(Box::new(Function::new(
8805                            "LOG10".to_string(),
8806                            vec![f.this],
8807                        ))));
8808                    }
8809                    return Ok(Expression::Log(f));
8810                }
8811            }
8812
8813            // BigQuery CAST(ARRAY[STRUCT(...)] AS STRUCT_TYPE[]) -> DuckDB: convert unnamed Structs to ROW()
8814            // This converts auto-named struct literals {'_0': x, '_1': y} inside typed arrays to ROW(x, y)
8815            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
8816                if let Expression::Cast(ref c) = e {
8817                    // Check if this is a CAST of an array to a struct array type
8818                    let is_struct_array_cast =
8819                        matches!(&c.to, crate::expressions::DataType::Array { .. });
8820                    if is_struct_array_cast {
8821                        let has_auto_named_structs = match &c.this {
8822                            Expression::Array(arr) => arr.expressions.iter().any(|elem| {
8823                                if let Expression::Struct(s) = elem {
8824                                    s.fields.iter().all(|(name, _)| {
8825                                        name.as_ref().map_or(true, |n| {
8826                                            n.starts_with('_') && n[1..].parse::<usize>().is_ok()
8827                                        })
8828                                    })
8829                                } else {
8830                                    false
8831                                }
8832                            }),
8833                            Expression::ArrayFunc(arr) => arr.expressions.iter().any(|elem| {
8834                                if let Expression::Struct(s) = elem {
8835                                    s.fields.iter().all(|(name, _)| {
8836                                        name.as_ref().map_or(true, |n| {
8837                                            n.starts_with('_') && n[1..].parse::<usize>().is_ok()
8838                                        })
8839                                    })
8840                                } else {
8841                                    false
8842                                }
8843                            }),
8844                            _ => false,
8845                        };
8846                        if has_auto_named_structs {
8847                            let convert_struct_to_row = |elem: Expression| -> Expression {
8848                                if let Expression::Struct(s) = elem {
8849                                    let row_args: Vec<Expression> =
8850                                        s.fields.into_iter().map(|(_, v)| v).collect();
8851                                    Expression::Function(Box::new(Function::new(
8852                                        "ROW".to_string(),
8853                                        row_args,
8854                                    )))
8855                                } else {
8856                                    elem
8857                                }
8858                            };
8859                            let mut c_clone = c.as_ref().clone();
8860                            match &mut c_clone.this {
8861                                Expression::Array(arr) => {
8862                                    arr.expressions = arr
8863                                        .expressions
8864                                        .drain(..)
8865                                        .map(convert_struct_to_row)
8866                                        .collect();
8867                                }
8868                                Expression::ArrayFunc(arr) => {
8869                                    arr.expressions = arr
8870                                        .expressions
8871                                        .drain(..)
8872                                        .map(convert_struct_to_row)
8873                                        .collect();
8874                                }
8875                                _ => {}
8876                            }
8877                            return Ok(Expression::Cast(Box::new(c_clone)));
8878                        }
8879                    }
8880                }
8881            }
8882
8883            // BigQuery SELECT AS STRUCT -> DuckDB struct literal {'key': value, ...}
8884            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
8885                if let Expression::Select(ref sel) = e {
8886                    if sel.kind.as_deref() == Some("STRUCT") {
8887                        let mut fields = Vec::new();
8888                        for expr in &sel.expressions {
8889                            match expr {
8890                                Expression::Alias(a) => {
8891                                    fields.push((Some(a.alias.name.clone()), a.this.clone()));
8892                                }
8893                                Expression::Column(c) => {
8894                                    fields.push((Some(c.name.name.clone()), expr.clone()));
8895                                }
8896                                _ => {
8897                                    fields.push((None, expr.clone()));
8898                                }
8899                            }
8900                        }
8901                        let struct_lit =
8902                            Expression::Struct(Box::new(crate::expressions::Struct { fields }));
8903                        let mut new_select = sel.as_ref().clone();
8904                        new_select.kind = None;
8905                        new_select.expressions = vec![struct_lit];
8906                        return Ok(Expression::Select(Box::new(new_select)));
8907                    }
8908                }
8909            }
8910
8911            // Convert @variable -> ${variable} for Spark/Hive/Databricks
8912            if matches!(source, DialectType::TSQL | DialectType::Fabric)
8913                && matches!(
8914                    target,
8915                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
8916                )
8917            {
8918                if let Expression::Parameter(ref p) = e {
8919                    if p.style == crate::expressions::ParameterStyle::At {
8920                        if let Some(ref name) = p.name {
8921                            return Ok(Expression::Parameter(Box::new(
8922                                crate::expressions::Parameter {
8923                                    name: Some(name.clone()),
8924                                    index: p.index,
8925                                    style: crate::expressions::ParameterStyle::DollarBrace,
8926                                    quoted: p.quoted,
8927                                    string_quoted: p.string_quoted,
8928                                    expression: None,
8929                                },
8930                            )));
8931                        }
8932                    }
8933                }
8934                // Also handle Column("@x") -> Parameter("x", DollarBrace) for TSQL vars
8935                if let Expression::Column(ref col) = e {
8936                    if col.name.name.starts_with('@') && col.table.is_none() {
8937                        let var_name = col.name.name.trim_start_matches('@').to_string();
8938                        return Ok(Expression::Parameter(Box::new(
8939                            crate::expressions::Parameter {
8940                                name: Some(var_name),
8941                                index: None,
8942                                style: crate::expressions::ParameterStyle::DollarBrace,
8943                                quoted: false,
8944                                string_quoted: false,
8945                                expression: None,
8946                            },
8947                        )));
8948                    }
8949                }
8950            }
8951
8952            // Convert @variable -> variable in SET statements for Spark/Databricks
8953            if matches!(source, DialectType::TSQL | DialectType::Fabric)
8954                && matches!(target, DialectType::Spark | DialectType::Databricks)
8955            {
8956                if let Expression::SetStatement(ref s) = e {
8957                    let mut new_items = s.items.clone();
8958                    let mut changed = false;
8959                    for item in &mut new_items {
8960                        // Strip @ from the SET name (Parameter style)
8961                        if let Expression::Parameter(ref p) = item.name {
8962                            if p.style == crate::expressions::ParameterStyle::At {
8963                                if let Some(ref name) = p.name {
8964                                    item.name = Expression::Identifier(Identifier::new(name));
8965                                    changed = true;
8966                                }
8967                            }
8968                        }
8969                        // Strip @ from the SET name (Identifier style - SET parser)
8970                        if let Expression::Identifier(ref id) = item.name {
8971                            if id.name.starts_with('@') {
8972                                let var_name = id.name.trim_start_matches('@').to_string();
8973                                item.name = Expression::Identifier(Identifier::new(&var_name));
8974                                changed = true;
8975                            }
8976                        }
8977                        // Strip @ from the SET name (Column style - alternative parsing)
8978                        if let Expression::Column(ref col) = item.name {
8979                            if col.name.name.starts_with('@') && col.table.is_none() {
8980                                let var_name = col.name.name.trim_start_matches('@').to_string();
8981                                item.name = Expression::Identifier(Identifier::new(&var_name));
8982                                changed = true;
8983                            }
8984                        }
8985                    }
8986                    if changed {
8987                        let mut new_set = (**s).clone();
8988                        new_set.items = new_items;
8989                        return Ok(Expression::SetStatement(Box::new(new_set)));
8990                    }
8991                }
8992            }
8993
8994            // Strip NOLOCK hint for non-TSQL targets
8995            if matches!(source, DialectType::TSQL | DialectType::Fabric)
8996                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
8997            {
8998                if let Expression::Table(ref tr) = e {
8999                    if !tr.hints.is_empty() {
9000                        let mut new_tr = tr.clone();
9001                        new_tr.hints.clear();
9002                        return Ok(Expression::Table(new_tr));
9003                    }
9004                }
9005            }
9006
9007            // Snowflake: TRUE IS TRUE -> TRUE, FALSE IS FALSE -> FALSE
9008            // Snowflake simplifies IS TRUE/IS FALSE on boolean literals
9009            if matches!(target, DialectType::Snowflake) {
9010                if let Expression::IsTrue(ref itf) = e {
9011                    if let Expression::Boolean(ref b) = itf.this {
9012                        if !itf.not {
9013                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9014                                value: b.value,
9015                            }));
9016                        } else {
9017                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9018                                value: !b.value,
9019                            }));
9020                        }
9021                    }
9022                }
9023                if let Expression::IsFalse(ref itf) = e {
9024                    if let Expression::Boolean(ref b) = itf.this {
9025                        if !itf.not {
9026                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9027                                value: !b.value,
9028                            }));
9029                        } else {
9030                            return Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
9031                                value: b.value,
9032                            }));
9033                        }
9034                    }
9035                }
9036            }
9037
9038            // BigQuery: split dotted backtick identifiers in table names
9039            // e.g., `a.b.c` -> "a"."b"."c" when source is BigQuery and target is not BigQuery
9040            if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
9041                if let Expression::CreateTable(ref ct) = e {
9042                    let mut changed = false;
9043                    let mut new_ct = ct.clone();
9044                    // Split the table name
9045                    if ct.name.schema.is_none() && ct.name.name.name.contains('.') {
9046                        let parts: Vec<&str> = ct.name.name.name.split('.').collect();
9047                        // Use quoted identifiers when the original was quoted (backtick in BigQuery)
9048                        let was_quoted = ct.name.name.quoted;
9049                        let mk_id = |s: &str| {
9050                            if was_quoted {
9051                                Identifier::quoted(s)
9052                            } else {
9053                                Identifier::new(s)
9054                            }
9055                        };
9056                        if parts.len() == 3 {
9057                            new_ct.name.catalog = Some(mk_id(parts[0]));
9058                            new_ct.name.schema = Some(mk_id(parts[1]));
9059                            new_ct.name.name = mk_id(parts[2]);
9060                            changed = true;
9061                        } else if parts.len() == 2 {
9062                            new_ct.name.schema = Some(mk_id(parts[0]));
9063                            new_ct.name.name = mk_id(parts[1]);
9064                            changed = true;
9065                        }
9066                    }
9067                    // Split the clone source name
9068                    if let Some(ref clone_src) = ct.clone_source {
9069                        if clone_src.schema.is_none() && clone_src.name.name.contains('.') {
9070                            let parts: Vec<&str> = clone_src.name.name.split('.').collect();
9071                            let was_quoted = clone_src.name.quoted;
9072                            let mk_id = |s: &str| {
9073                                if was_quoted {
9074                                    Identifier::quoted(s)
9075                                } else {
9076                                    Identifier::new(s)
9077                                }
9078                            };
9079                            let mut new_src = clone_src.clone();
9080                            if parts.len() == 3 {
9081                                new_src.catalog = Some(mk_id(parts[0]));
9082                                new_src.schema = Some(mk_id(parts[1]));
9083                                new_src.name = mk_id(parts[2]);
9084                                new_ct.clone_source = Some(new_src);
9085                                changed = true;
9086                            } else if parts.len() == 2 {
9087                                new_src.schema = Some(mk_id(parts[0]));
9088                                new_src.name = mk_id(parts[1]);
9089                                new_ct.clone_source = Some(new_src);
9090                                changed = true;
9091                            }
9092                        }
9093                    }
9094                    if changed {
9095                        return Ok(Expression::CreateTable(new_ct));
9096                    }
9097                }
9098            }
9099
9100            // BigQuery array subscript: a[1], b[OFFSET(1)], c[ORDINAL(1)], d[SAFE_OFFSET(1)], e[SAFE_ORDINAL(1)]
9101            // -> DuckDB/Presto: convert 0-based to 1-based, handle SAFE_* -> ELEMENT_AT for Presto
9102            if matches!(source, DialectType::BigQuery)
9103                && matches!(
9104                    target,
9105                    DialectType::DuckDB
9106                        | DialectType::Presto
9107                        | DialectType::Trino
9108                        | DialectType::Athena
9109                )
9110            {
9111                if let Expression::Subscript(ref sub) = e {
9112                    let (new_index, is_safe) = match &sub.index {
9113                        // a[1] -> a[1+1] = a[2] (plain index is 0-based in BQ)
9114                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
9115                            let Literal::Number(n) = lit.as_ref() else {
9116                                unreachable!()
9117                            };
9118                            if let Ok(val) = n.parse::<i64>() {
9119                                (
9120                                    Some(Expression::Literal(Box::new(Literal::Number(
9121                                        (val + 1).to_string(),
9122                                    )))),
9123                                    false,
9124                                )
9125                            } else {
9126                                (None, false)
9127                            }
9128                        }
9129                        // OFFSET(n) -> n+1 (0-based)
9130                        Expression::Function(ref f)
9131                            if f.name.eq_ignore_ascii_case("OFFSET") && f.args.len() == 1 =>
9132                        {
9133                            if let Expression::Literal(lit) = &f.args[0] {
9134                                if let Literal::Number(n) = lit.as_ref() {
9135                                    if let Ok(val) = n.parse::<i64>() {
9136                                        (
9137                                            Some(Expression::Literal(Box::new(Literal::Number(
9138                                                (val + 1).to_string(),
9139                                            )))),
9140                                            false,
9141                                        )
9142                                    } else {
9143                                        (
9144                                            Some(Expression::Add(Box::new(
9145                                                crate::expressions::BinaryOp::new(
9146                                                    f.args[0].clone(),
9147                                                    Expression::number(1),
9148                                                ),
9149                                            ))),
9150                                            false,
9151                                        )
9152                                    }
9153                                } else {
9154                                    (None, false)
9155                                }
9156                            } else {
9157                                (
9158                                    Some(Expression::Add(Box::new(
9159                                        crate::expressions::BinaryOp::new(
9160                                            f.args[0].clone(),
9161                                            Expression::number(1),
9162                                        ),
9163                                    ))),
9164                                    false,
9165                                )
9166                            }
9167                        }
9168                        // ORDINAL(n) -> n (already 1-based)
9169                        Expression::Function(ref f)
9170                            if f.name.eq_ignore_ascii_case("ORDINAL") && f.args.len() == 1 =>
9171                        {
9172                            (Some(f.args[0].clone()), false)
9173                        }
9174                        // SAFE_OFFSET(n) -> n+1 (0-based, safe)
9175                        Expression::Function(ref f)
9176                            if f.name.eq_ignore_ascii_case("SAFE_OFFSET") && f.args.len() == 1 =>
9177                        {
9178                            if let Expression::Literal(lit) = &f.args[0] {
9179                                if let Literal::Number(n) = lit.as_ref() {
9180                                    if let Ok(val) = n.parse::<i64>() {
9181                                        (
9182                                            Some(Expression::Literal(Box::new(Literal::Number(
9183                                                (val + 1).to_string(),
9184                                            )))),
9185                                            true,
9186                                        )
9187                                    } else {
9188                                        (
9189                                            Some(Expression::Add(Box::new(
9190                                                crate::expressions::BinaryOp::new(
9191                                                    f.args[0].clone(),
9192                                                    Expression::number(1),
9193                                                ),
9194                                            ))),
9195                                            true,
9196                                        )
9197                                    }
9198                                } else {
9199                                    (None, false)
9200                                }
9201                            } else {
9202                                (
9203                                    Some(Expression::Add(Box::new(
9204                                        crate::expressions::BinaryOp::new(
9205                                            f.args[0].clone(),
9206                                            Expression::number(1),
9207                                        ),
9208                                    ))),
9209                                    true,
9210                                )
9211                            }
9212                        }
9213                        // SAFE_ORDINAL(n) -> n (already 1-based, safe)
9214                        Expression::Function(ref f)
9215                            if f.name.eq_ignore_ascii_case("SAFE_ORDINAL") && f.args.len() == 1 =>
9216                        {
9217                            (Some(f.args[0].clone()), true)
9218                        }
9219                        _ => (None, false),
9220                    };
9221                    if let Some(idx) = new_index {
9222                        if is_safe
9223                            && matches!(
9224                                target,
9225                                DialectType::Presto | DialectType::Trino | DialectType::Athena
9226                            )
9227                        {
9228                            // Presto: SAFE_OFFSET/SAFE_ORDINAL -> ELEMENT_AT(arr, idx)
9229                            return Ok(Expression::Function(Box::new(Function::new(
9230                                "ELEMENT_AT".to_string(),
9231                                vec![sub.this.clone(), idx],
9232                            ))));
9233                        } else {
9234                            // DuckDB or non-safe: just use subscript with converted index
9235                            return Ok(Expression::Subscript(Box::new(
9236                                crate::expressions::Subscript {
9237                                    this: sub.this.clone(),
9238                                    index: idx,
9239                                },
9240                            )));
9241                        }
9242                    }
9243                }
9244            }
9245
9246            // BigQuery LENGTH(x) -> DuckDB CASE TYPEOF(x) WHEN 'BLOB' THEN OCTET_LENGTH(...) ELSE LENGTH(...) END
9247            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::DuckDB) {
9248                if let Expression::Length(ref uf) = e {
9249                    let arg = uf.this.clone();
9250                    let typeof_func = Expression::Function(Box::new(Function::new(
9251                        "TYPEOF".to_string(),
9252                        vec![arg.clone()],
9253                    )));
9254                    let blob_cast = Expression::Cast(Box::new(Cast {
9255                        this: arg.clone(),
9256                        to: DataType::VarBinary { length: None },
9257                        trailing_comments: vec![],
9258                        double_colon_syntax: false,
9259                        format: None,
9260                        default: None,
9261                        inferred_type: None,
9262                    }));
9263                    let octet_length = Expression::Function(Box::new(Function::new(
9264                        "OCTET_LENGTH".to_string(),
9265                        vec![blob_cast],
9266                    )));
9267                    let text_cast = Expression::Cast(Box::new(Cast {
9268                        this: arg,
9269                        to: DataType::Text,
9270                        trailing_comments: vec![],
9271                        double_colon_syntax: false,
9272                        format: None,
9273                        default: None,
9274                        inferred_type: None,
9275                    }));
9276                    let length_text = Expression::Length(Box::new(crate::expressions::UnaryFunc {
9277                        this: text_cast,
9278                        original_name: None,
9279                        inferred_type: None,
9280                    }));
9281                    return Ok(Expression::Case(Box::new(Case {
9282                        operand: Some(typeof_func),
9283                        whens: vec![(
9284                            Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
9285                            octet_length,
9286                        )],
9287                        else_: Some(length_text),
9288                        comments: Vec::new(),
9289                        inferred_type: None,
9290                    })));
9291                }
9292            }
9293
9294            // BigQuery UNNEST alias handling (only for non-BigQuery sources):
9295            // UNNEST(...) AS x -> UNNEST(...) (drop unused table alias)
9296            // UNNEST(...) AS x(y) -> UNNEST(...) AS y (use column alias as main alias)
9297            if matches!(target, DialectType::BigQuery) && !matches!(source, DialectType::BigQuery) {
9298                if let Expression::Alias(ref a) = e {
9299                    if matches!(&a.this, Expression::Unnest(_)) {
9300                        if a.column_aliases.is_empty() {
9301                            // Drop the entire alias, return just the UNNEST expression
9302                            return Ok(a.this.clone());
9303                        } else {
9304                            // Use first column alias as the main alias
9305                            let mut new_alias = a.as_ref().clone();
9306                            new_alias.alias = a.column_aliases[0].clone();
9307                            new_alias.column_aliases.clear();
9308                            return Ok(Expression::Alias(Box::new(new_alias)));
9309                        }
9310                    }
9311                }
9312            }
9313
9314            // BigQuery IN UNNEST(expr) -> IN (SELECT UNNEST/EXPLODE(expr)) for non-BigQuery targets
9315            if matches!(source, DialectType::BigQuery) && !matches!(target, DialectType::BigQuery) {
9316                if let Expression::In(ref in_expr) = e {
9317                    if let Some(ref unnest_inner) = in_expr.unnest {
9318                        // Build the function call for the target dialect
9319                        let func_expr = if matches!(
9320                            target,
9321                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
9322                        ) {
9323                            // Use EXPLODE for Hive/Spark
9324                            Expression::Function(Box::new(Function::new(
9325                                "EXPLODE".to_string(),
9326                                vec![*unnest_inner.clone()],
9327                            )))
9328                        } else {
9329                            // Use UNNEST for Presto/Trino/DuckDB/etc.
9330                            Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
9331                                this: *unnest_inner.clone(),
9332                                expressions: Vec::new(),
9333                                with_ordinality: false,
9334                                alias: None,
9335                                offset_alias: None,
9336                            }))
9337                        };
9338
9339                        // Wrap in SELECT
9340                        let mut inner_select = crate::expressions::Select::new();
9341                        inner_select.expressions = vec![func_expr];
9342
9343                        let subquery_expr = Expression::Select(Box::new(inner_select));
9344
9345                        return Ok(Expression::In(Box::new(crate::expressions::In {
9346                            this: in_expr.this.clone(),
9347                            expressions: Vec::new(),
9348                            query: Some(subquery_expr),
9349                            not: in_expr.not,
9350                            global: in_expr.global,
9351                            unnest: None,
9352                            is_field: false,
9353                        })));
9354                    }
9355                }
9356            }
9357
9358            // SQLite: GENERATE_SERIES AS t(i) -> (SELECT value AS i FROM GENERATE_SERIES(...)) AS t
9359            // This handles the subquery wrapping for RANGE -> GENERATE_SERIES in FROM context
9360            if matches!(target, DialectType::SQLite) && matches!(source, DialectType::DuckDB) {
9361                if let Expression::Alias(ref a) = e {
9362                    if let Expression::Function(ref f) = a.this {
9363                        if f.name.eq_ignore_ascii_case("GENERATE_SERIES")
9364                            && !a.column_aliases.is_empty()
9365                        {
9366                            // Build: (SELECT value AS col_alias FROM GENERATE_SERIES(start, end)) AS table_alias
9367                            let col_alias = a.column_aliases[0].clone();
9368                            let mut inner_select = crate::expressions::Select::new();
9369                            inner_select.expressions =
9370                                vec![Expression::Alias(Box::new(crate::expressions::Alias::new(
9371                                    Expression::Identifier(Identifier::new("value".to_string())),
9372                                    col_alias,
9373                                )))];
9374                            inner_select.from = Some(crate::expressions::From {
9375                                expressions: vec![a.this.clone()],
9376                            });
9377                            let subquery =
9378                                Expression::Subquery(Box::new(crate::expressions::Subquery {
9379                                    this: Expression::Select(Box::new(inner_select)),
9380                                    alias: Some(a.alias.clone()),
9381                                    column_aliases: Vec::new(),
9382                                    alias_explicit_as: false,
9383                                    alias_keyword: None,
9384                                    order_by: None,
9385                                    limit: None,
9386                                    offset: None,
9387                                    lateral: false,
9388                                    modifiers_inside: false,
9389                                    trailing_comments: Vec::new(),
9390                                    distribute_by: None,
9391                                    sort_by: None,
9392                                    cluster_by: None,
9393                                    inferred_type: None,
9394                                }));
9395                            return Ok(subquery);
9396                        }
9397                    }
9398                }
9399            }
9400
9401            // BigQuery implicit UNNEST: comma-join on array path -> CROSS JOIN UNNEST
9402            // e.g., SELECT results FROM Coordinates, Coordinates.position AS results
9403            //     -> SELECT results FROM Coordinates CROSS JOIN UNNEST(Coordinates.position) AS results
9404            if matches!(source, DialectType::BigQuery) {
9405                if let Expression::Select(ref s) = e {
9406                    if let Some(ref from) = s.from {
9407                        if from.expressions.len() >= 2 {
9408                            // Collect table names from first expression
9409                            let first_tables: Vec<String> = from
9410                                .expressions
9411                                .iter()
9412                                .take(1)
9413                                .filter_map(|expr| {
9414                                    if let Expression::Table(t) = expr {
9415                                        Some(t.name.name.to_ascii_lowercase())
9416                                    } else {
9417                                        None
9418                                    }
9419                                })
9420                                .collect();
9421
9422                            // Check if any subsequent FROM expressions are schema-qualified with a matching table name
9423                            // or have a dotted name matching a table
9424                            let mut needs_rewrite = false;
9425                            for expr in from.expressions.iter().skip(1) {
9426                                if let Expression::Table(t) = expr {
9427                                    if let Some(ref schema) = t.schema {
9428                                        if first_tables.contains(&schema.name.to_ascii_lowercase())
9429                                        {
9430                                            needs_rewrite = true;
9431                                            break;
9432                                        }
9433                                    }
9434                                    // Also check dotted names in quoted identifiers (e.g., `Coordinates.position`)
9435                                    if t.schema.is_none() && t.name.name.contains('.') {
9436                                        let parts: Vec<&str> = t.name.name.split('.').collect();
9437                                        if parts.len() >= 2
9438                                            && first_tables.contains(&parts[0].to_ascii_lowercase())
9439                                        {
9440                                            needs_rewrite = true;
9441                                            break;
9442                                        }
9443                                    }
9444                                }
9445                            }
9446
9447                            if needs_rewrite {
9448                                let mut new_select = s.clone();
9449                                let mut new_from_exprs = vec![from.expressions[0].clone()];
9450                                let mut new_joins = s.joins.clone();
9451
9452                                for expr in from.expressions.iter().skip(1) {
9453                                    if let Expression::Table(ref t) = expr {
9454                                        if let Some(ref schema) = t.schema {
9455                                            if first_tables
9456                                                .contains(&schema.name.to_ascii_lowercase())
9457                                            {
9458                                                // This is an array path reference, convert to CROSS JOIN UNNEST
9459                                                let col_expr = Expression::Column(Box::new(
9460                                                    crate::expressions::Column {
9461                                                        name: t.name.clone(),
9462                                                        table: Some(schema.clone()),
9463                                                        join_mark: false,
9464                                                        trailing_comments: vec![],
9465                                                        span: None,
9466                                                        inferred_type: None,
9467                                                    },
9468                                                ));
9469                                                let unnest_expr = Expression::Unnest(Box::new(
9470                                                    crate::expressions::UnnestFunc {
9471                                                        this: col_expr,
9472                                                        expressions: Vec::new(),
9473                                                        with_ordinality: false,
9474                                                        alias: None,
9475                                                        offset_alias: None,
9476                                                    },
9477                                                ));
9478                                                let join_this = if let Some(ref alias) = t.alias {
9479                                                    if matches!(
9480                                                        target,
9481                                                        DialectType::Presto
9482                                                            | DialectType::Trino
9483                                                            | DialectType::Athena
9484                                                    ) {
9485                                                        // Presto: UNNEST(x) AS _t0(results)
9486                                                        Expression::Alias(Box::new(
9487                                                            crate::expressions::Alias {
9488                                                                this: unnest_expr,
9489                                                                alias: Identifier::new("_t0"),
9490                                                                column_aliases: vec![alias.clone()],
9491                                                                alias_explicit_as: false,
9492                                                                alias_keyword: None,
9493                                                                pre_alias_comments: vec![],
9494                                                                trailing_comments: vec![],
9495                                                                inferred_type: None,
9496                                                            },
9497                                                        ))
9498                                                    } else {
9499                                                        // BigQuery: UNNEST(x) AS results
9500                                                        Expression::Alias(Box::new(
9501                                                            crate::expressions::Alias {
9502                                                                this: unnest_expr,
9503                                                                alias: alias.clone(),
9504                                                                column_aliases: vec![],
9505                                                                alias_explicit_as: false,
9506                                                                alias_keyword: None,
9507                                                                pre_alias_comments: vec![],
9508                                                                trailing_comments: vec![],
9509                                                                inferred_type: None,
9510                                                            },
9511                                                        ))
9512                                                    }
9513                                                } else {
9514                                                    unnest_expr
9515                                                };
9516                                                new_joins.push(crate::expressions::Join {
9517                                                    kind: crate::expressions::JoinKind::Cross,
9518                                                    this: join_this,
9519                                                    on: None,
9520                                                    using: Vec::new(),
9521                                                    use_inner_keyword: false,
9522                                                    use_outer_keyword: false,
9523                                                    deferred_condition: false,
9524                                                    join_hint: None,
9525                                                    match_condition: None,
9526                                                    pivots: Vec::new(),
9527                                                    comments: Vec::new(),
9528                                                    nesting_group: 0,
9529                                                    directed: false,
9530                                                });
9531                                            } else {
9532                                                new_from_exprs.push(expr.clone());
9533                                            }
9534                                        } else if t.schema.is_none() && t.name.name.contains('.') {
9535                                            // Dotted name in quoted identifier: `Coordinates.position`
9536                                            let parts: Vec<&str> = t.name.name.split('.').collect();
9537                                            if parts.len() >= 2
9538                                                && first_tables
9539                                                    .contains(&parts[0].to_ascii_lowercase())
9540                                            {
9541                                                let join_this =
9542                                                    if matches!(target, DialectType::BigQuery) {
9543                                                        // BigQuery: keep as single quoted identifier, just convert comma -> CROSS JOIN
9544                                                        Expression::Table(t.clone())
9545                                                    } else {
9546                                                        // Other targets: split into "schema"."name"
9547                                                        let mut new_t = t.clone();
9548                                                        new_t.schema =
9549                                                            Some(Identifier::quoted(parts[0]));
9550                                                        new_t.name = Identifier::quoted(parts[1]);
9551                                                        Expression::Table(new_t)
9552                                                    };
9553                                                new_joins.push(crate::expressions::Join {
9554                                                    kind: crate::expressions::JoinKind::Cross,
9555                                                    this: join_this,
9556                                                    on: None,
9557                                                    using: Vec::new(),
9558                                                    use_inner_keyword: false,
9559                                                    use_outer_keyword: false,
9560                                                    deferred_condition: false,
9561                                                    join_hint: None,
9562                                                    match_condition: None,
9563                                                    pivots: Vec::new(),
9564                                                    comments: Vec::new(),
9565                                                    nesting_group: 0,
9566                                                    directed: false,
9567                                                });
9568                                            } else {
9569                                                new_from_exprs.push(expr.clone());
9570                                            }
9571                                        } else {
9572                                            new_from_exprs.push(expr.clone());
9573                                        }
9574                                    } else {
9575                                        new_from_exprs.push(expr.clone());
9576                                    }
9577                                }
9578
9579                                new_select.from = Some(crate::expressions::From {
9580                                    expressions: new_from_exprs,
9581                                    ..from.clone()
9582                                });
9583                                new_select.joins = new_joins;
9584                                return Ok(Expression::Select(new_select));
9585                            }
9586                        }
9587                    }
9588                }
9589            }
9590
9591            // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE for Hive/Spark
9592            if matches!(
9593                target,
9594                DialectType::Hive | DialectType::Spark | DialectType::Databricks
9595            ) {
9596                if let Expression::Select(ref s) = e {
9597                    // Check if any joins are CROSS JOIN with UNNEST/EXPLODE
9598                    let is_unnest_or_explode_expr = |expr: &Expression| -> bool {
9599                        matches!(expr, Expression::Unnest(_))
9600                            || matches!(expr, Expression::Function(f) if f.name.eq_ignore_ascii_case("EXPLODE"))
9601                    };
9602                    let has_unnest_join = s.joins.iter().any(|j| {
9603                        j.kind == crate::expressions::JoinKind::Cross && (
9604                            matches!(&j.this, Expression::Alias(a) if is_unnest_or_explode_expr(&a.this))
9605                            || is_unnest_or_explode_expr(&j.this)
9606                        )
9607                    });
9608                    if has_unnest_join {
9609                        let mut select = s.clone();
9610                        let mut new_joins = Vec::new();
9611                        for join in select.joins.drain(..) {
9612                            if join.kind == crate::expressions::JoinKind::Cross {
9613                                // Extract the UNNEST/EXPLODE from the join
9614                                let (func_expr, table_alias, col_aliases) = match &join.this {
9615                                    Expression::Alias(a) => {
9616                                        let ta = if a.alias.is_empty() {
9617                                            None
9618                                        } else {
9619                                            Some(a.alias.clone())
9620                                        };
9621                                        let cas = a.column_aliases.clone();
9622                                        match &a.this {
9623                                            Expression::Unnest(u) => {
9624                                                // Multi-arg UNNEST(y, z) -> INLINE(ARRAYS_ZIP(y, z))
9625                                                if !u.expressions.is_empty() {
9626                                                    let mut all_args = vec![u.this.clone()];
9627                                                    all_args.extend(u.expressions.clone());
9628                                                    let arrays_zip =
9629                                                        Expression::Function(Box::new(
9630                                                            crate::expressions::Function::new(
9631                                                                "ARRAYS_ZIP".to_string(),
9632                                                                all_args,
9633                                                            ),
9634                                                        ));
9635                                                    let inline = Expression::Function(Box::new(
9636                                                        crate::expressions::Function::new(
9637                                                            "INLINE".to_string(),
9638                                                            vec![arrays_zip],
9639                                                        ),
9640                                                    ));
9641                                                    (Some(inline), ta, a.column_aliases.clone())
9642                                                } else {
9643                                                    // Convert UNNEST(x) to EXPLODE(x) or POSEXPLODE(x)
9644                                                    let func_name = if u.with_ordinality {
9645                                                        "POSEXPLODE"
9646                                                    } else {
9647                                                        "EXPLODE"
9648                                                    };
9649                                                    let explode = Expression::Function(Box::new(
9650                                                        crate::expressions::Function::new(
9651                                                            func_name.to_string(),
9652                                                            vec![u.this.clone()],
9653                                                        ),
9654                                                    ));
9655                                                    // For POSEXPLODE, add 'pos' to column aliases
9656                                                    let cas = if u.with_ordinality {
9657                                                        let mut pos_aliases =
9658                                                            vec![Identifier::new(
9659                                                                "pos".to_string(),
9660                                                            )];
9661                                                        pos_aliases
9662                                                            .extend(a.column_aliases.clone());
9663                                                        pos_aliases
9664                                                    } else {
9665                                                        a.column_aliases.clone()
9666                                                    };
9667                                                    (Some(explode), ta, cas)
9668                                                }
9669                                            }
9670                                            Expression::Function(f)
9671                                                if f.name.eq_ignore_ascii_case("EXPLODE") =>
9672                                            {
9673                                                (Some(Expression::Function(f.clone())), ta, cas)
9674                                            }
9675                                            _ => (None, None, Vec::new()),
9676                                        }
9677                                    }
9678                                    Expression::Unnest(u) => {
9679                                        let func_name = if u.with_ordinality {
9680                                            "POSEXPLODE"
9681                                        } else {
9682                                            "EXPLODE"
9683                                        };
9684                                        let explode = Expression::Function(Box::new(
9685                                            crate::expressions::Function::new(
9686                                                func_name.to_string(),
9687                                                vec![u.this.clone()],
9688                                            ),
9689                                        ));
9690                                        let ta = u.alias.clone();
9691                                        let col_aliases = if u.with_ordinality {
9692                                            vec![Identifier::new("pos".to_string())]
9693                                        } else {
9694                                            Vec::new()
9695                                        };
9696                                        (Some(explode), ta, col_aliases)
9697                                    }
9698                                    _ => (None, None, Vec::new()),
9699                                };
9700                                if let Some(func) = func_expr {
9701                                    select.lateral_views.push(crate::expressions::LateralView {
9702                                        this: func,
9703                                        table_alias,
9704                                        column_aliases: col_aliases,
9705                                        outer: false,
9706                                    });
9707                                } else {
9708                                    new_joins.push(join);
9709                                }
9710                            } else {
9711                                new_joins.push(join);
9712                            }
9713                        }
9714                        select.joins = new_joins;
9715                        return Ok(Expression::Select(select));
9716                    }
9717                }
9718            }
9719
9720            // UNNEST expansion: DuckDB SELECT UNNEST(arr) in SELECT list -> expanded query
9721            // for BigQuery, Presto/Trino, Snowflake
9722            if matches!(source, DialectType::DuckDB | DialectType::PostgreSQL)
9723                && matches!(
9724                    target,
9725                    DialectType::BigQuery
9726                        | DialectType::Presto
9727                        | DialectType::Trino
9728                        | DialectType::Snowflake
9729                )
9730            {
9731                if let Expression::Select(ref s) = e {
9732                    // Check if any SELECT expressions contain UNNEST
9733                    // Note: UNNEST can appear as Expression::Unnest OR Expression::Function("UNNEST")
9734                    let has_unnest_in_select = s.expressions.iter().any(|expr| {
9735                        fn contains_unnest(e: &Expression) -> bool {
9736                            match e {
9737                                Expression::Unnest(_) => true,
9738                                Expression::Function(f)
9739                                    if f.name.eq_ignore_ascii_case("UNNEST") =>
9740                                {
9741                                    true
9742                                }
9743                                Expression::Alias(a) => contains_unnest(&a.this),
9744                                Expression::Add(op)
9745                                | Expression::Sub(op)
9746                                | Expression::Mul(op)
9747                                | Expression::Div(op) => {
9748                                    contains_unnest(&op.left) || contains_unnest(&op.right)
9749                                }
9750                                _ => false,
9751                            }
9752                        }
9753                        contains_unnest(expr)
9754                    });
9755
9756                    if has_unnest_in_select {
9757                        let rewritten = Self::rewrite_unnest_expansion(s, target);
9758                        if let Some(new_select) = rewritten {
9759                            return Ok(Expression::Select(Box::new(new_select)));
9760                        }
9761                    }
9762                }
9763            }
9764
9765            // BigQuery -> PostgreSQL: convert escape sequences in string literals to actual characters
9766            // BigQuery '\n' -> PostgreSQL literal newline in string
9767            if matches!(source, DialectType::BigQuery) && matches!(target, DialectType::PostgreSQL)
9768            {
9769                if let Expression::Literal(ref lit) = e {
9770                    if let Literal::String(ref s) = lit.as_ref() {
9771                        if s.contains("\\n")
9772                            || s.contains("\\t")
9773                            || s.contains("\\r")
9774                            || s.contains("\\\\")
9775                        {
9776                            let converted = s
9777                                .replace("\\n", "\n")
9778                                .replace("\\t", "\t")
9779                                .replace("\\r", "\r")
9780                                .replace("\\\\", "\\");
9781                            return Ok(Expression::Literal(Box::new(Literal::String(converted))));
9782                        }
9783                    }
9784                }
9785            }
9786
9787            // Cross-dialect: convert Literal::Timestamp to target-specific CAST form
9788            // when source != target (identity tests keep the Literal::Timestamp for native handling)
9789            if source != target {
9790                if let Expression::Literal(ref lit) = e {
9791                    if let Literal::Timestamp(ref s) = lit.as_ref() {
9792                        let s = s.clone();
9793                        // MySQL: TIMESTAMP handling depends on source dialect
9794                        // BigQuery TIMESTAMP is timezone-aware -> TIMESTAMP() function in MySQL
9795                        // Other sources' TIMESTAMP is non-timezone -> CAST('x' AS DATETIME) in MySQL
9796                        if matches!(target, DialectType::MySQL) {
9797                            if matches!(source, DialectType::BigQuery) {
9798                                // BigQuery TIMESTAMP is timezone-aware -> MySQL TIMESTAMP() function
9799                                return Ok(Expression::Function(Box::new(Function::new(
9800                                    "TIMESTAMP".to_string(),
9801                                    vec![Expression::Literal(Box::new(Literal::String(s)))],
9802                                ))));
9803                            } else {
9804                                // Non-timezone TIMESTAMP -> CAST('x' AS DATETIME) in MySQL
9805                                return Ok(Expression::Cast(Box::new(Cast {
9806                                    this: Expression::Literal(Box::new(Literal::String(s))),
9807                                    to: DataType::Custom {
9808                                        name: "DATETIME".to_string(),
9809                                    },
9810                                    trailing_comments: Vec::new(),
9811                                    double_colon_syntax: false,
9812                                    format: None,
9813                                    default: None,
9814                                    inferred_type: None,
9815                                })));
9816                            }
9817                        }
9818                        let dt = match target {
9819                            DialectType::BigQuery | DialectType::StarRocks => DataType::Custom {
9820                                name: "DATETIME".to_string(),
9821                            },
9822                            DialectType::Snowflake => {
9823                                // BigQuery TIMESTAMP is timezone-aware -> use TIMESTAMPTZ for Snowflake
9824                                if matches!(source, DialectType::BigQuery) {
9825                                    DataType::Custom {
9826                                        name: "TIMESTAMPTZ".to_string(),
9827                                    }
9828                                } else if matches!(
9829                                    source,
9830                                    DialectType::PostgreSQL
9831                                        | DialectType::Redshift
9832                                        | DialectType::Snowflake
9833                                ) {
9834                                    DataType::Timestamp {
9835                                        precision: None,
9836                                        timezone: false,
9837                                    }
9838                                } else {
9839                                    DataType::Custom {
9840                                        name: "TIMESTAMPNTZ".to_string(),
9841                                    }
9842                                }
9843                            }
9844                            DialectType::Spark | DialectType::Databricks => {
9845                                // BigQuery TIMESTAMP is timezone-aware -> use plain TIMESTAMP for Spark/Databricks
9846                                if matches!(source, DialectType::BigQuery) {
9847                                    DataType::Timestamp {
9848                                        precision: None,
9849                                        timezone: false,
9850                                    }
9851                                } else {
9852                                    DataType::Custom {
9853                                        name: "TIMESTAMP_NTZ".to_string(),
9854                                    }
9855                                }
9856                            }
9857                            DialectType::ClickHouse => DataType::Nullable {
9858                                inner: Box::new(DataType::Custom {
9859                                    name: "DateTime".to_string(),
9860                                }),
9861                            },
9862                            DialectType::TSQL | DialectType::Fabric => DataType::Custom {
9863                                name: "DATETIME2".to_string(),
9864                            },
9865                            DialectType::DuckDB => {
9866                                // DuckDB: use TIMESTAMPTZ when source is BigQuery (BQ TIMESTAMP is always UTC/tz-aware)
9867                                // or when the timestamp string explicitly has timezone info
9868                                if matches!(source, DialectType::BigQuery)
9869                                    || Self::timestamp_string_has_timezone(&s)
9870                                {
9871                                    DataType::Custom {
9872                                        name: "TIMESTAMPTZ".to_string(),
9873                                    }
9874                                } else {
9875                                    DataType::Timestamp {
9876                                        precision: None,
9877                                        timezone: false,
9878                                    }
9879                                }
9880                            }
9881                            _ => DataType::Timestamp {
9882                                precision: None,
9883                                timezone: false,
9884                            },
9885                        };
9886                        return Ok(Expression::Cast(Box::new(Cast {
9887                            this: Expression::Literal(Box::new(Literal::String(s))),
9888                            to: dt,
9889                            trailing_comments: vec![],
9890                            double_colon_syntax: false,
9891                            format: None,
9892                            default: None,
9893                            inferred_type: None,
9894                        })));
9895                    }
9896                }
9897            }
9898
9899            // PostgreSQL DELETE requires explicit AS for table aliases
9900            if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
9901                if let Expression::Delete(ref del) = e {
9902                    if del.alias.is_some() && !del.alias_explicit_as {
9903                        let mut new_del = del.clone();
9904                        new_del.alias_explicit_as = true;
9905                        return Ok(Expression::Delete(new_del));
9906                    }
9907                }
9908            }
9909
9910            // UNION/INTERSECT/EXCEPT DISTINCT handling:
9911            // Some dialects require explicit DISTINCT (BigQuery, ClickHouse),
9912            // while others don't support it (Presto, Spark, DuckDB, etc.)
9913            {
9914                let needs_distinct =
9915                    matches!(target, DialectType::BigQuery | DialectType::ClickHouse);
9916                let drop_distinct = matches!(
9917                    target,
9918                    DialectType::Presto
9919                        | DialectType::Trino
9920                        | DialectType::Athena
9921                        | DialectType::Spark
9922                        | DialectType::Databricks
9923                        | DialectType::DuckDB
9924                        | DialectType::Hive
9925                        | DialectType::MySQL
9926                        | DialectType::PostgreSQL
9927                        | DialectType::SQLite
9928                        | DialectType::TSQL
9929                        | DialectType::Redshift
9930                        | DialectType::Snowflake
9931                        | DialectType::Oracle
9932                        | DialectType::Teradata
9933                        | DialectType::Drill
9934                        | DialectType::Doris
9935                        | DialectType::StarRocks
9936                );
9937                match &e {
9938                    Expression::Union(u) if !u.all && needs_distinct && !u.distinct => {
9939                        let mut new_u = (**u).clone();
9940                        new_u.distinct = true;
9941                        return Ok(Expression::Union(Box::new(new_u)));
9942                    }
9943                    Expression::Intersect(i) if !i.all && needs_distinct && !i.distinct => {
9944                        let mut new_i = (**i).clone();
9945                        new_i.distinct = true;
9946                        return Ok(Expression::Intersect(Box::new(new_i)));
9947                    }
9948                    Expression::Except(ex) if !ex.all && needs_distinct && !ex.distinct => {
9949                        let mut new_ex = (**ex).clone();
9950                        new_ex.distinct = true;
9951                        return Ok(Expression::Except(Box::new(new_ex)));
9952                    }
9953                    Expression::Union(u) if u.distinct && drop_distinct => {
9954                        let mut new_u = (**u).clone();
9955                        new_u.distinct = false;
9956                        return Ok(Expression::Union(Box::new(new_u)));
9957                    }
9958                    Expression::Intersect(i) if i.distinct && drop_distinct => {
9959                        let mut new_i = (**i).clone();
9960                        new_i.distinct = false;
9961                        return Ok(Expression::Intersect(Box::new(new_i)));
9962                    }
9963                    Expression::Except(ex) if ex.distinct && drop_distinct => {
9964                        let mut new_ex = (**ex).clone();
9965                        new_ex.distinct = false;
9966                        return Ok(Expression::Except(Box::new(new_ex)));
9967                    }
9968                    _ => {}
9969                }
9970            }
9971
9972            // ClickHouse: MAP('a', '1') -> map('a', '1') (lowercase function name)
9973            if matches!(target, DialectType::ClickHouse) {
9974                if let Expression::Function(ref f) = e {
9975                    if f.name.eq_ignore_ascii_case("MAP") && !f.args.is_empty() {
9976                        let mut new_f = f.as_ref().clone();
9977                        new_f.name = "map".to_string();
9978                        return Ok(Expression::Function(Box::new(new_f)));
9979                    }
9980                }
9981            }
9982
9983            // ClickHouse: INTERSECT ALL -> INTERSECT (ClickHouse doesn't support ALL on INTERSECT)
9984            if matches!(target, DialectType::ClickHouse) {
9985                if let Expression::Intersect(ref i) = e {
9986                    if i.all {
9987                        let mut new_i = (**i).clone();
9988                        new_i.all = false;
9989                        return Ok(Expression::Intersect(Box::new(new_i)));
9990                    }
9991                }
9992            }
9993
9994            // Integer division: a / b -> CAST(a AS DOUBLE) / b for dialects that need it
9995            // Only from Generic source, to prevent double-wrapping
9996            if matches!(source, DialectType::Generic) {
9997                if let Expression::Div(ref op) = e {
9998                    let cast_type = match target {
9999                        DialectType::TSQL | DialectType::Fabric => Some(DataType::Float {
10000                            precision: None,
10001                            scale: None,
10002                            real_spelling: false,
10003                        }),
10004                        DialectType::Drill
10005                        | DialectType::Trino
10006                        | DialectType::Athena
10007                        | DialectType::Presto => Some(DataType::Double {
10008                            precision: None,
10009                            scale: None,
10010                        }),
10011                        DialectType::PostgreSQL
10012                        | DialectType::Redshift
10013                        | DialectType::Materialize
10014                        | DialectType::Teradata
10015                        | DialectType::RisingWave => Some(DataType::Double {
10016                            precision: None,
10017                            scale: None,
10018                        }),
10019                        _ => None,
10020                    };
10021                    if let Some(dt) = cast_type {
10022                        let cast_left = Expression::Cast(Box::new(Cast {
10023                            this: op.left.clone(),
10024                            to: dt,
10025                            double_colon_syntax: false,
10026                            trailing_comments: Vec::new(),
10027                            format: None,
10028                            default: None,
10029                            inferred_type: None,
10030                        }));
10031                        let new_op = crate::expressions::BinaryOp {
10032                            left: cast_left,
10033                            right: op.right.clone(),
10034                            left_comments: op.left_comments.clone(),
10035                            operator_comments: op.operator_comments.clone(),
10036                            trailing_comments: op.trailing_comments.clone(),
10037                            inferred_type: None,
10038                        };
10039                        return Ok(Expression::Div(Box::new(new_op)));
10040                    }
10041                }
10042            }
10043
10044            // CREATE DATABASE -> CREATE SCHEMA for DuckDB target
10045            if matches!(target, DialectType::DuckDB) {
10046                if let Expression::CreateDatabase(db) = e {
10047                    let mut schema = crate::expressions::CreateSchema::new(db.name.name.clone());
10048                    schema.if_not_exists = db.if_not_exists;
10049                    return Ok(Expression::CreateSchema(Box::new(schema)));
10050                }
10051                if let Expression::DropDatabase(db) = e {
10052                    let mut schema = crate::expressions::DropSchema::new(db.name.name.clone());
10053                    schema.if_exists = db.if_exists;
10054                    return Ok(Expression::DropSchema(Box::new(schema)));
10055                }
10056            }
10057
10058            // Strip ClickHouse Nullable(...) wrapper for non-ClickHouse targets
10059            if matches!(source, DialectType::ClickHouse)
10060                && !matches!(target, DialectType::ClickHouse)
10061            {
10062                if let Expression::Cast(ref c) = e {
10063                    if let DataType::Custom { ref name } = c.to {
10064                        if name.len() >= 9
10065                            && name[..9].eq_ignore_ascii_case("NULLABLE(")
10066                            && name.ends_with(")")
10067                        {
10068                            let inner = &name[9..name.len() - 1]; // strip "Nullable(" and ")"
10069                            let inner_upper = inner.to_ascii_uppercase();
10070                            let new_dt = match inner_upper.as_str() {
10071                                "DATETIME" | "DATETIME64" => DataType::Timestamp {
10072                                    precision: None,
10073                                    timezone: false,
10074                                },
10075                                "DATE" => DataType::Date,
10076                                "INT64" | "BIGINT" => DataType::BigInt { length: None },
10077                                "INT32" | "INT" | "INTEGER" => DataType::Int {
10078                                    length: None,
10079                                    integer_spelling: false,
10080                                },
10081                                "FLOAT64" | "DOUBLE" => DataType::Double {
10082                                    precision: None,
10083                                    scale: None,
10084                                },
10085                                "STRING" => DataType::Text,
10086                                _ => DataType::Custom {
10087                                    name: inner.to_string(),
10088                                },
10089                            };
10090                            let mut new_cast = c.clone();
10091                            new_cast.to = new_dt;
10092                            return Ok(Expression::Cast(new_cast));
10093                        }
10094                    }
10095                }
10096            }
10097
10098            // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(...))
10099            if matches!(target, DialectType::Snowflake) {
10100                if let Expression::ArrayConcatAgg(ref agg) = e {
10101                    let mut agg_clone = agg.as_ref().clone();
10102                    agg_clone.name = None; // Clear name so generator uses default "ARRAY_AGG"
10103                    let array_agg = Expression::ArrayAgg(Box::new(agg_clone));
10104                    let flatten = Expression::Function(Box::new(Function::new(
10105                        "ARRAY_FLATTEN".to_string(),
10106                        vec![array_agg],
10107                    )));
10108                    return Ok(flatten);
10109                }
10110            }
10111
10112            // ARRAY_CONCAT_AGG -> others: keep as function for cross-dialect
10113            if !matches!(target, DialectType::BigQuery | DialectType::Snowflake) {
10114                if let Expression::ArrayConcatAgg(agg) = e {
10115                    let arg = agg.this;
10116                    return Ok(Expression::Function(Box::new(Function::new(
10117                        "ARRAY_CONCAT_AGG".to_string(),
10118                        vec![arg],
10119                    ))));
10120                }
10121            }
10122
10123            // Determine what action to take by inspecting e immutably
10124            let action = {
10125                let source_propagates_nulls =
10126                    matches!(source, DialectType::Snowflake | DialectType::BigQuery);
10127                let target_ignores_nulls =
10128                    matches!(target, DialectType::DuckDB | DialectType::PostgreSQL);
10129
10130                match &e {
10131                    Expression::Function(f) => {
10132                        let name = f.name.to_ascii_uppercase();
10133                        // DuckDB json(x) is a synonym for CAST(x AS JSON) — parses a string.
10134                        // Map to JSON_PARSE(x) for Trino/Presto/Athena to preserve semantics.
10135                        if name == "JSON"
10136                            && f.args.len() == 1
10137                            && matches!(source, DialectType::DuckDB)
10138                            && matches!(
10139                                target,
10140                                DialectType::Presto | DialectType::Trino | DialectType::Athena
10141                            )
10142                        {
10143                            Action::DuckDBJsonFuncToJsonParse
10144                        // DuckDB json_valid(x) has no direct Trino equivalent; emit the
10145                        // SQL:2016 `x IS JSON` predicate which has matching semantics.
10146                        } else if name == "JSON_VALID"
10147                            && f.args.len() == 1
10148                            && matches!(source, DialectType::DuckDB)
10149                            && matches!(
10150                                target,
10151                                DialectType::Presto | DialectType::Trino | DialectType::Athena
10152                            )
10153                        {
10154                            Action::DuckDBJsonValidToIsJson
10155                        // DATE_PART: strip quotes from first arg when target is Snowflake (source != Snowflake)
10156                        } else if (name == "DATE_PART" || name == "DATEPART")
10157                            && f.args.len() == 2
10158                            && matches!(target, DialectType::Snowflake)
10159                            && !matches!(source, DialectType::Snowflake)
10160                            && matches!(
10161                                &f.args[0],
10162                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10163                            )
10164                        {
10165                            Action::DatePartUnquote
10166                        } else if source_propagates_nulls
10167                            && target_ignores_nulls
10168                            && (name == "GREATEST" || name == "LEAST")
10169                            && f.args.len() >= 2
10170                        {
10171                            Action::GreatestLeastNull
10172                        } else if matches!(source, DialectType::Snowflake)
10173                            && name == "ARRAY_GENERATE_RANGE"
10174                            && f.args.len() >= 2
10175                        {
10176                            Action::ArrayGenerateRange
10177                        } else if matches!(source, DialectType::Snowflake)
10178                            && matches!(target, DialectType::DuckDB)
10179                            && name == "DATE_TRUNC"
10180                            && f.args.len() == 2
10181                        {
10182                            // Determine if DuckDB DATE_TRUNC needs CAST wrapping to preserve input type.
10183                            // Logic based on Python sqlglot's input_type_preserved flag:
10184                            // - DATE + non-date-unit (HOUR, MINUTE, etc.) -> wrap
10185                            // - TIMESTAMP + date-unit (YEAR, QUARTER, MONTH, WEEK, DAY) -> wrap
10186                            // - TIMESTAMPTZ/TIMESTAMPLTZ/TIME -> always wrap
10187                            let unit_str = match &f.args[0] {
10188                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_)) => {
10189                                    let crate::expressions::Literal::String(s) = lit.as_ref() else { unreachable!() };
10190                                    Some(s.to_ascii_uppercase())
10191                                }
10192                                _ => None,
10193                            };
10194                            let is_date_unit = unit_str.as_ref().map_or(false, |u| {
10195                                matches!(u.as_str(), "YEAR" | "QUARTER" | "MONTH" | "WEEK" | "DAY")
10196                            });
10197                            match &f.args[1] {
10198                                Expression::Cast(c) => match &c.to {
10199                                    DataType::Time { .. } => Action::DateTruncWrapCast,
10200                                    DataType::Custom { name }
10201                                        if name.eq_ignore_ascii_case("TIMESTAMPTZ")
10202                                            || name.eq_ignore_ascii_case("TIMESTAMPLTZ") =>
10203                                    {
10204                                        Action::DateTruncWrapCast
10205                                    }
10206                                    DataType::Timestamp { timezone: true, .. } => {
10207                                        Action::DateTruncWrapCast
10208                                    }
10209                                    DataType::Date if !is_date_unit => Action::DateTruncWrapCast,
10210                                    DataType::Timestamp {
10211                                        timezone: false, ..
10212                                    } if is_date_unit => Action::DateTruncWrapCast,
10213                                    _ => Action::None,
10214                                },
10215                                _ => Action::None,
10216                            }
10217                        } else if matches!(source, DialectType::Snowflake)
10218                            && matches!(target, DialectType::DuckDB)
10219                            && name == "TO_DATE"
10220                            && f.args.len() == 1
10221                            && !matches!(
10222                                &f.args[0],
10223                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10224                            )
10225                        {
10226                            Action::ToDateToCast
10227                        } else if !matches!(source, DialectType::Redshift)
10228                            && matches!(target, DialectType::Redshift)
10229                            && name == "CONVERT_TIMEZONE"
10230                            && (f.args.len() == 2 || f.args.len() == 3)
10231                        {
10232                            // Convert Function("CONVERT_TIMEZONE") to Expression::ConvertTimezone
10233                            // so Redshift's transform_expr won't expand 2-arg to 3-arg with 'UTC'.
10234                            // The Redshift parser adds 'UTC' as default source_tz, but when
10235                            // transpiling from other dialects, we should preserve the original form.
10236                            Action::ConvertTimezoneToExpr
10237                        } else if matches!(source, DialectType::Snowflake)
10238                            && matches!(target, DialectType::DuckDB)
10239                            && name == "REGEXP_REPLACE"
10240                            && f.args.len() == 4
10241                            && !matches!(
10242                                &f.args[3],
10243                                Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
10244                            )
10245                        {
10246                            // Snowflake REGEXP_REPLACE with position arg -> DuckDB needs 'g' flag
10247                            Action::RegexpReplaceSnowflakeToDuckDB
10248                        } else if matches!(source, DialectType::Snowflake)
10249                            && matches!(target, DialectType::DuckDB)
10250                            && name == "REGEXP_REPLACE"
10251                            && f.args.len() == 5
10252                        {
10253                            // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB
10254                            Action::RegexpReplacePositionSnowflakeToDuckDB
10255                        } else if matches!(source, DialectType::Snowflake)
10256                            && matches!(target, DialectType::DuckDB)
10257                            && name == "REGEXP_SUBSTR"
10258                        {
10259                            // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
10260                            Action::RegexpSubstrSnowflakeToDuckDB
10261                        } else if matches!(source, DialectType::Snowflake)
10262                            && matches!(target, DialectType::Snowflake)
10263                            && (name == "REGEXP_SUBSTR" || name == "REGEXP_SUBSTR_ALL")
10264                            && f.args.len() == 6
10265                        {
10266                            // Snowflake identity: strip trailing group=0
10267                            Action::RegexpSubstrSnowflakeIdentity
10268                        } else if matches!(source, DialectType::Snowflake)
10269                            && matches!(target, DialectType::DuckDB)
10270                            && name == "REGEXP_SUBSTR_ALL"
10271                        {
10272                            // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
10273                            Action::RegexpSubstrAllSnowflakeToDuckDB
10274                        } else if matches!(source, DialectType::Snowflake)
10275                            && matches!(target, DialectType::DuckDB)
10276                            && name == "REGEXP_COUNT"
10277                        {
10278                            // Snowflake REGEXP_COUNT -> DuckDB LENGTH(REGEXP_EXTRACT_ALL(...))
10279                            Action::RegexpCountSnowflakeToDuckDB
10280                        } else if matches!(source, DialectType::Snowflake)
10281                            && matches!(target, DialectType::DuckDB)
10282                            && name == "REGEXP_INSTR"
10283                        {
10284                            // Snowflake REGEXP_INSTR -> DuckDB complex CASE expression
10285                            Action::RegexpInstrSnowflakeToDuckDB
10286                        } else if matches!(source, DialectType::BigQuery)
10287                            && matches!(target, DialectType::Snowflake)
10288                            && name == "REGEXP_EXTRACT_ALL"
10289                        {
10290                            // BigQuery REGEXP_EXTRACT_ALL -> Snowflake REGEXP_SUBSTR_ALL
10291                            Action::RegexpExtractAllToSnowflake
10292                        } else if name == "_BQ_TO_HEX" {
10293                            // Internal marker from TO_HEX conversion - bare (no LOWER/UPPER wrapper)
10294                            Action::BigQueryToHexBare
10295                        } else if matches!(source, DialectType::BigQuery)
10296                            && !matches!(target, DialectType::BigQuery)
10297                        {
10298                            // BigQuery-specific functions that need to be converted to standard forms
10299                            match name.as_str() {
10300                                "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF"
10301                                | "DATE_DIFF"
10302                                | "TIMESTAMP_ADD" | "TIMESTAMP_SUB"
10303                                | "DATETIME_ADD" | "DATETIME_SUB"
10304                                | "TIME_ADD" | "TIME_SUB"
10305                                | "DATE_ADD" | "DATE_SUB"
10306                                | "SAFE_DIVIDE"
10307                                | "GENERATE_UUID"
10308                                | "COUNTIF"
10309                                | "EDIT_DISTANCE"
10310                                | "TIMESTAMP_SECONDS" | "TIMESTAMP_MILLIS" | "TIMESTAMP_MICROS"
10311                                | "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" | "DATE_TRUNC"
10312                                | "TO_HEX"
10313                                | "TO_JSON_STRING"
10314                                | "GENERATE_ARRAY" | "GENERATE_TIMESTAMP_ARRAY"
10315                                | "DIV"
10316                                | "UNIX_DATE" | "UNIX_SECONDS" | "UNIX_MILLIS" | "UNIX_MICROS"
10317                                | "LAST_DAY"
10318                                | "TIME" | "DATETIME" | "TIMESTAMP" | "STRING"
10319                                | "REGEXP_CONTAINS"
10320                                | "CONTAINS_SUBSTR"
10321                                | "SAFE_ADD" | "SAFE_SUBTRACT" | "SAFE_MULTIPLY"
10322                                | "SAFE_CAST"
10323                                | "GENERATE_DATE_ARRAY"
10324                                | "PARSE_DATE" | "PARSE_DATETIME" | "PARSE_TIMESTAMP"
10325                                | "FORMAT_DATE" | "FORMAT_DATETIME" | "FORMAT_TIMESTAMP"
10326                                | "ARRAY_CONCAT"
10327                                | "JSON_QUERY" | "JSON_VALUE_ARRAY"
10328                                | "INSTR"
10329                                | "MD5" | "SHA1" | "SHA256" | "SHA512"
10330                                | "GENERATE_UUID()" // just in case
10331                                | "REGEXP_EXTRACT_ALL"
10332                                | "REGEXP_EXTRACT"
10333                                | "INT64"
10334                                | "ARRAY_CONCAT_AGG"
10335                                | "DATE_DIFF(" // just in case
10336                                | "TO_HEX_MD5" // internal
10337                                | "MOD"
10338                                | "CONCAT"
10339                                | "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME"
10340                                | "STRUCT"
10341                                | "ROUND"
10342                                | "MAKE_INTERVAL"
10343                                | "ARRAY_TO_STRING"
10344                                | "PERCENTILE_CONT"
10345                                    => Action::BigQueryFunctionNormalize,
10346                                "ARRAY" if matches!(target, DialectType::Snowflake)
10347                                    && f.args.len() == 1
10348                                    && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"))
10349                                    => Action::BigQueryArraySelectAsStructToSnowflake,
10350                                _ => Action::None,
10351                            }
10352                        } else if matches!(source, DialectType::BigQuery)
10353                            && matches!(target, DialectType::BigQuery)
10354                        {
10355                            // BigQuery -> BigQuery normalizations
10356                            match name.as_str() {
10357                                "TIMESTAMP_DIFF"
10358                                | "DATETIME_DIFF"
10359                                | "TIME_DIFF"
10360                                | "DATE_DIFF"
10361                                | "DATE_ADD"
10362                                | "TO_HEX"
10363                                | "CURRENT_TIMESTAMP"
10364                                | "CURRENT_DATE"
10365                                | "CURRENT_TIME"
10366                                | "CURRENT_DATETIME"
10367                                | "GENERATE_DATE_ARRAY"
10368                                | "INSTR"
10369                                | "FORMAT_DATETIME"
10370                                | "DATETIME"
10371                                | "MAKE_INTERVAL" => Action::BigQueryFunctionNormalize,
10372                                _ => Action::None,
10373                            }
10374                        } else {
10375                            // Generic function normalization for non-BigQuery sources
10376                            match name.as_str() {
10377                                "ARBITRARY" | "AGGREGATE"
10378                                | "REGEXP_MATCHES" | "REGEXP_FULL_MATCH"
10379                                | "STRUCT_EXTRACT"
10380                                | "LIST_FILTER" | "LIST_TRANSFORM" | "LIST_SORT" | "LIST_REVERSE_SORT"
10381                                | "STRING_TO_ARRAY" | "STR_SPLIT" | "STR_SPLIT_REGEX" | "SPLIT_TO_ARRAY"
10382                                | "SUBSTRINGINDEX"
10383                                | "ARRAY_LENGTH" | "SIZE" | "CARDINALITY"
10384                                | "UNICODE"
10385                                | "XOR"
10386                                | "ARRAY_REVERSE_SORT"
10387                                | "ENCODE" | "DECODE"
10388                                | "QUANTILE"
10389                                | "EPOCH" | "EPOCH_MS"
10390                                | "HASHBYTES"
10391                                | "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT"
10392                                | "APPROX_DISTINCT"
10393                                | "DATE_PARSE" | "FORMAT_DATETIME"
10394                                | "REGEXP_EXTRACT" | "REGEXP_SUBSTR" | "TO_DAYS"
10395                                | "RLIKE"
10396                                | "DATEDIFF" | "DATE_DIFF" | "MONTHS_BETWEEN"
10397                                | "ADD_MONTHS" | "DATEADD" | "DATE_ADD" | "DATE_SUB" | "DATETRUNC"
10398                                | "LAST_DAY" | "LAST_DAY_OF_MONTH" | "EOMONTH"
10399                                | "ARRAY_CONSTRUCT" | "ARRAY_CAT" | "ARRAY_COMPACT"
10400                                | "ARRAY_FILTER" | "FILTER" | "REDUCE" | "ARRAY_REVERSE"
10401                                | "MAP" | "MAP_FROM_ENTRIES"
10402                                | "COLLECT_LIST" | "COLLECT_SET"
10403                                | "ISNAN" | "IS_NAN"
10404                                | "TO_UTC_TIMESTAMP" | "FROM_UTC_TIMESTAMP"
10405                                | "FORMAT_NUMBER"
10406                                | "TOMONDAY" | "TOSTARTOFWEEK" | "TOSTARTOFMONTH" | "TOSTARTOFYEAR"
10407                                | "ELEMENT_AT"
10408                                | "EXPLODE" | "EXPLODE_OUTER" | "POSEXPLODE"
10409                                | "SPLIT_PART"
10410                                // GENERATE_SERIES: handled separately below
10411                                | "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR"
10412                                | "JSON_QUERY" | "JSON_VALUE"
10413                                | "JSON_SEARCH"
10414                                | "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
10415                                | "TO_UNIX_TIMESTAMP" | "UNIX_TIMESTAMP"
10416                                | "CURDATE" | "CURTIME"
10417                                | "ARRAY_TO_STRING"
10418                                | "ARRAY_SORT" | "SORT_ARRAY"
10419                                | "LEFT" | "RIGHT"
10420                                | "MAP_FROM_ARRAYS"
10421                                | "LIKE" | "ILIKE"
10422                                | "ARRAY_CONCAT" | "LIST_CONCAT"
10423                                | "QUANTILE_CONT" | "QUANTILE_DISC"
10424                                | "PERCENTILE_CONT" | "PERCENTILE_DISC"
10425                                | "PERCENTILE_APPROX" | "APPROX_PERCENTILE"
10426                                | "LOCATE" | "STRPOS" | "INSTR"
10427                                | "CHAR"
10428                                // CONCAT: handled separately for COALESCE wrapping
10429                                | "ARRAY_JOIN"
10430                                | "ARRAY_CONTAINS" | "HAS" | "CONTAINS"
10431                                | "ISNULL"
10432                                | "MONTHNAME"
10433                                | "TO_TIMESTAMP"
10434                                | "TO_DATE"
10435                                | "TO_JSON"
10436                                | "REGEXP_SPLIT"
10437                                | "SPLIT"
10438                                | "FORMATDATETIME"
10439                                | "ARRAYJOIN"
10440                                | "SPLITBYSTRING" | "SPLITBYREGEXP"
10441                                | "NVL"
10442                                | "TO_CHAR"
10443                                | "DBMS_RANDOM.VALUE"
10444                                | "REGEXP_LIKE"
10445                                | "REPLICATE"
10446                                | "LEN"
10447                                | "COUNT_BIG"
10448                                | "DATEFROMPARTS"
10449                                | "DATETIMEFROMPARTS"
10450                                | "CONVERT" | "TRY_CONVERT"
10451                                | "STRFTIME" | "STRPTIME"
10452                                | "DATE_FORMAT" | "FORMAT_DATE"
10453                                | "PARSE_TIMESTAMP" | "PARSE_DATETIME" | "PARSE_DATE"
10454                                | "FROM_ISO8601_TIMESTAMP" | "FROM_ISO8601_DATE"
10455                                | "FROM_BASE64" | "TO_BASE64"
10456                                | "GETDATE"
10457                                | "TO_HEX" | "FROM_HEX" | "UNHEX" | "HEX"
10458                                | "TO_UTF8" | "FROM_UTF8"
10459                                | "STARTS_WITH" | "STARTSWITH"
10460                                | "APPROX_COUNT_DISTINCT"
10461                                | "JSON_FORMAT"
10462                                | "SYSDATE"
10463                                | "LOGICAL_OR" | "LOGICAL_AND"
10464                                | "MONTHS_ADD"
10465                                | "SCHEMA_NAME"
10466                                | "STRTOL"
10467                                | "EDITDIST3"
10468                                | "FORMAT"
10469                                | "LIST_CONTAINS" | "LIST_HAS"
10470                                | "VARIANCE" | "STDDEV"
10471                                | "ISINF"
10472                                | "TO_UNIXTIME"
10473                                | "FROM_UNIXTIME"
10474                                | "DATEPART" | "DATE_PART"
10475                                | "DATENAME"
10476                                | "STRING_AGG"
10477                                | "JSON_ARRAYAGG"
10478                                | "APPROX_QUANTILE"
10479                                | "MAKE_DATE"
10480                                | "LIST_HAS_ANY" | "ARRAY_HAS_ANY"
10481                                | "RANGE"
10482                                | "TRY_ELEMENT_AT"
10483                                | "STR_TO_MAP"
10484                                | "STRING"
10485                                | "STR_TO_TIME"
10486                                | "CURRENT_SCHEMA"
10487                                | "LTRIM" | "RTRIM"
10488                                | "UUID"
10489                                | "FARM_FINGERPRINT"
10490                                | "JSON_KEYS"
10491                                | "WEEKOFYEAR"
10492                                | "CONCAT_WS"
10493                                | "TRY_DIVIDE"
10494                                | "ARRAY_SLICE"
10495                                | "ARRAY_PREPEND"
10496                                | "ARRAY_REMOVE"
10497                                | "GENERATE_DATE_ARRAY"
10498                                | "PARSE_JSON"
10499                                | "JSON_REMOVE"
10500                                | "JSON_SET"
10501                                | "LEVENSHTEIN"
10502                                | "CURRENT_VERSION"
10503                                | "ARRAY_MAX"
10504                                | "ARRAY_MIN"
10505                                | "JAROWINKLER_SIMILARITY"
10506                                | "CURRENT_SCHEMAS"
10507                                | "TO_VARIANT"
10508                                | "JSON_GROUP_ARRAY" | "JSON_GROUP_OBJECT"
10509                                | "ARRAYS_OVERLAP" | "ARRAY_INTERSECTION"
10510                                    => Action::GenericFunctionNormalize,
10511                                // Canonical date functions -> dialect-specific
10512                                "TS_OR_DS_TO_DATE" => Action::TsOrDsToDateConvert,
10513                                "TS_OR_DS_TO_DATE_STR" if f.args.len() == 1 => Action::TsOrDsToDateStrConvert,
10514                                "DATE_STR_TO_DATE" if f.args.len() == 1 => Action::DateStrToDateConvert,
10515                                "TIME_STR_TO_DATE" if f.args.len() == 1 => Action::TimeStrToDateConvert,
10516                                "TIME_STR_TO_TIME" if f.args.len() <= 2 => Action::TimeStrToTimeConvert,
10517                                "TIME_STR_TO_UNIX" if f.args.len() == 1 => Action::TimeStrToUnixConvert,
10518                                "TIME_TO_TIME_STR" if f.args.len() == 1 => Action::TimeToTimeStrConvert,
10519                                "DATE_TO_DATE_STR" if f.args.len() == 1 => Action::DateToDateStrConvert,
10520                                "DATE_TO_DI" if f.args.len() == 1 => Action::DateToDiConvert,
10521                                "DI_TO_DATE" if f.args.len() == 1 => Action::DiToDateConvert,
10522                                "TS_OR_DI_TO_DI" if f.args.len() == 1 => Action::TsOrDiToDiConvert,
10523                                "UNIX_TO_STR" if f.args.len() == 2 => Action::UnixToStrConvert,
10524                                "UNIX_TO_TIME" if f.args.len() == 1 => Action::UnixToTimeConvert,
10525                                "UNIX_TO_TIME_STR" if f.args.len() == 1 => Action::UnixToTimeStrConvert,
10526                                "TIME_TO_UNIX" if f.args.len() == 1 => Action::TimeToUnixConvert,
10527                                "TIME_TO_STR" if f.args.len() == 2 => Action::TimeToStrConvert,
10528                                "STR_TO_UNIX" if f.args.len() == 2 => Action::StrToUnixConvert,
10529                                // STR_TO_DATE(x, fmt) -> dialect-specific
10530                                "STR_TO_DATE" if f.args.len() == 2
10531                                    && matches!(source, DialectType::Generic) => Action::StrToDateConvert,
10532                                "STR_TO_DATE" => Action::GenericFunctionNormalize,
10533                                // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
10534                                "TS_OR_DS_ADD" if f.args.len() == 3
10535                                    && matches!(source, DialectType::Generic) => Action::TsOrDsAddConvert,
10536                                // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, '1970-01-01')
10537                                "DATE_FROM_UNIX_DATE" if f.args.len() == 1 => Action::DateFromUnixDateConvert,
10538                                // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
10539                                "NVL2" if (f.args.len() == 2 || f.args.len() == 3) => Action::Nvl2Expand,
10540                                // IFNULL(a, b) -> COALESCE(a, b) when coming from Generic source
10541                                "IFNULL" if f.args.len() == 2 => Action::IfnullToCoalesce,
10542                                // IS_ASCII(x) -> dialect-specific
10543                                "IS_ASCII" if f.args.len() == 1 => Action::IsAsciiConvert,
10544                                // STR_POSITION(haystack, needle[, pos[, occ]]) -> dialect-specific
10545                                "STR_POSITION" => Action::StrPositionConvert,
10546                                // ARRAY_SUM -> dialect-specific
10547                                "ARRAY_SUM" => Action::ArraySumConvert,
10548                                // ARRAY_SIZE -> dialect-specific (Drill only)
10549                                "ARRAY_SIZE" if matches!(target, DialectType::Drill) => Action::ArraySizeConvert,
10550                                // ARRAY_ANY -> dialect-specific
10551                                "ARRAY_ANY" if f.args.len() == 2 => Action::ArrayAnyConvert,
10552                                // Functions needing specific cross-dialect transforms
10553                                "MAX_BY" | "MIN_BY" if matches!(target, DialectType::ClickHouse | DialectType::Spark | DialectType::Databricks | DialectType::DuckDB) => Action::MaxByMinByConvert,
10554                                "STRUCT" if matches!(source, DialectType::Spark | DialectType::Databricks)
10555                                    && !matches!(target, DialectType::Spark | DialectType::Databricks | DialectType::Hive) => Action::SparkStructConvert,
10556                                "ARRAY" if matches!(source, DialectType::BigQuery)
10557                                    && matches!(target, DialectType::Snowflake)
10558                                    && f.args.len() == 1
10559                                    && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT")) => Action::BigQueryArraySelectAsStructToSnowflake,
10560                                "ARRAY" if matches!(target, DialectType::Presto | DialectType::Trino | DialectType::Athena | DialectType::BigQuery | DialectType::DuckDB | DialectType::Snowflake | DialectType::ClickHouse | DialectType::StarRocks) => Action::ArraySyntaxConvert,
10561                                "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,
10562                                "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,
10563                                // DATE_TRUNC('unit', x) from Generic source -> arg swap for BigQuery/Doris/Spark/MySQL
10564                                "DATE_TRUNC" if f.args.len() == 2
10565                                    && matches!(source, DialectType::Generic)
10566                                    && matches!(target, DialectType::BigQuery | DialectType::Doris | DialectType::StarRocks
10567                                        | DialectType::Spark | DialectType::Databricks | DialectType::MySQL) => Action::DateTruncSwapArgs,
10568                                // TIMESTAMP_TRUNC(x, UNIT) from Generic source -> convert to per-dialect
10569                                "TIMESTAMP_TRUNC" if f.args.len() >= 2
10570                                    && matches!(source, DialectType::Generic) => Action::TimestampTruncConvert,
10571                                "UNIFORM" if matches!(target, DialectType::Snowflake) => Action::GenericFunctionNormalize,
10572                                // GENERATE_SERIES -> SEQUENCE/UNNEST/EXPLODE for target dialects
10573                                "GENERATE_SERIES" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10574                                    && !matches!(target, DialectType::PostgreSQL | DialectType::Redshift | DialectType::TSQL | DialectType::Fabric) => Action::GenerateSeriesConvert,
10575                                // GENERATE_SERIES with interval normalization for PG target
10576                                "GENERATE_SERIES" if f.args.len() >= 3
10577                                    && matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10578                                    && matches!(target, DialectType::PostgreSQL | DialectType::Redshift) => Action::GenerateSeriesConvert,
10579                                "GENERATE_SERIES" => Action::None, // passthrough for other cases
10580                                // CONCAT(a, b) -> COALESCE wrapping for Presto/ClickHouse from PostgreSQL
10581                                "CONCAT" if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
10582                                    && matches!(target, DialectType::Presto | DialectType::Trino | DialectType::ClickHouse) => Action::ConcatCoalesceWrap,
10583                                "CONCAT" => Action::GenericFunctionNormalize,
10584                                // CBRT(x) -> POWER(CAST(x AS FLOAT), 1.0 / 3.0)
10585                                "CBRT" if f.args.len() == 1
10586                                    && Self::is_postgres_family_source(source)
10587                                    && matches!(target, DialectType::TSQL | DialectType::Fabric) => Action::CbrtToPower,
10588                                "JSON_BUILD_OBJECT" | "JSONB_BUILD_OBJECT"
10589                                    if Self::is_postgres_family_source(source)
10590                                        && matches!(target, DialectType::TSQL | DialectType::Fabric)
10591                                        && f.args.len() % 2 == 0 =>
10592                                {
10593                                    Action::PostgresJsonBuildObjectToJsonObject
10594                                }
10595                                "JSON_AGG" | "JSONB_AGG"
10596                                    if Self::is_postgres_family_source(source)
10597                                        && matches!(target, DialectType::TSQL | DialectType::Fabric)
10598                                        && f.args.len() == 1 =>
10599                                {
10600                                    Action::PostgresJsonAggToJsonArrayAgg
10601                                }
10602                                // DIV(a, b) -> target-specific integer division
10603                                "DIV" if f.args.len() == 2
10604                                    && Self::is_postgres_family_source(source)
10605                                    && matches!(
10606                                        target,
10607                                        DialectType::DuckDB
10608                                            | DialectType::BigQuery
10609                                            | DialectType::SQLite
10610                                            | DialectType::TSQL
10611                                            | DialectType::Fabric
10612                                    ) => Action::DivFuncConvert,
10613                                // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
10614                                "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG" if f.args.len() == 2
10615                                    && matches!(target, DialectType::DuckDB) => Action::JsonObjectAggConvert,
10616                                // JSONB_EXISTS -> JSON_EXISTS for DuckDB
10617                                "JSONB_EXISTS" if f.args.len() == 2
10618                                    && matches!(target, DialectType::DuckDB) => Action::JsonbExistsConvert,
10619                                // DATE_BIN -> TIME_BUCKET for DuckDB
10620                                "DATE_BIN" if matches!(target, DialectType::DuckDB) => Action::DateBinConvert,
10621                                // Multi-arg MIN(a,b,c) -> LEAST, MAX(a,b,c) -> GREATEST
10622                                "MIN" | "MAX" if f.args.len() > 1 && !matches!(target, DialectType::SQLite) => Action::MinMaxToLeastGreatest,
10623                                // ClickHouse uniq -> APPROX_COUNT_DISTINCT for other dialects
10624                                "UNIQ" if matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseUniqToApproxCountDistinct,
10625                                // ClickHouse any -> ANY_VALUE for other dialects
10626                                "ANY" if f.args.len() == 1 && matches!(source, DialectType::ClickHouse) && !matches!(target, DialectType::ClickHouse) => Action::ClickHouseAnyToAnyValue,
10627                                _ => Action::None,
10628                            }
10629                        }
10630                    }
10631                    Expression::AggregateFunction(af) => {
10632                        let name = af.name.to_ascii_uppercase();
10633                        match name.as_str() {
10634                            "ARBITRARY" | "AGGREGATE" => Action::GenericFunctionNormalize,
10635                            "JSON_AGG" | "JSONB_AGG"
10636                                if Self::is_postgres_family_source(source)
10637                                    && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
10638                            {
10639                                Action::PostgresJsonAggToJsonArrayAgg
10640                            }
10641                            "JSON_ARRAYAGG" => Action::GenericFunctionNormalize,
10642                            // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
10643                            "JSON_OBJECT_AGG" | "JSONB_OBJECT_AGG"
10644                                if matches!(target, DialectType::DuckDB) =>
10645                            {
10646                                Action::JsonObjectAggConvert
10647                            }
10648                            "ARRAY_AGG"
10649                                if matches!(
10650                                    target,
10651                                    DialectType::Hive
10652                                        | DialectType::Spark
10653                                        | DialectType::Databricks
10654                                ) =>
10655                            {
10656                                Action::ArrayAggToCollectList
10657                            }
10658                            "MAX_BY" | "MIN_BY"
10659                                if matches!(
10660                                    target,
10661                                    DialectType::ClickHouse
10662                                        | DialectType::Spark
10663                                        | DialectType::Databricks
10664                                        | DialectType::DuckDB
10665                                ) =>
10666                            {
10667                                Action::MaxByMinByConvert
10668                            }
10669                            "COLLECT_LIST"
10670                                if matches!(
10671                                    target,
10672                                    DialectType::Presto | DialectType::Trino | DialectType::DuckDB
10673                                ) =>
10674                            {
10675                                Action::CollectListToArrayAgg
10676                            }
10677                            "COLLECT_SET"
10678                                if matches!(
10679                                    target,
10680                                    DialectType::Presto
10681                                        | DialectType::Trino
10682                                        | DialectType::Snowflake
10683                                        | DialectType::DuckDB
10684                                ) =>
10685                            {
10686                                Action::CollectSetConvert
10687                            }
10688                            "PERCENTILE"
10689                                if matches!(
10690                                    target,
10691                                    DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10692                                ) =>
10693                            {
10694                                Action::PercentileConvert
10695                            }
10696                            // CORR -> CASE WHEN ISNAN(CORR(a,b)) THEN NULL ELSE CORR(a,b) END for DuckDB
10697                            "CORR"
10698                                if matches!(target, DialectType::DuckDB)
10699                                    && matches!(source, DialectType::Snowflake) =>
10700                            {
10701                                Action::CorrIsnanWrap
10702                            }
10703                            // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
10704                            "APPROX_QUANTILES"
10705                                if matches!(source, DialectType::BigQuery)
10706                                    && matches!(target, DialectType::DuckDB) =>
10707                            {
10708                                Action::BigQueryApproxQuantiles
10709                            }
10710                            // BigQuery PERCENTILE_CONT(x, frac RESPECT NULLS) -> QUANTILE_CONT(x, frac) for DuckDB
10711                            "PERCENTILE_CONT"
10712                                if matches!(source, DialectType::BigQuery)
10713                                    && matches!(target, DialectType::DuckDB)
10714                                    && af.args.len() >= 2 =>
10715                            {
10716                                Action::BigQueryPercentileContToDuckDB
10717                            }
10718                            _ => Action::None,
10719                        }
10720                    }
10721                    Expression::JSONArrayAgg(_) => match target {
10722                        DialectType::PostgreSQL => Action::GenericFunctionNormalize,
10723                        _ => Action::None,
10724                    },
10725                    Expression::ToNumber(tn) => {
10726                        // TO_NUMBER(x) with 1 arg -> CAST(x AS DOUBLE) for most targets
10727                        if tn.format.is_none() && tn.precision.is_none() && tn.scale.is_none() {
10728                            match target {
10729                                DialectType::Oracle
10730                                | DialectType::Snowflake
10731                                | DialectType::Teradata => Action::None,
10732                                _ => Action::GenericFunctionNormalize,
10733                            }
10734                        } else {
10735                            Action::None
10736                        }
10737                    }
10738                    Expression::Nvl2(_) => {
10739                        // NVL2(a, b, c) -> CASE WHEN NOT a IS NULL THEN b ELSE c END for most dialects
10740                        // Keep as NVL2 for dialects that support it natively
10741                        match target {
10742                            DialectType::Oracle
10743                            | DialectType::Snowflake
10744                            | DialectType::Teradata
10745                            | DialectType::Spark
10746                            | DialectType::Databricks
10747                            | DialectType::Redshift => Action::None,
10748                            _ => Action::Nvl2Expand,
10749                        }
10750                    }
10751                    Expression::Decode(_) | Expression::DecodeCase(_) => {
10752                        // DECODE(a, b, c[, d, e[, ...]]) -> CASE WHEN with null-safe comparisons
10753                        // Keep as DECODE for Oracle/Snowflake
10754                        match target {
10755                            DialectType::Oracle | DialectType::Snowflake => Action::None,
10756                            _ => Action::DecodeSimplify,
10757                        }
10758                    }
10759                    Expression::Coalesce(ref cf) => {
10760                        // IFNULL(a, b) -> COALESCE(a, b): clear original_name for cross-dialect
10761                        // BigQuery keeps IFNULL natively when source is also BigQuery
10762                        if cf.original_name.as_deref() == Some("IFNULL")
10763                            && !(matches!(source, DialectType::BigQuery)
10764                                && matches!(target, DialectType::BigQuery))
10765                        {
10766                            Action::IfnullToCoalesce
10767                        } else {
10768                            Action::None
10769                        }
10770                    }
10771                    Expression::IfFunc(if_func) => {
10772                        if matches!(source, DialectType::Snowflake)
10773                            && matches!(
10774                                target,
10775                                DialectType::Presto | DialectType::Trino | DialectType::SQLite
10776                            )
10777                            && matches!(if_func.false_value, Some(Expression::Div(_)))
10778                        {
10779                            Action::Div0TypedDivision
10780                        } else {
10781                            Action::None
10782                        }
10783                    }
10784                    Expression::ToJson(_) => match target {
10785                        DialectType::Presto | DialectType::Trino => Action::ToJsonConvert,
10786                        DialectType::BigQuery => Action::ToJsonConvert,
10787                        DialectType::DuckDB => Action::ToJsonConvert,
10788                        _ => Action::None,
10789                    },
10790                    Expression::ArrayAgg(ref agg) => {
10791                        if matches!(target, DialectType::MySQL | DialectType::SingleStore) {
10792                            Action::ArrayAggToGroupConcat
10793                        } else if matches!(
10794                            target,
10795                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
10796                        ) {
10797                            // Any source -> Hive/Spark: convert ARRAY_AGG to COLLECT_LIST
10798                            Action::ArrayAggToCollectList
10799                        } else if matches!(
10800                            source,
10801                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
10802                        ) && matches!(target, DialectType::DuckDB)
10803                            && agg.filter.is_some()
10804                        {
10805                            // Spark/Hive ARRAY_AGG excludes NULLs, DuckDB includes them
10806                            // Need to add NOT x IS NULL to existing filter
10807                            Action::ArrayAggNullFilter
10808                        } else if matches!(target, DialectType::DuckDB)
10809                            && agg.ignore_nulls == Some(true)
10810                            && !agg.order_by.is_empty()
10811                        {
10812                            // BigQuery ARRAY_AGG(x IGNORE NULLS ORDER BY ...) -> DuckDB ARRAY_AGG(x ORDER BY a NULLS FIRST, ...)
10813                            Action::ArrayAggIgnoreNullsDuckDB
10814                        } else if !matches!(source, DialectType::Snowflake) {
10815                            Action::None
10816                        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
10817                            let is_array_agg = agg.name.as_deref().map_or(false, |n| n.eq_ignore_ascii_case("ARRAY_AGG"))
10818                                || agg.name.is_none();
10819                            if is_array_agg {
10820                                Action::ArrayAggCollectList
10821                            } else {
10822                                Action::None
10823                            }
10824                        } else if matches!(
10825                            target,
10826                            DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10827                        ) && agg.filter.is_none()
10828                        {
10829                            Action::ArrayAggFilter
10830                        } else {
10831                            Action::None
10832                        }
10833                    }
10834                    Expression::WithinGroup(wg) => {
10835                        if matches!(source, DialectType::Snowflake)
10836                            && matches!(
10837                                target,
10838                                DialectType::DuckDB | DialectType::Presto | DialectType::Trino
10839                            )
10840                            && matches!(wg.this, Expression::ArrayAgg(_))
10841                        {
10842                            Action::ArrayAggWithinGroupFilter
10843                        } else if matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("STRING_AGG"))
10844                            || matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("STRING_AGG"))
10845                            || matches!(&wg.this, Expression::StringAgg(_))
10846                        {
10847                            Action::StringAggConvert
10848                        } else if matches!(
10849                            target,
10850                            DialectType::Presto
10851                                | DialectType::Trino
10852                                | DialectType::Athena
10853                                | DialectType::Spark
10854                                | DialectType::Databricks
10855                        ) && (matches!(&wg.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("PERCENTILE_CONT") || f.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
10856                            || matches!(&wg.this, Expression::AggregateFunction(af) if af.name.eq_ignore_ascii_case("PERCENTILE_CONT") || af.name.eq_ignore_ascii_case("PERCENTILE_DISC"))
10857                            || matches!(&wg.this, Expression::PercentileCont(_)))
10858                        {
10859                            Action::PercentileContConvert
10860                        } else {
10861                            Action::None
10862                        }
10863                    }
10864                    // For BigQuery: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
10865                    // because BigQuery's TIMESTAMP is really TIMESTAMPTZ, and
10866                    // DATETIME is the timezone-unaware type
10867                    Expression::Cast(ref c) => {
10868                        if c.format.is_some()
10869                            && (matches!(source, DialectType::BigQuery)
10870                                || matches!(source, DialectType::Teradata))
10871                        {
10872                            Action::BigQueryCastFormat
10873                        } else if matches!(target, DialectType::BigQuery)
10874                            && !matches!(source, DialectType::BigQuery)
10875                            && matches!(
10876                                c.to,
10877                                DataType::Timestamp {
10878                                    timezone: false,
10879                                    ..
10880                                }
10881                            )
10882                        {
10883                            Action::CastTimestampToDatetime
10884                        } else if matches!(target, DialectType::MySQL | DialectType::StarRocks)
10885                            && !matches!(source, DialectType::MySQL | DialectType::StarRocks)
10886                            && matches!(
10887                                c.to,
10888                                DataType::Timestamp {
10889                                    timezone: false,
10890                                    ..
10891                                }
10892                            )
10893                        {
10894                            // Generic/other -> MySQL/StarRocks: CAST(x AS TIMESTAMP) -> CAST(x AS DATETIME)
10895                            // but MySQL-native CAST(x AS TIMESTAMP) stays as TIMESTAMP(x) via transform_cast
10896                            Action::CastTimestampToDatetime
10897                        } else if matches!(
10898                            source,
10899                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
10900                        ) && matches!(
10901                            target,
10902                            DialectType::Presto
10903                                | DialectType::Trino
10904                                | DialectType::Athena
10905                                | DialectType::DuckDB
10906                                | DialectType::Snowflake
10907                                | DialectType::BigQuery
10908                                | DialectType::Databricks
10909                                | DialectType::TSQL
10910                        ) {
10911                            Action::HiveCastToTryCast
10912                        } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
10913                            && matches!(target, DialectType::MySQL | DialectType::StarRocks)
10914                        {
10915                            // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
10916                            Action::CastTimestamptzToFunc
10917                        } else if matches!(c.to, DataType::Timestamp { timezone: true, .. })
10918                            && matches!(
10919                                target,
10920                                DialectType::Hive
10921                                    | DialectType::Spark
10922                                    | DialectType::Databricks
10923                                    | DialectType::BigQuery
10924                            )
10925                        {
10926                            // CAST(x AS TIMESTAMP WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
10927                            Action::CastTimestampStripTz
10928                        } else if matches!(&c.to, DataType::Json)
10929                            && matches!(source, DialectType::DuckDB)
10930                            && matches!(target, DialectType::Snowflake)
10931                        {
10932                            Action::DuckDBCastJsonToVariant
10933                        } else if matches!(&c.to, DataType::Json)
10934                            && matches!(&c.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
10935                            && matches!(
10936                                target,
10937                                DialectType::Presto
10938                                    | DialectType::Trino
10939                                    | DialectType::Athena
10940                                    | DialectType::Snowflake
10941                            )
10942                        {
10943                            // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
10944                            // Only when the input is a string literal (JSON 'value' syntax)
10945                            Action::JsonLiteralToJsonParse
10946                        } else if matches!(&c.to, DataType::Json)
10947                            && matches!(source, DialectType::DuckDB)
10948                            && matches!(
10949                                target,
10950                                DialectType::Presto | DialectType::Trino | DialectType::Athena
10951                            )
10952                        {
10953                            // DuckDB's CAST(x AS JSON) parses the string value into a JSON value.
10954                            // Trino/Presto/Athena's CAST(x AS JSON) instead wraps the value as a
10955                            // JSON string (no parsing) — different semantics. Use JSON_PARSE(x)
10956                            // in the target to preserve DuckDB's parse semantics.
10957                            Action::JsonLiteralToJsonParse
10958                        } else if matches!(&c.to, DataType::Json | DataType::JsonB)
10959                            && matches!(target, DialectType::Spark | DialectType::Databricks)
10960                        {
10961                            // CAST(x AS JSON) -> TO_JSON(x) for Spark
10962                            Action::CastToJsonForSpark
10963                        } else if (matches!(
10964                            &c.to,
10965                            DataType::Array { .. } | DataType::Map { .. } | DataType::Struct { .. }
10966                        )) && matches!(
10967                            target,
10968                            DialectType::Spark | DialectType::Databricks
10969                        ) && (matches!(&c.this, Expression::ParseJson(_))
10970                            || matches!(
10971                                &c.this,
10972                                Expression::Function(f)
10973                                    if f.name.eq_ignore_ascii_case("JSON_EXTRACT")
10974                                        || f.name.eq_ignore_ascii_case("JSON_EXTRACT_SCALAR")
10975                                        || f.name.eq_ignore_ascii_case("GET_JSON_OBJECT")
10976                            ))
10977                        {
10978                            // CAST(JSON_PARSE(...) AS ARRAY/MAP) or CAST(JSON_EXTRACT/GET_JSON_OBJECT(...) AS ARRAY/MAP)
10979                            // -> FROM_JSON(..., type_string) for Spark
10980                            Action::CastJsonToFromJson
10981                        } else if matches!(target, DialectType::Spark | DialectType::Databricks)
10982                            && matches!(
10983                                c.to,
10984                                DataType::Timestamp {
10985                                    timezone: false,
10986                                    ..
10987                                }
10988                            )
10989                            && matches!(source, DialectType::DuckDB)
10990                        {
10991                            Action::StrftimeCastTimestamp
10992                        } else if matches!(source, DialectType::DuckDB)
10993                            && matches!(
10994                                c.to,
10995                                DataType::Decimal {
10996                                    precision: None,
10997                                    ..
10998                                }
10999                            )
11000                        {
11001                            Action::DecimalDefaultPrecision
11002                        } else if matches!(source, DialectType::MySQL | DialectType::SingleStore)
11003                            && matches!(c.to, DataType::Char { length: None })
11004                            && !matches!(target, DialectType::MySQL | DialectType::SingleStore)
11005                        {
11006                            // MySQL CAST(x AS CHAR) was originally TEXT - convert to target text type
11007                            Action::MysqlCastCharToText
11008                        } else if matches!(
11009                            source,
11010                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
11011                        ) && matches!(
11012                            target,
11013                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
11014                        ) && Self::has_varchar_char_type(&c.to)
11015                        {
11016                            // Spark parses VARCHAR(n)/CHAR(n) as TEXT, so normalize back to STRING
11017                            Action::SparkCastVarcharToString
11018                        } else {
11019                            Action::None
11020                        }
11021                    }
11022                    Expression::SafeCast(ref c) => {
11023                        if c.format.is_some()
11024                            && matches!(source, DialectType::BigQuery)
11025                            && !matches!(target, DialectType::BigQuery)
11026                        {
11027                            Action::BigQueryCastFormat
11028                        } else {
11029                            Action::None
11030                        }
11031                    }
11032                    Expression::TryCast(ref c) => {
11033                        if matches!(&c.to, DataType::Json)
11034                            && matches!(source, DialectType::DuckDB)
11035                            && matches!(
11036                                target,
11037                                DialectType::Presto | DialectType::Trino | DialectType::Athena
11038                            )
11039                        {
11040                            // DuckDB's TRY_CAST(x AS JSON) tries to parse x as JSON, returning
11041                            // NULL on parse failure. Trino/Presto/Athena's TRY_CAST(x AS JSON)
11042                            // wraps the value as a JSON string (no parse). Emit TRY(JSON_PARSE(x))
11043                            // to preserve DuckDB's parse-or-null semantics.
11044                            Action::DuckDBTryCastJsonToTryJsonParse
11045                        } else {
11046                            Action::None
11047                        }
11048                    }
11049                    Expression::JSONArray(ref ja)
11050                        if matches!(target, DialectType::Snowflake)
11051                            && ja.null_handling.is_none()
11052                            && ja.return_type.is_none()
11053                            && ja.strict.is_none() =>
11054                    {
11055                        Action::GenericFunctionNormalize
11056                    }
11057                    Expression::JsonArray(_) if matches!(target, DialectType::Snowflake) => {
11058                        Action::GenericFunctionNormalize
11059                    }
11060                    // For DuckDB: DATE_TRUNC should preserve the input type
11061                    Expression::DateTrunc(_) | Expression::TimestampTrunc(_) => {
11062                        if matches!(source, DialectType::Snowflake)
11063                            && matches!(target, DialectType::DuckDB)
11064                        {
11065                            Action::DateTruncWrapCast
11066                        } else {
11067                            Action::None
11068                        }
11069                    }
11070                    // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
11071                    Expression::SetStatement(s) => {
11072                        if matches!(target, DialectType::DuckDB)
11073                            && !matches!(source, DialectType::TSQL | DialectType::Fabric)
11074                            && s.items.iter().any(|item| item.kind.is_none())
11075                        {
11076                            Action::SetToVariable
11077                        } else {
11078                            Action::None
11079                        }
11080                    }
11081                    // Cross-dialect NULL ordering normalization.
11082                    // When nulls_first is not specified, fill in the source dialect's implied
11083                    // default so the target generator can correctly add/strip NULLS FIRST/LAST.
11084                    Expression::Ordered(o) => {
11085                        // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
11086                        if matches!(target, DialectType::MySQL) && o.nulls_first.is_some() {
11087                            Action::MysqlNullsOrdering
11088                        } else {
11089                            // Skip targets that don't support NULLS FIRST/LAST syntax unless
11090                            // the generator can preserve semantics with a CASE sort key.
11091                            let target_rewrites_nulls =
11092                                matches!(target, DialectType::TSQL | DialectType::Fabric);
11093                            let target_supports_nulls = !matches!(
11094                                target,
11095                                DialectType::MySQL
11096                                    | DialectType::TSQL
11097                                    | DialectType::Fabric
11098                                    | DialectType::StarRocks
11099                                    | DialectType::Doris
11100                            );
11101                            if o.nulls_first.is_none()
11102                                && source != target
11103                                && (target_supports_nulls || target_rewrites_nulls)
11104                            {
11105                                Action::NullsOrdering
11106                            } else {
11107                                Action::None
11108                            }
11109                        }
11110                    }
11111                    // BigQuery data types: convert INT64, BYTES, NUMERIC etc. to standard types
11112                    Expression::DataType(dt) => {
11113                        if matches!(source, DialectType::BigQuery)
11114                            && !matches!(target, DialectType::BigQuery)
11115                        {
11116                            match dt {
11117                                DataType::Custom { ref name }
11118                                    if name.eq_ignore_ascii_case("INT64")
11119                                        || name.eq_ignore_ascii_case("FLOAT64")
11120                                        || name.eq_ignore_ascii_case("BOOL")
11121                                        || name.eq_ignore_ascii_case("BYTES")
11122                                        || name.eq_ignore_ascii_case("NUMERIC")
11123                                        || name.eq_ignore_ascii_case("STRING")
11124                                        || name.eq_ignore_ascii_case("DATETIME") =>
11125                                {
11126                                    Action::BigQueryCastType
11127                                }
11128                                _ => Action::None,
11129                            }
11130                        } else if matches!(source, DialectType::TSQL) {
11131                            // For TSQL source -> any target (including TSQL itself for REAL)
11132                            match dt {
11133                                // REAL -> FLOAT even for TSQL->TSQL
11134                                DataType::Custom { ref name }
11135                                    if name.eq_ignore_ascii_case("REAL") =>
11136                                {
11137                                    Action::TSQLTypeNormalize
11138                                }
11139                                DataType::Float {
11140                                    real_spelling: true,
11141                                    ..
11142                                } => Action::TSQLTypeNormalize,
11143                                // Other TSQL type normalizations only for non-TSQL targets
11144                                DataType::Custom { ref name }
11145                                    if !matches!(target, DialectType::TSQL)
11146                                        && (name.eq_ignore_ascii_case("MONEY")
11147                                            || name.eq_ignore_ascii_case("SMALLMONEY")
11148                                            || name.eq_ignore_ascii_case("DATETIME2")
11149                                            || name.eq_ignore_ascii_case("IMAGE")
11150                                            || name.eq_ignore_ascii_case("BIT")
11151                                            || name.eq_ignore_ascii_case("ROWVERSION")
11152                                            || name.eq_ignore_ascii_case("UNIQUEIDENTIFIER")
11153                                            || name.eq_ignore_ascii_case("DATETIMEOFFSET")
11154                                            || (name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC"))
11155                                            || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("DATETIME2("))
11156                                            || (name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME("))) =>
11157                                {
11158                                    Action::TSQLTypeNormalize
11159                                }
11160                                DataType::Float {
11161                                    precision: Some(_), ..
11162                                } if !matches!(target, DialectType::TSQL) => {
11163                                    Action::TSQLTypeNormalize
11164                                }
11165                                DataType::TinyInt { .. }
11166                                    if !matches!(target, DialectType::TSQL) =>
11167                                {
11168                                    Action::TSQLTypeNormalize
11169                                }
11170                                // INTEGER -> INT for Databricks/Spark targets
11171                                DataType::Int {
11172                                    integer_spelling: true,
11173                                    ..
11174                                } if matches!(
11175                                    target,
11176                                    DialectType::Databricks | DialectType::Spark
11177                                ) =>
11178                                {
11179                                    Action::TSQLTypeNormalize
11180                                }
11181                                _ => Action::None,
11182                            }
11183                        } else if (matches!(source, DialectType::Oracle)
11184                            || matches!(source, DialectType::Generic))
11185                            && !matches!(target, DialectType::Oracle)
11186                        {
11187                            match dt {
11188                                DataType::Custom { ref name }
11189                                    if (name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2("))
11190                                        || (name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2("))
11191                                        || name.eq_ignore_ascii_case("VARCHAR2")
11192                                        || name.eq_ignore_ascii_case("NVARCHAR2") =>
11193                                {
11194                                    Action::OracleVarchar2ToVarchar
11195                                }
11196                                _ => Action::None,
11197                            }
11198                        } else if matches!(target, DialectType::Snowflake)
11199                            && !matches!(source, DialectType::Snowflake)
11200                        {
11201                            // When target is Snowflake but source is NOT Snowflake,
11202                            // protect FLOAT from being converted to DOUBLE by Snowflake's transform.
11203                            // Snowflake treats FLOAT=DOUBLE internally, but non-Snowflake sources
11204                            // should keep their FLOAT spelling.
11205                            match dt {
11206                                DataType::Float { .. } => Action::SnowflakeFloatProtect,
11207                                _ => Action::None,
11208                            }
11209                        } else {
11210                            Action::None
11211                        }
11212                    }
11213                    // LOWER patterns from BigQuery TO_HEX conversions:
11214                    // - LOWER(LOWER(HEX(x))) from non-BQ targets: flatten
11215                    // - LOWER(Function("TO_HEX")) for BQ->BQ: strip LOWER
11216                    Expression::Lower(uf) => {
11217                        if matches!(source, DialectType::BigQuery) {
11218                            match &uf.this {
11219                                Expression::Lower(_) => Action::BigQueryToHexLower,
11220                                Expression::Function(f)
11221                                    if f.name == "TO_HEX"
11222                                        && matches!(target, DialectType::BigQuery) =>
11223                                {
11224                                    // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
11225                                    Action::BigQueryToHexLower
11226                                }
11227                                _ => Action::None,
11228                            }
11229                        } else {
11230                            Action::None
11231                        }
11232                    }
11233                    // UPPER patterns from BigQuery TO_HEX conversions:
11234                    // - UPPER(LOWER(HEX(x))) from non-BQ targets: extract inner
11235                    // - UPPER(Function("TO_HEX")) for BQ->BQ: keep as UPPER(TO_HEX(x))
11236                    Expression::Upper(uf) => {
11237                        if matches!(source, DialectType::BigQuery) {
11238                            match &uf.this {
11239                                Expression::Lower(_) => Action::BigQueryToHexUpper,
11240                                _ => Action::None,
11241                            }
11242                        } else {
11243                            Action::None
11244                        }
11245                    }
11246                    // BigQuery LAST_DAY(date, unit) -> strip unit for non-BigQuery targets
11247                    // Snowflake supports LAST_DAY with unit, so keep it there
11248                    Expression::LastDay(ld) => {
11249                        if matches!(source, DialectType::BigQuery)
11250                            && !matches!(target, DialectType::BigQuery | DialectType::Snowflake)
11251                            && ld.unit.is_some()
11252                        {
11253                            Action::BigQueryLastDayStripUnit
11254                        } else {
11255                            Action::None
11256                        }
11257                    }
11258                    // BigQuery SafeDivide expressions (already parsed as SafeDivide)
11259                    Expression::SafeDivide(_) => {
11260                        if matches!(source, DialectType::BigQuery)
11261                            && !matches!(target, DialectType::BigQuery)
11262                        {
11263                            Action::BigQuerySafeDivide
11264                        } else {
11265                            Action::None
11266                        }
11267                    }
11268                    // BigQuery ANY_VALUE(x HAVING MAX/MIN y) -> ARG_MAX_NULL/ARG_MIN_NULL for DuckDB
11269                    // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
11270                    Expression::AnyValue(ref agg) => {
11271                        if matches!(source, DialectType::BigQuery)
11272                            && matches!(target, DialectType::DuckDB)
11273                            && agg.having_max.is_some()
11274                        {
11275                            Action::BigQueryAnyValueHaving
11276                        } else if matches!(target, DialectType::Spark | DialectType::Databricks)
11277                            && !matches!(source, DialectType::Spark | DialectType::Databricks)
11278                            && agg.ignore_nulls.is_none()
11279                        {
11280                            Action::AnyValueIgnoreNulls
11281                        } else {
11282                            Action::None
11283                        }
11284                    }
11285                    Expression::Any(ref q) => {
11286                        if matches!(source, DialectType::PostgreSQL)
11287                            && matches!(
11288                                target,
11289                                DialectType::Spark | DialectType::Databricks | DialectType::Hive
11290                            )
11291                            && q.op.is_some()
11292                            && !matches!(
11293                                q.subquery,
11294                                Expression::Select(_) | Expression::Subquery(_)
11295                            )
11296                        {
11297                            Action::AnyToExists
11298                        } else {
11299                            Action::None
11300                        }
11301                    }
11302                    // BigQuery APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [quantiles]) for DuckDB
11303                    // Snowflake RLIKE does full-string match; DuckDB REGEXP_FULL_MATCH also does full-string match
11304                    Expression::RegexpLike(_)
11305                        if matches!(source, DialectType::Snowflake)
11306                            && matches!(target, DialectType::DuckDB) =>
11307                    {
11308                        Action::RlikeSnowflakeToDuckDB
11309                    }
11310                    // PostgreSQL regex predicates have no native T-SQL/Fabric equivalent.
11311                    // Default mode emits a best-effort PATINDEX predicate; strict mode rejects
11312                    // before this rewrite runs.
11313                    Expression::RegexpLike(_) | Expression::RegexpILike(_)
11314                        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
11315                            && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11316                    {
11317                        Action::RegexpLikeToTsqlPatindex
11318                    }
11319                    Expression::SimilarTo(s)
11320                        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
11321                            && matches!(target, DialectType::TSQL | DialectType::Fabric)
11322                            && Self::similar_to_can_lower_to_tsql_like(s) =>
11323                    {
11324                        Action::SimilarToToTsqlLike
11325                    }
11326                    // RegexpLike from non-DuckDB/non-Snowflake sources -> REGEXP_MATCHES for DuckDB target
11327                    Expression::RegexpLike(_)
11328                        if !matches!(source, DialectType::DuckDB)
11329                            && matches!(target, DialectType::DuckDB) =>
11330                    {
11331                        Action::RegexpLikeToDuckDB
11332                    }
11333                    // RegexpLike -> Exasol: anchor pattern with .*...*
11334                    Expression::RegexpLike(_)
11335                        if matches!(target, DialectType::Exasol) =>
11336                    {
11337                        Action::RegexpLikeExasolAnchor
11338                    }
11339                    // Safe-division source -> non-safe target: NULLIF wrapping and/or CAST
11340                    // Safe-division dialects: MySQL, DuckDB, SingleStore, TiDB, ClickHouse, Doris
11341                    Expression::Div(ref op)
11342                        if matches!(
11343                            source,
11344                            DialectType::MySQL
11345                                | DialectType::DuckDB
11346                                | DialectType::SingleStore
11347                                | DialectType::TiDB
11348                                | DialectType::ClickHouse
11349                                | DialectType::Doris
11350                        ) && matches!(
11351                            target,
11352                            DialectType::PostgreSQL
11353                                | DialectType::Redshift
11354                                | DialectType::Drill
11355                                | DialectType::Trino
11356                                | DialectType::Presto
11357                                | DialectType::Athena
11358                                | DialectType::TSQL
11359                                | DialectType::Teradata
11360                                | DialectType::SQLite
11361                                | DialectType::BigQuery
11362                                | DialectType::Snowflake
11363                                | DialectType::Databricks
11364                                | DialectType::Oracle
11365                                | DialectType::Materialize
11366                                | DialectType::RisingWave
11367                        ) =>
11368                    {
11369                        // Only wrap if RHS is not already NULLIF
11370                        if !matches!(&op.right, Expression::Function(f) if f.name.eq_ignore_ascii_case("NULLIF"))
11371                        {
11372                            Action::MySQLSafeDivide
11373                        } else {
11374                            Action::None
11375                        }
11376                    }
11377                    // ALTER TABLE ... RENAME TO <schema>.<table> -> strip schema for most targets
11378                    // For TSQL/Fabric, convert to sp_rename instead
11379                    Expression::AlterTable(ref at) if !at.actions.is_empty() => {
11380                        if let Some(crate::expressions::AlterTableAction::RenameTable(
11381                            ref new_tbl,
11382                        )) = at.actions.first()
11383                        {
11384                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
11385                                // TSQL: ALTER TABLE RENAME -> EXEC sp_rename
11386                                Action::AlterTableToSpRename
11387                            } else if new_tbl.schema.is_some()
11388                                && matches!(
11389                                    target,
11390                                    DialectType::BigQuery
11391                                        | DialectType::Doris
11392                                        | DialectType::StarRocks
11393                                        | DialectType::DuckDB
11394                                        | DialectType::PostgreSQL
11395                                        | DialectType::Redshift
11396                                )
11397                            {
11398                                Action::AlterTableRenameStripSchema
11399                            } else {
11400                                Action::None
11401                            }
11402                        } else {
11403                            Action::None
11404                        }
11405                    }
11406                    // EPOCH(x) expression -> target-specific epoch conversion
11407                    Expression::Epoch(_) if !matches!(target, DialectType::DuckDB) => {
11408                        Action::EpochConvert
11409                    }
11410                    // EPOCH_MS(x) expression -> target-specific epoch ms conversion
11411                    Expression::EpochMs(_) if !matches!(target, DialectType::DuckDB) => {
11412                        Action::EpochMsConvert
11413                    }
11414                    // STRING_AGG -> GROUP_CONCAT for MySQL/SQLite
11415                    Expression::StringAgg(_) => {
11416                        if matches!(
11417                            target,
11418                            DialectType::MySQL
11419                                | DialectType::SingleStore
11420                                | DialectType::Doris
11421                                | DialectType::StarRocks
11422                                | DialectType::SQLite
11423                        ) {
11424                            Action::StringAggConvert
11425                        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
11426                            Action::StringAggConvert
11427                        } else {
11428                            Action::None
11429                        }
11430                    }
11431                    Expression::CombinedParameterizedAgg(_) => Action::GenericFunctionNormalize,
11432                    // GROUP_CONCAT -> STRING_AGG for PostgreSQL/Presto/etc.
11433                    // Also handles GROUP_CONCAT normalization for MySQL/SQLite targets
11434                    Expression::GroupConcat(_) => Action::GroupConcatConvert,
11435                    // CARDINALITY/ARRAY_LENGTH/ARRAY_SIZE -> target-specific array length
11436                    // DuckDB CARDINALITY -> keep as CARDINALITY for DuckDB target (used for maps)
11437                    Expression::Cardinality(_)
11438                        if matches!(source, DialectType::DuckDB)
11439                            && matches!(target, DialectType::DuckDB) =>
11440                    {
11441                        Action::None
11442                    }
11443                    Expression::Cardinality(_) | Expression::ArrayLength(_) => {
11444                        Action::ArrayLengthConvert
11445                    }
11446                    Expression::ArraySize(_) => {
11447                        if matches!(target, DialectType::Drill) {
11448                            Action::ArraySizeDrill
11449                        } else {
11450                            Action::ArrayLengthConvert
11451                        }
11452                    }
11453                    // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter/ARRAY subquery
11454                    Expression::ArrayRemove(_) => match target {
11455                        DialectType::DuckDB | DialectType::ClickHouse | DialectType::BigQuery => {
11456                            Action::ArrayRemoveConvert
11457                        }
11458                        _ => Action::None,
11459                    },
11460                    // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse
11461                    Expression::ArrayReverse(_) => match target {
11462                        DialectType::ClickHouse => Action::ArrayReverseConvert,
11463                        _ => Action::None,
11464                    },
11465                    // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS for Spark/Databricks/Snowflake
11466                    Expression::JsonKeys(_) => match target {
11467                        DialectType::Spark | DialectType::Databricks | DialectType::Snowflake => {
11468                            Action::JsonKeysConvert
11469                        }
11470                        _ => Action::None,
11471                    },
11472                    // PARSE_JSON(x) -> strip for SQLite/Doris/MySQL/StarRocks
11473                    Expression::ParseJson(_) => match target {
11474                        DialectType::SQLite
11475                        | DialectType::Doris
11476                        | DialectType::MySQL
11477                        | DialectType::StarRocks => Action::ParseJsonStrip,
11478                        _ => Action::None,
11479                    },
11480                    // WeekOfYear -> WEEKISO for Snowflake (cross-dialect only)
11481                    Expression::WeekOfYear(_)
11482                        if matches!(target, DialectType::Snowflake)
11483                            && !matches!(source, DialectType::Snowflake) =>
11484                    {
11485                        Action::WeekOfYearToWeekIso
11486                    }
11487                    // NVL: clear original_name so generator uses dialect-specific function names
11488                    Expression::Nvl(f) if f.original_name.is_some() => Action::NvlClearOriginal,
11489                    // XOR: expand for dialects that don't support the XOR keyword
11490                    Expression::Xor(_) => {
11491                        let target_supports_xor = matches!(
11492                            target,
11493                            DialectType::MySQL
11494                                | DialectType::SingleStore
11495                                | DialectType::Doris
11496                                | DialectType::StarRocks
11497                        );
11498                        if !target_supports_xor {
11499                            Action::XorExpand
11500                        } else {
11501                            Action::None
11502                        }
11503                    }
11504                    // TSQL #table -> temp table normalization (CREATE TABLE)
11505                    Expression::CreateTable(ct)
11506                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
11507                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11508                            && ct.name.name.name.starts_with('#') =>
11509                    {
11510                        Action::TempTableHash
11511                    }
11512                    // TSQL #table -> strip # from table references in SELECT/etc.
11513                    Expression::Table(tr)
11514                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
11515                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11516                            && tr.name.name.starts_with('#') =>
11517                    {
11518                        Action::TempTableHash
11519                    }
11520                    // TSQL #table -> strip # from DROP TABLE names
11521                    Expression::DropTable(ref dt)
11522                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
11523                            && !matches!(target, DialectType::TSQL | DialectType::Fabric)
11524                            && dt.names.iter().any(|n| n.name.name.starts_with('#')) =>
11525                    {
11526                        Action::TempTableHash
11527                    }
11528                    // JSON_EXTRACT / PostgreSQL `->` -> T-SQL JSON functions
11529                    Expression::JsonExtract(_)
11530                        if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11531                    {
11532                        Action::JsonExtractToTsql
11533                    }
11534                    // JSON_EXTRACT_SCALAR / PostgreSQL `->>`/`#>>` -> T-SQL JSON functions
11535                    Expression::JsonExtractScalar(_)
11536                        if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11537                    {
11538                        Action::JsonExtractToTsql
11539                    }
11540                    // PostgreSQL `#>` -> T-SQL/Fabric JSON_QUERY
11541                    Expression::JsonExtractPath(_)
11542                        if matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11543                    {
11544                        Action::JsonExtractToTsql
11545                    }
11546                    // JSON_EXTRACT -> JSONExtractString for ClickHouse
11547                    Expression::JsonExtract(_) if matches!(target, DialectType::ClickHouse) => {
11548                        Action::JsonExtractToClickHouse
11549                    }
11550                    // JSON_EXTRACT_SCALAR -> JSONExtractString for ClickHouse
11551                    Expression::JsonExtractScalar(_)
11552                        if matches!(target, DialectType::ClickHouse) =>
11553                    {
11554                        Action::JsonExtractToClickHouse
11555                    }
11556                    // JSON_EXTRACT -> arrow syntax for SQLite/DuckDB
11557                    Expression::JsonExtract(ref f)
11558                        if !f.arrow_syntax
11559                            && matches!(target, DialectType::SQLite | DialectType::DuckDB) =>
11560                    {
11561                        Action::JsonExtractToArrow
11562                    }
11563                    // JSON_EXTRACT with JSONPath -> JSON_EXTRACT_PATH for PostgreSQL (non-PG sources only)
11564                    Expression::JsonExtract(ref f)
11565                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift)
11566                            && !matches!(
11567                                source,
11568                                DialectType::PostgreSQL
11569                                    | DialectType::Redshift
11570                                    | DialectType::Materialize
11571                            )
11572                            && matches!(&f.path, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$'))) =>
11573                    {
11574                        Action::JsonExtractToGetJsonObject
11575                    }
11576                    // JSON_EXTRACT -> GET_JSON_OBJECT for Hive/Spark
11577                    Expression::JsonExtract(_)
11578                        if matches!(
11579                            target,
11580                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
11581                        ) =>
11582                    {
11583                        Action::JsonExtractToGetJsonObject
11584                    }
11585                    // JSON_EXTRACT_SCALAR -> target-specific for PostgreSQL, Snowflake, SQLite
11586                    // Skip if already in arrow/hash_arrow syntax (same-dialect identity case)
11587                    Expression::JsonExtractScalar(ref f)
11588                        if !f.arrow_syntax
11589                            && !f.hash_arrow_syntax
11590                            && matches!(
11591                                target,
11592                                DialectType::PostgreSQL
11593                                    | DialectType::Redshift
11594                                    | DialectType::Snowflake
11595                                    | DialectType::SQLite
11596                                    | DialectType::DuckDB
11597                            ) =>
11598                    {
11599                        Action::JsonExtractScalarConvert
11600                    }
11601                    // JSON_EXTRACT_SCALAR -> GET_JSON_OBJECT for Hive/Spark
11602                    Expression::JsonExtractScalar(_)
11603                        if matches!(
11604                            target,
11605                            DialectType::Hive | DialectType::Spark | DialectType::Databricks
11606                        ) =>
11607                    {
11608                        Action::JsonExtractScalarToGetJsonObject
11609                    }
11610                    // JSON_EXTRACT path normalization for BigQuery, MySQL (bracket/wildcard handling)
11611                    Expression::JsonExtract(ref f)
11612                        if !f.arrow_syntax
11613                            && matches!(target, DialectType::BigQuery | DialectType::MySQL) =>
11614                    {
11615                        Action::JsonPathNormalize
11616                    }
11617                    // JsonQuery (parsed JSON_QUERY) -> target-specific
11618                    Expression::JsonQuery(_) => Action::JsonQueryValueConvert,
11619                    // JsonValue (parsed JSON_VALUE) -> target-specific
11620                    Expression::JsonValue(_) => Action::JsonQueryValueConvert,
11621                    // AT TIME ZONE -> AT_TIMEZONE for Presto, FROM_UTC_TIMESTAMP for Spark,
11622                    // TIMESTAMP(DATETIME(...)) for BigQuery, CONVERT_TIMEZONE for Snowflake
11623                    Expression::AtTimeZone(_)
11624                        if matches!(
11625                            target,
11626                            DialectType::Presto
11627                                | DialectType::Trino
11628                                | DialectType::Athena
11629                                | DialectType::Spark
11630                                | DialectType::Databricks
11631                                | DialectType::BigQuery
11632                                | DialectType::Snowflake
11633                        ) =>
11634                    {
11635                        Action::AtTimeZoneConvert
11636                    }
11637                    // DAY_OF_WEEK -> dialect-specific
11638                    Expression::DayOfWeek(_)
11639                        if matches!(
11640                            target,
11641                            DialectType::DuckDB | DialectType::Spark | DialectType::Databricks
11642                        ) =>
11643                    {
11644                        Action::DayOfWeekConvert
11645                    }
11646                    // CURRENT_USER -> CURRENT_USER() for Snowflake
11647                    Expression::CurrentUser(_) if matches!(target, DialectType::Snowflake) => {
11648                        Action::CurrentUserParens
11649                    }
11650                    // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
11651                    Expression::ElementAt(_)
11652                        if matches!(target, DialectType::PostgreSQL | DialectType::BigQuery) =>
11653                    {
11654                        Action::ElementAtConvert
11655                    }
11656                    // ARRAY[...] (ArrayFunc bracket_notation=false) -> convert for target dialect
11657                    Expression::ArrayFunc(ref arr)
11658                        if !arr.bracket_notation
11659                            && matches!(
11660                                target,
11661                                DialectType::Spark
11662                                    | DialectType::Databricks
11663                                    | DialectType::Hive
11664                                    | DialectType::BigQuery
11665                                    | DialectType::DuckDB
11666                                    | DialectType::Snowflake
11667                                    | DialectType::Presto
11668                                    | DialectType::Trino
11669                                    | DialectType::Athena
11670                                    | DialectType::ClickHouse
11671                                    | DialectType::StarRocks
11672                            ) =>
11673                    {
11674                        Action::ArraySyntaxConvert
11675                    }
11676                    // VARIANCE expression -> varSamp for ClickHouse
11677                    Expression::Variance(_) if matches!(target, DialectType::ClickHouse) => {
11678                        Action::VarianceToClickHouse
11679                    }
11680                    // STDDEV expression -> stddevSamp for ClickHouse
11681                    Expression::Stddev(_) if matches!(target, DialectType::ClickHouse) => {
11682                        Action::StddevToClickHouse
11683                    }
11684                    // ApproxQuantile -> APPROX_PERCENTILE for Snowflake
11685                    Expression::ApproxQuantile(_) if matches!(target, DialectType::Snowflake) => {
11686                        Action::ApproxQuantileConvert
11687                    }
11688                    // MonthsBetween -> target-specific
11689                    Expression::MonthsBetween(_)
11690                        if !matches!(
11691                            target,
11692                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
11693                        ) =>
11694                    {
11695                        Action::MonthsBetweenConvert
11696                    }
11697                    // AddMonths -> target-specific DATEADD/DATE_ADD
11698                    Expression::AddMonths(_) => Action::AddMonthsConvert,
11699                    // MapFromArrays -> target-specific (MAP, OBJECT_CONSTRUCT, MAP_FROM_ARRAYS)
11700                    Expression::MapFromArrays(_)
11701                        if !matches!(target, DialectType::Spark | DialectType::Databricks) =>
11702                    {
11703                        Action::MapFromArraysConvert
11704                    }
11705                    // CURRENT_USER -> CURRENT_USER() for Spark
11706                    Expression::CurrentUser(_)
11707                        if matches!(target, DialectType::Spark | DialectType::Databricks) =>
11708                    {
11709                        Action::CurrentUserSparkParens
11710                    }
11711                    // MONTH/YEAR/DAY('string') from Spark -> cast string to DATE for DuckDB/Presto
11712                    Expression::Month(ref f) | Expression::Year(ref f) | Expression::Day(ref f)
11713                        if matches!(
11714                            source,
11715                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
11716                        ) && matches!(&f.this, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
11717                            && matches!(
11718                                target,
11719                                DialectType::DuckDB
11720                                    | DialectType::Presto
11721                                    | DialectType::Trino
11722                                    | DialectType::Athena
11723                                    | DialectType::PostgreSQL
11724                                    | DialectType::Redshift
11725                            ) =>
11726                    {
11727                        Action::SparkDateFuncCast
11728                    }
11729                    // $parameter -> @parameter for BigQuery
11730                    Expression::Parameter(ref p)
11731                        if matches!(target, DialectType::BigQuery)
11732                            && matches!(source, DialectType::DuckDB)
11733                            && (p.style == crate::expressions::ParameterStyle::Dollar
11734                                || p.style == crate::expressions::ParameterStyle::DoubleDollar) =>
11735                    {
11736                        Action::DollarParamConvert
11737                    }
11738                    // EscapeString literal: normalize literal newlines to \n
11739                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::EscapeString(ref s) if s.contains('\n') || s.contains('\r') || s.contains('\t'))
11740                        =>
11741                    {
11742                        Action::EscapeStringNormalize
11743                    }
11744                    // straight_join: keep lowercase for DuckDB, quote for MySQL
11745                    Expression::Column(ref col)
11746                        if col.name.name == "STRAIGHT_JOIN"
11747                            && col.table.is_none()
11748                            && matches!(source, DialectType::DuckDB)
11749                            && matches!(target, DialectType::DuckDB | DialectType::MySQL) =>
11750                    {
11751                        Action::StraightJoinCase
11752                    }
11753                    // DATE and TIMESTAMP literal type conversions are now handled in the generator directly
11754                    // Snowflake INTERVAL format: INTERVAL '2' HOUR -> INTERVAL '2 HOUR'
11755                    Expression::Interval(ref iv)
11756                        if matches!(
11757                            target,
11758                            DialectType::Snowflake
11759                                | DialectType::PostgreSQL
11760                                | DialectType::Redshift
11761                        ) && iv.unit.is_some()
11762                            && iv.this.as_ref().map_or(false, |t| matches!(t, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))) =>
11763                    {
11764                        Action::SnowflakeIntervalFormat
11765                    }
11766                    // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB target
11767                    Expression::TableSample(ref ts) if matches!(target, DialectType::DuckDB) => {
11768                        if let Some(ref sample) = ts.sample {
11769                            if !sample.explicit_method {
11770                                Action::TablesampleReservoir
11771                            } else {
11772                                Action::None
11773                            }
11774                        } else {
11775                            Action::None
11776                        }
11777                    }
11778                    // TABLESAMPLE from non-Snowflake source to Snowflake: strip method and PERCENT
11779                    // Handles both Expression::TableSample wrapper and Expression::Table with table_sample
11780                    Expression::TableSample(ref ts)
11781                        if matches!(target, DialectType::Snowflake)
11782                            && !matches!(source, DialectType::Snowflake)
11783                            && ts.sample.is_some() =>
11784                    {
11785                        if let Some(ref sample) = ts.sample {
11786                            if !sample.explicit_method {
11787                                Action::TablesampleSnowflakeStrip
11788                            } else {
11789                                Action::None
11790                            }
11791                        } else {
11792                            Action::None
11793                        }
11794                    }
11795                    Expression::Table(ref t)
11796                        if matches!(target, DialectType::Snowflake)
11797                            && !matches!(source, DialectType::Snowflake)
11798                            && t.table_sample.is_some() =>
11799                    {
11800                        if let Some(ref sample) = t.table_sample {
11801                            if !sample.explicit_method {
11802                                Action::TablesampleSnowflakeStrip
11803                            } else {
11804                                Action::None
11805                            }
11806                        } else {
11807                            Action::None
11808                        }
11809                    }
11810                    // ALTER TABLE RENAME -> EXEC sp_rename for TSQL
11811                    Expression::AlterTable(ref at)
11812                        if matches!(target, DialectType::TSQL | DialectType::Fabric)
11813                            && !at.actions.is_empty()
11814                            && matches!(
11815                                at.actions.first(),
11816                                Some(crate::expressions::AlterTableAction::RenameTable(_))
11817                            ) =>
11818                    {
11819                        Action::AlterTableToSpRename
11820                    }
11821                    // Subscript index: 1-based to 0-based for BigQuery/Hive/Spark
11822                    Expression::Subscript(ref sub)
11823                        if matches!(
11824                            target,
11825                            DialectType::BigQuery
11826                                | DialectType::Hive
11827                                | DialectType::Spark
11828                                | DialectType::Databricks
11829                        ) && matches!(
11830                            source,
11831                            DialectType::DuckDB
11832                                | DialectType::PostgreSQL
11833                                | DialectType::Presto
11834                                | DialectType::Trino
11835                                | DialectType::Redshift
11836                                | DialectType::ClickHouse
11837                        ) && matches!(&sub.index, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(ref n) if n.parse::<i64>().unwrap_or(0) > 0)) =>
11838                    {
11839                        Action::ArrayIndexConvert
11840                    }
11841                    // ANY_VALUE IGNORE NULLS detection moved to the AnyValue arm above
11842                    // MysqlNullsOrdering for Ordered is now handled in the Ordered arm above
11843                    // RESPECT NULLS handling for SQLite (strip it, add NULLS LAST to ORDER BY)
11844                    // and for MySQL (rewrite ORDER BY with CASE WHEN for null ordering)
11845                    Expression::WindowFunction(ref wf) => {
11846                        // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
11847                        // EXCEPT for ROW_NUMBER which keeps NULLS LAST
11848                        let is_row_number = matches!(wf.this, Expression::RowNumber(_));
11849                        if matches!(target, DialectType::BigQuery)
11850                            && !is_row_number
11851                            && !wf.over.order_by.is_empty()
11852                            && wf.over.order_by.iter().any(|o| o.nulls_first.is_some())
11853                        {
11854                            Action::BigQueryNullsOrdering
11855                        // DuckDB -> MySQL: Add CASE WHEN for NULLS LAST simulation in window ORDER BY
11856                        // But NOT when frame is RANGE/GROUPS, since adding CASE WHEN would break value-based frames
11857                        } else {
11858                            let source_nulls_last = matches!(source, DialectType::DuckDB);
11859                            let has_range_frame = wf.over.frame.as_ref().map_or(false, |f| {
11860                                matches!(
11861                                    f.kind,
11862                                    crate::expressions::WindowFrameKind::Range
11863                                        | crate::expressions::WindowFrameKind::Groups
11864                                )
11865                            });
11866                            if source_nulls_last
11867                                && matches!(target, DialectType::MySQL)
11868                                && !wf.over.order_by.is_empty()
11869                                && wf.over.order_by.iter().any(|o| !o.desc)
11870                                && !has_range_frame
11871                            {
11872                                Action::MysqlNullsLastRewrite
11873                            } else {
11874                                // Check for Snowflake window frame handling for FIRST_VALUE/LAST_VALUE/NTH_VALUE
11875                                let is_ranking_window_func = matches!(
11876                                    &wf.this,
11877                                    Expression::FirstValue(_)
11878                                        | Expression::LastValue(_)
11879                                        | Expression::NthValue(_)
11880                                );
11881                                let has_full_unbounded_frame = wf.over.frame.as_ref().map_or(false, |f| {
11882                                    matches!(f.kind, crate::expressions::WindowFrameKind::Rows)
11883                                        && matches!(f.start, crate::expressions::WindowFrameBound::UnboundedPreceding)
11884                                        && matches!(f.end, Some(crate::expressions::WindowFrameBound::UnboundedFollowing))
11885                                        && f.exclude.is_none()
11886                                });
11887                                if is_ranking_window_func && matches!(source, DialectType::Snowflake) {
11888                                    if has_full_unbounded_frame && matches!(target, DialectType::Snowflake) {
11889                                        // Strip the default frame for Snowflake target
11890                                        Action::SnowflakeWindowFrameStrip
11891                                    } else if !has_full_unbounded_frame && wf.over.frame.is_none() && !matches!(target, DialectType::Snowflake) {
11892                                        // Add default frame for non-Snowflake target
11893                                        Action::SnowflakeWindowFrameAdd
11894                                    } else {
11895                                        match &wf.this {
11896                                            Expression::FirstValue(ref vf)
11897                                            | Expression::LastValue(ref vf)
11898                                                if vf.ignore_nulls == Some(false) =>
11899                                            {
11900                                                match target {
11901                                                    DialectType::SQLite => Action::RespectNullsConvert,
11902                                                    _ => Action::None,
11903                                                }
11904                                            }
11905                                            _ => Action::None,
11906                                        }
11907                                    }
11908                                } else {
11909                                    match &wf.this {
11910                                        Expression::FirstValue(ref vf)
11911                                        | Expression::LastValue(ref vf)
11912                                            if vf.ignore_nulls == Some(false) =>
11913                                        {
11914                                            // RESPECT NULLS
11915                                            match target {
11916                                                DialectType::SQLite | DialectType::PostgreSQL => {
11917                                                    Action::RespectNullsConvert
11918                                                }
11919                                                _ => Action::None,
11920                                            }
11921                                        }
11922                                        _ => Action::None,
11923                                    }
11924                                }
11925                            }
11926                        }
11927                    }
11928                    // CREATE TABLE a LIKE b -> dialect-specific transformations
11929                    Expression::CreateTable(ref ct)
11930                        if ct.columns.is_empty()
11931                            && ct.constraints.iter().any(|c| {
11932                                matches!(c, crate::expressions::TableConstraint::Like { .. })
11933                            })
11934                            && matches!(
11935                                target,
11936                                DialectType::DuckDB | DialectType::SQLite | DialectType::Drill
11937                            ) =>
11938                    {
11939                        Action::CreateTableLikeToCtas
11940                    }
11941                    Expression::CreateTable(ref ct)
11942                        if ct.columns.is_empty()
11943                            && ct.constraints.iter().any(|c| {
11944                                matches!(c, crate::expressions::TableConstraint::Like { .. })
11945                            })
11946                            && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
11947                    {
11948                        Action::CreateTableLikeToSelectInto
11949                    }
11950                    Expression::CreateTable(ref ct)
11951                        if ct.columns.is_empty()
11952                            && ct.constraints.iter().any(|c| {
11953                                matches!(c, crate::expressions::TableConstraint::Like { .. })
11954                            })
11955                            && matches!(target, DialectType::ClickHouse) =>
11956                    {
11957                        Action::CreateTableLikeToAs
11958                    }
11959                    // CREATE TABLE: strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
11960                    Expression::CreateTable(ref ct)
11961                        if matches!(target, DialectType::DuckDB)
11962                            && matches!(
11963                                source,
11964                                DialectType::DuckDB
11965                                    | DialectType::Spark
11966                                    | DialectType::Databricks
11967                                    | DialectType::Hive
11968                            ) =>
11969                    {
11970                        let has_comment = ct.columns.iter().any(|c| {
11971                            c.comment.is_some()
11972                                || c.constraints.iter().any(|con| {
11973                                    matches!(con, crate::expressions::ColumnConstraint::Comment(_))
11974                                })
11975                        });
11976                        let has_props = !ct.properties.is_empty();
11977                        if has_comment || has_props {
11978                            Action::CreateTableStripComment
11979                        } else {
11980                            Action::None
11981                        }
11982                    }
11983                    // Array conversion: Expression::Array -> Expression::ArrayFunc for PostgreSQL
11984                    Expression::Array(_)
11985                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) =>
11986                    {
11987                        Action::ArrayConcatBracketConvert
11988                    }
11989                    // ArrayFunc (bracket notation) -> Function("ARRAY") for Redshift (from BigQuery source)
11990                    Expression::ArrayFunc(ref arr)
11991                        if arr.bracket_notation
11992                            && matches!(source, DialectType::BigQuery)
11993                            && matches!(target, DialectType::Redshift) =>
11994                    {
11995                        Action::ArrayConcatBracketConvert
11996                    }
11997                    // BIT_OR/BIT_AND/BIT_XOR: float/decimal arg cast for DuckDB, or rename for Snowflake
11998                    Expression::BitwiseOrAgg(ref f)
11999                    | Expression::BitwiseAndAgg(ref f)
12000                    | Expression::BitwiseXorAgg(ref f) => {
12001                        if matches!(target, DialectType::DuckDB) {
12002                            // Check if the arg is CAST(val AS FLOAT/DOUBLE/DECIMAL/REAL)
12003                            if let Expression::Cast(ref c) = f.this {
12004                                match &c.to {
12005                                    DataType::Float { .. }
12006                                    | DataType::Double { .. }
12007                                    | DataType::Decimal { .. } => Action::BitAggFloatCast,
12008                                    DataType::Custom { ref name }
12009                                        if name.eq_ignore_ascii_case("REAL") =>
12010                                    {
12011                                        Action::BitAggFloatCast
12012                                    }
12013                                    _ => Action::None,
12014                                }
12015                            } else {
12016                                Action::None
12017                            }
12018                        } else if matches!(target, DialectType::Snowflake) {
12019                            Action::BitAggSnowflakeRename
12020                        } else {
12021                            Action::None
12022                        }
12023                    }
12024                    // FILTER -> IFF for Snowflake (aggregate functions with FILTER clause)
12025                    Expression::Filter(ref _f) if matches!(target, DialectType::Snowflake) => {
12026                        Action::FilterToIff
12027                    }
12028                    // AggFunc.filter -> IFF wrapping for Snowflake (e.g., AVG(x) FILTER(WHERE cond))
12029                    Expression::Avg(ref f)
12030                    | Expression::Sum(ref f)
12031                    | Expression::Min(ref f)
12032                    | Expression::Max(ref f)
12033                    | Expression::CountIf(ref f)
12034                    | Expression::Stddev(ref f)
12035                    | Expression::StddevPop(ref f)
12036                    | Expression::StddevSamp(ref f)
12037                    | Expression::Variance(ref f)
12038                    | Expression::VarPop(ref f)
12039                    | Expression::VarSamp(ref f)
12040                    | Expression::Median(ref f)
12041                    | Expression::Mode(ref f)
12042                    | Expression::First(ref f)
12043                    | Expression::Last(ref f)
12044                    | Expression::ApproxDistinct(ref f)
12045                        if f.filter.is_some() && matches!(target, DialectType::Snowflake) =>
12046                    {
12047                        Action::AggFilterToIff
12048                    }
12049                    Expression::Count(ref c)
12050                        if c.filter.is_some() && matches!(target, DialectType::Snowflake) =>
12051                    {
12052                        Action::AggFilterToIff
12053                    }
12054                    // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN ... END) for dialects that don't support multi-arg DISTINCT
12055                    Expression::Count(ref c)
12056                        if c.distinct
12057                            && matches!(&c.this, Some(Expression::Tuple(_)))
12058                            && matches!(
12059                                target,
12060                                DialectType::Presto
12061                                    | DialectType::Trino
12062                                    | DialectType::DuckDB
12063                                    | DialectType::PostgreSQL
12064                            ) =>
12065                    {
12066                        Action::CountDistinctMultiArg
12067                    }
12068                    // JSON arrow -> GET_PATH/PARSE_JSON for Snowflake
12069                    Expression::JsonExtract(_) if matches!(target, DialectType::Snowflake) => {
12070                        Action::JsonToGetPath
12071                    }
12072                    // DuckDB struct/dict -> BigQuery STRUCT / Presto ROW
12073                    Expression::Struct(_)
12074                        if matches!(
12075                            target,
12076                            DialectType::BigQuery | DialectType::Presto | DialectType::Trino
12077                        ) && matches!(source, DialectType::DuckDB) =>
12078                    {
12079                        Action::StructToRow
12080                    }
12081                    // DuckDB curly-brace dict {'key': value} -> BigQuery STRUCT / Presto ROW
12082                    Expression::MapFunc(ref m)
12083                        if m.curly_brace_syntax
12084                            && matches!(
12085                                target,
12086                                DialectType::BigQuery | DialectType::Presto | DialectType::Trino
12087                            )
12088                            && matches!(source, DialectType::DuckDB) =>
12089                    {
12090                        Action::StructToRow
12091                    }
12092                    // APPROX_COUNT_DISTINCT -> APPROX_DISTINCT for Presto/Trino
12093                    Expression::ApproxCountDistinct(_)
12094                        if matches!(
12095                            target,
12096                            DialectType::Presto | DialectType::Trino | DialectType::Athena
12097                        ) =>
12098                    {
12099                        Action::ApproxCountDistinctToApproxDistinct
12100                    }
12101                    // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val) for Presto, ARRAY_CONTAINS(CAST(val AS VARIANT), arr) for Snowflake
12102                    Expression::ArrayContains(_)
12103                        if matches!(
12104                            target,
12105                            DialectType::Presto | DialectType::Trino | DialectType::Snowflake
12106                        ) && !(matches!(source, DialectType::Snowflake) && matches!(target, DialectType::Snowflake)) =>
12107                    {
12108                        Action::ArrayContainsConvert
12109                    }
12110                    // ARRAY_CONTAINS -> DuckDB NULL-aware CASE (from Snowflake source with check_null semantics)
12111                    Expression::ArrayContains(_)
12112                        if matches!(target, DialectType::DuckDB)
12113                            && matches!(source, DialectType::Snowflake) =>
12114                    {
12115                        Action::ArrayContainsDuckDBConvert
12116                    }
12117                    // ARRAY_EXCEPT -> target-specific conversion
12118                    Expression::ArrayExcept(_)
12119                        if matches!(
12120                            target,
12121                            DialectType::DuckDB | DialectType::Snowflake | DialectType::Presto | DialectType::Trino | DialectType::Athena
12122                        ) =>
12123                    {
12124                        Action::ArrayExceptConvert
12125                    }
12126                    // ARRAY_POSITION -> swap args for Snowflake target (only when source is not Snowflake)
12127                    Expression::ArrayPosition(_)
12128                        if matches!(target, DialectType::Snowflake)
12129                            && !matches!(source, DialectType::Snowflake) =>
12130                    {
12131                        Action::ArrayPositionSnowflakeSwap
12132                    }
12133                    // ARRAY_POSITION(val, arr) -> ARRAY_POSITION(arr, val) - 1 for DuckDB from Snowflake source
12134                    Expression::ArrayPosition(_)
12135                        if matches!(target, DialectType::DuckDB)
12136                            && matches!(source, DialectType::Snowflake) =>
12137                    {
12138                        Action::SnowflakeArrayPositionToDuckDB
12139                    }
12140                    // ARRAY_DISTINCT -> arrayDistinct for ClickHouse
12141                    Expression::ArrayDistinct(_)
12142                        if matches!(target, DialectType::ClickHouse) =>
12143                    {
12144                        Action::ArrayDistinctClickHouse
12145                    }
12146                    // ARRAY_DISTINCT -> DuckDB LIST_DISTINCT with NULL-aware CASE
12147                    Expression::ArrayDistinct(_)
12148                        if matches!(target, DialectType::DuckDB)
12149                            && matches!(source, DialectType::Snowflake) =>
12150                    {
12151                        Action::ArrayDistinctConvert
12152                    }
12153                    // StrPosition with position -> complex expansion for Presto/DuckDB
12154                    // STRPOS doesn't support a position arg in these dialects
12155                    Expression::StrPosition(ref sp)
12156                        if sp.position.is_some()
12157                            && matches!(
12158                                target,
12159                                DialectType::Presto
12160                                    | DialectType::Trino
12161                                    | DialectType::Athena
12162                                    | DialectType::DuckDB
12163                            ) =>
12164                    {
12165                        Action::StrPositionExpand
12166                    }
12167                    // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
12168                    Expression::First(ref f)
12169                        if f.ignore_nulls == Some(true)
12170                            && matches!(target, DialectType::DuckDB) =>
12171                    {
12172                        Action::FirstToAnyValue
12173                    }
12174                    // BEGIN -> START TRANSACTION for Presto/Trino
12175                    Expression::Command(ref cmd)
12176                        if cmd.this.eq_ignore_ascii_case("BEGIN")
12177                            && matches!(
12178                                target,
12179                                DialectType::Presto | DialectType::Trino | DialectType::Athena
12180                            ) =>
12181                    {
12182                        // Handled inline below
12183                        Action::None // We'll handle it directly
12184                    }
12185                    // Note: PostgreSQL ^ is now parsed as Power directly (not BitwiseXor).
12186                    // PostgreSQL # is parsed as BitwiseXor (which is correct).
12187                    Expression::Cbrt(_)
12188                        if Self::is_postgres_family_source(source)
12189                            && matches!(target, DialectType::TSQL | DialectType::Fabric) =>
12190                    {
12191                        Action::CbrtToPower
12192                    }
12193                    // a || b (Concat operator) -> CONCAT function for Presto/Trino
12194                    Expression::Concat(ref _op)
12195                        if matches!(source, DialectType::PostgreSQL | DialectType::Redshift)
12196                            && matches!(target, DialectType::Presto | DialectType::Trino) =>
12197                    {
12198                        Action::PipeConcatToConcat
12199                    }
12200                    _ => Action::None,
12201                }
12202            };
12203
12204            match action {
12205                Action::None => {
12206                    // Handle inline transforms that don't need a dedicated action
12207                    if matches!(target, DialectType::TSQL | DialectType::Fabric) {
12208                        if let Some(rewritten) = Self::rewrite_tsql_interval_arithmetic(&e, source)
12209                        {
12210                            return Ok(rewritten);
12211                        }
12212                    }
12213
12214                    // BETWEEN SYMMETRIC/ASYMMETRIC expansion for non-PostgreSQL/Dremio targets
12215                    if let Expression::Between(ref b) = e {
12216                        if let Some(sym) = b.symmetric {
12217                            let keeps_symmetric =
12218                                matches!(target, DialectType::PostgreSQL | DialectType::Dremio);
12219                            if !keeps_symmetric {
12220                                if sym {
12221                                    // SYMMETRIC: expand to (x BETWEEN a AND b OR x BETWEEN b AND a)
12222                                    let b = if let Expression::Between(b) = e {
12223                                        *b
12224                                    } else {
12225                                        unreachable!()
12226                                    };
12227                                    let between1 = Expression::Between(Box::new(
12228                                        crate::expressions::Between {
12229                                            this: b.this.clone(),
12230                                            low: b.low.clone(),
12231                                            high: b.high.clone(),
12232                                            not: b.not,
12233                                            symmetric: None,
12234                                        },
12235                                    ));
12236                                    let between2 = Expression::Between(Box::new(
12237                                        crate::expressions::Between {
12238                                            this: b.this,
12239                                            low: b.high,
12240                                            high: b.low,
12241                                            not: b.not,
12242                                            symmetric: None,
12243                                        },
12244                                    ));
12245                                    return Ok(Expression::Paren(Box::new(
12246                                        crate::expressions::Paren {
12247                                            this: Expression::Or(Box::new(
12248                                                crate::expressions::BinaryOp::new(
12249                                                    between1, between2,
12250                                                ),
12251                                            )),
12252                                            trailing_comments: vec![],
12253                                        },
12254                                    )));
12255                                } else {
12256                                    // ASYMMETRIC: strip qualifier, keep as regular BETWEEN
12257                                    let b = if let Expression::Between(b) = e {
12258                                        *b
12259                                    } else {
12260                                        unreachable!()
12261                                    };
12262                                    return Ok(Expression::Between(Box::new(
12263                                        crate::expressions::Between {
12264                                            this: b.this,
12265                                            low: b.low,
12266                                            high: b.high,
12267                                            not: b.not,
12268                                            symmetric: None,
12269                                        },
12270                                    )));
12271                                }
12272                            }
12273                        }
12274                    }
12275
12276                    // ILIKE -> LOWER(x) LIKE LOWER(y) for StarRocks/Doris
12277                    if let Expression::ILike(ref _like) = e {
12278                        if matches!(target, DialectType::StarRocks | DialectType::Doris) {
12279                            let like = if let Expression::ILike(l) = e {
12280                                *l
12281                            } else {
12282                                unreachable!()
12283                            };
12284                            let lower_left = Expression::Function(Box::new(Function::new(
12285                                "LOWER".to_string(),
12286                                vec![like.left],
12287                            )));
12288                            let lower_right = Expression::Function(Box::new(Function::new(
12289                                "LOWER".to_string(),
12290                                vec![like.right],
12291                            )));
12292                            return Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
12293                                left: lower_left,
12294                                right: lower_right,
12295                                escape: like.escape,
12296                                quantifier: like.quantifier,
12297                                inferred_type: None,
12298                            })));
12299                        }
12300                    }
12301
12302                    // Oracle DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL, RAND() for others
12303                    if let Expression::MethodCall(ref mc) = e {
12304                        if matches!(source, DialectType::Oracle)
12305                            && mc.method.name.eq_ignore_ascii_case("VALUE")
12306                            && mc.args.is_empty()
12307                        {
12308                            let is_dbms_random = match &mc.this {
12309                                Expression::Identifier(id) => {
12310                                    id.name.eq_ignore_ascii_case("DBMS_RANDOM")
12311                                }
12312                                Expression::Column(col) => {
12313                                    col.table.is_none()
12314                                        && col.name.name.eq_ignore_ascii_case("DBMS_RANDOM")
12315                                }
12316                                _ => false,
12317                            };
12318                            if is_dbms_random {
12319                                let func_name = match target {
12320                                    DialectType::PostgreSQL
12321                                    | DialectType::Redshift
12322                                    | DialectType::DuckDB
12323                                    | DialectType::SQLite => "RANDOM",
12324                                    DialectType::Oracle => "DBMS_RANDOM.VALUE",
12325                                    _ => "RAND",
12326                                };
12327                                return Ok(Expression::Function(Box::new(Function::new(
12328                                    func_name.to_string(),
12329                                    vec![],
12330                                ))));
12331                            }
12332                        }
12333                    }
12334                    // TRIM without explicit position -> add BOTH for ClickHouse
12335                    if let Expression::Trim(ref trim) = e {
12336                        if matches!(target, DialectType::ClickHouse)
12337                            && trim.sql_standard_syntax
12338                            && trim.characters.is_some()
12339                            && !trim.position_explicit
12340                        {
12341                            let mut new_trim = (**trim).clone();
12342                            new_trim.position_explicit = true;
12343                            return Ok(Expression::Trim(Box::new(new_trim)));
12344                        }
12345                    }
12346                    // BEGIN -> START TRANSACTION for Presto/Trino
12347                    if let Expression::Transaction(ref txn) = e {
12348                        if matches!(
12349                            target,
12350                            DialectType::Presto | DialectType::Trino | DialectType::Athena
12351                        ) {
12352                            // Convert BEGIN to START TRANSACTION by setting mark to "START"
12353                            let mut txn = txn.clone();
12354                            txn.mark = Some(Box::new(Expression::Identifier(Identifier::new(
12355                                "START".to_string(),
12356                            ))));
12357                            return Ok(Expression::Transaction(Box::new(*txn)));
12358                        }
12359                    }
12360                    // IS TRUE/FALSE -> simplified forms for Presto/Trino
12361                    if matches!(
12362                        target,
12363                        DialectType::Presto | DialectType::Trino | DialectType::Athena
12364                    ) {
12365                        match &e {
12366                            Expression::IsTrue(itf) if !itf.not => {
12367                                // x IS TRUE -> x
12368                                return Ok(itf.this.clone());
12369                            }
12370                            Expression::IsTrue(itf) if itf.not => {
12371                                // x IS NOT TRUE -> NOT x
12372                                return Ok(Expression::Not(Box::new(
12373                                    crate::expressions::UnaryOp {
12374                                        this: itf.this.clone(),
12375                                        inferred_type: None,
12376                                    },
12377                                )));
12378                            }
12379                            Expression::IsFalse(itf) if !itf.not => {
12380                                // x IS FALSE -> NOT x
12381                                return Ok(Expression::Not(Box::new(
12382                                    crate::expressions::UnaryOp {
12383                                        this: itf.this.clone(),
12384                                        inferred_type: None,
12385                                    },
12386                                )));
12387                            }
12388                            Expression::IsFalse(itf) if itf.not => {
12389                                // x IS NOT FALSE -> NOT NOT x
12390                                let not_x =
12391                                    Expression::Not(Box::new(crate::expressions::UnaryOp {
12392                                        this: itf.this.clone(),
12393                                        inferred_type: None,
12394                                    }));
12395                                return Ok(Expression::Not(Box::new(
12396                                    crate::expressions::UnaryOp {
12397                                        this: not_x,
12398                                        inferred_type: None,
12399                                    },
12400                                )));
12401                            }
12402                            _ => {}
12403                        }
12404                    }
12405                    // x IS NOT FALSE -> NOT x IS FALSE for Redshift
12406                    if matches!(target, DialectType::Redshift) {
12407                        if let Expression::IsFalse(ref itf) = e {
12408                            if itf.not {
12409                                return Ok(Expression::Not(Box::new(
12410                                    crate::expressions::UnaryOp {
12411                                        this: Expression::IsFalse(Box::new(
12412                                            crate::expressions::IsTrueFalse {
12413                                                this: itf.this.clone(),
12414                                                not: false,
12415                                            },
12416                                        )),
12417                                        inferred_type: None,
12418                                    },
12419                                )));
12420                            }
12421                        }
12422                    }
12423                    // REGEXP_REPLACE: add 'g' flag when source defaults to global replacement
12424                    // Snowflake default is global, PostgreSQL/DuckDB default is first-match-only
12425                    if let Expression::Function(ref f) = e {
12426                        if f.name.eq_ignore_ascii_case("REGEXP_REPLACE")
12427                            && matches!(source, DialectType::Snowflake)
12428                            && matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
12429                        {
12430                            if f.args.len() == 3 {
12431                                let mut args = f.args.clone();
12432                                args.push(Expression::string("g"));
12433                                return Ok(Expression::Function(Box::new(Function::new(
12434                                    "REGEXP_REPLACE".to_string(),
12435                                    args,
12436                                ))));
12437                            } else if f.args.len() == 4 {
12438                                // 4th arg might be position, add 'g' as 5th
12439                                let mut args = f.args.clone();
12440                                args.push(Expression::string("g"));
12441                                return Ok(Expression::Function(Box::new(Function::new(
12442                                    "REGEXP_REPLACE".to_string(),
12443                                    args,
12444                                ))));
12445                            }
12446                        }
12447                    }
12448                    Ok(e)
12449                }
12450
12451                Action::GreatestLeastNull => {
12452                    let f = if let Expression::Function(f) = e {
12453                        *f
12454                    } else {
12455                        unreachable!("action only triggered for Function expressions")
12456                    };
12457                    let mut null_checks: Vec<Expression> = f
12458                        .args
12459                        .iter()
12460                        .map(|a| {
12461                            Expression::IsNull(Box::new(IsNull {
12462                                this: a.clone(),
12463                                not: false,
12464                                postfix_form: false,
12465                            }))
12466                        })
12467                        .collect();
12468                    let condition = if null_checks.len() == 1 {
12469                        null_checks.remove(0)
12470                    } else {
12471                        let first = null_checks.remove(0);
12472                        null_checks.into_iter().fold(first, |acc, check| {
12473                            Expression::Or(Box::new(BinaryOp::new(acc, check)))
12474                        })
12475                    };
12476                    Ok(Expression::Case(Box::new(Case {
12477                        operand: None,
12478                        whens: vec![(condition, Expression::Null(Null))],
12479                        else_: Some(Expression::Function(Box::new(Function::new(
12480                            f.name, f.args,
12481                        )))),
12482                        comments: Vec::new(),
12483                        inferred_type: None,
12484                    })))
12485                }
12486
12487                Action::ArrayGenerateRange => {
12488                    let f = if let Expression::Function(f) = e {
12489                        *f
12490                    } else {
12491                        unreachable!("action only triggered for Function expressions")
12492                    };
12493                    let start = f.args[0].clone();
12494                    let end = f.args[1].clone();
12495                    let step = f.args.get(2).cloned();
12496
12497                    // Helper: compute end - 1 for converting exclusive→inclusive end.
12498                    // When end is a literal number, simplify to a computed literal.
12499                    fn exclusive_to_inclusive_end(end: &Expression) -> Expression {
12500                        // Try to simplify literal numbers
12501                        match end {
12502                            Expression::Literal(lit)
12503                                if matches!(lit.as_ref(), Literal::Number(_)) =>
12504                            {
12505                                let Literal::Number(n) = lit.as_ref() else {
12506                                    unreachable!()
12507                                };
12508                                if let Ok(val) = n.parse::<i64>() {
12509                                    return Expression::number(val - 1);
12510                                }
12511                            }
12512                            Expression::Neg(u) => {
12513                                if let Expression::Literal(lit) = &u.this {
12514                                    if let Literal::Number(n) = lit.as_ref() {
12515                                        if let Ok(val) = n.parse::<i64>() {
12516                                            return Expression::number(-val - 1);
12517                                        }
12518                                    }
12519                                }
12520                            }
12521                            _ => {}
12522                        }
12523                        // Non-literal: produce end - 1 expression
12524                        Expression::Sub(Box::new(BinaryOp::new(end.clone(), Expression::number(1))))
12525                    }
12526
12527                    match target {
12528                        // Snowflake ARRAY_GENERATE_RANGE and DuckDB RANGE both use exclusive end,
12529                        // so no adjustment needed — just rename the function.
12530                        DialectType::Snowflake => {
12531                            let mut args = vec![start, end];
12532                            if let Some(s) = step {
12533                                args.push(s);
12534                            }
12535                            Ok(Expression::Function(Box::new(Function::new(
12536                                "ARRAY_GENERATE_RANGE".to_string(),
12537                                args,
12538                            ))))
12539                        }
12540                        DialectType::DuckDB => {
12541                            let mut args = vec![start, end];
12542                            if let Some(s) = step {
12543                                args.push(s);
12544                            }
12545                            Ok(Expression::Function(Box::new(Function::new(
12546                                "RANGE".to_string(),
12547                                args,
12548                            ))))
12549                        }
12550                        // These dialects use inclusive end, so convert exclusive→inclusive.
12551                        // Presto/Trino: simplify literal numbers (3 → 2).
12552                        DialectType::Presto | DialectType::Trino => {
12553                            let end_inclusive = exclusive_to_inclusive_end(&end);
12554                            let mut args = vec![start, end_inclusive];
12555                            if let Some(s) = step {
12556                                args.push(s);
12557                            }
12558                            Ok(Expression::Function(Box::new(Function::new(
12559                                "SEQUENCE".to_string(),
12560                                args,
12561                            ))))
12562                        }
12563                        // PostgreSQL, Redshift, BigQuery: keep as end - 1 expression form.
12564                        DialectType::PostgreSQL | DialectType::Redshift => {
12565                            let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
12566                                end.clone(),
12567                                Expression::number(1),
12568                            )));
12569                            let mut args = vec![start, end_minus_1];
12570                            if let Some(s) = step {
12571                                args.push(s);
12572                            }
12573                            Ok(Expression::Function(Box::new(Function::new(
12574                                "GENERATE_SERIES".to_string(),
12575                                args,
12576                            ))))
12577                        }
12578                        DialectType::BigQuery => {
12579                            let end_minus_1 = Expression::Sub(Box::new(BinaryOp::new(
12580                                end.clone(),
12581                                Expression::number(1),
12582                            )));
12583                            let mut args = vec![start, end_minus_1];
12584                            if let Some(s) = step {
12585                                args.push(s);
12586                            }
12587                            Ok(Expression::Function(Box::new(Function::new(
12588                                "GENERATE_ARRAY".to_string(),
12589                                args,
12590                            ))))
12591                        }
12592                        _ => Ok(Expression::Function(Box::new(Function::new(
12593                            f.name, f.args,
12594                        )))),
12595                    }
12596                }
12597
12598                Action::Div0TypedDivision => {
12599                    let if_func = if let Expression::IfFunc(f) = e {
12600                        *f
12601                    } else {
12602                        unreachable!("action only triggered for IfFunc expressions")
12603                    };
12604                    if let Some(Expression::Div(div)) = if_func.false_value {
12605                        let cast_type = if matches!(target, DialectType::SQLite) {
12606                            DataType::Float {
12607                                precision: None,
12608                                scale: None,
12609                                real_spelling: true,
12610                            }
12611                        } else {
12612                            DataType::Double {
12613                                precision: None,
12614                                scale: None,
12615                            }
12616                        };
12617                        let casted_left = Expression::Cast(Box::new(Cast {
12618                            this: div.left,
12619                            to: cast_type,
12620                            trailing_comments: vec![],
12621                            double_colon_syntax: false,
12622                            format: None,
12623                            default: None,
12624                            inferred_type: None,
12625                        }));
12626                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
12627                            condition: if_func.condition,
12628                            true_value: if_func.true_value,
12629                            false_value: Some(Expression::Div(Box::new(BinaryOp::new(
12630                                casted_left,
12631                                div.right,
12632                            )))),
12633                            original_name: if_func.original_name,
12634                            inferred_type: None,
12635                        })))
12636                    } else {
12637                        // Not actually a Div, reconstruct
12638                        Ok(Expression::IfFunc(Box::new(if_func)))
12639                    }
12640                }
12641
12642                Action::ArrayAggCollectList => {
12643                    let agg = if let Expression::ArrayAgg(a) = e {
12644                        *a
12645                    } else {
12646                        unreachable!("action only triggered for ArrayAgg expressions")
12647                    };
12648                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
12649                        name: Some("COLLECT_LIST".to_string()),
12650                        ..agg
12651                    })))
12652                }
12653
12654                Action::ArrayAggToGroupConcat => {
12655                    let agg = if let Expression::ArrayAgg(a) = e {
12656                        *a
12657                    } else {
12658                        unreachable!("action only triggered for ArrayAgg expressions")
12659                    };
12660                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
12661                        name: Some("GROUP_CONCAT".to_string()),
12662                        ..agg
12663                    })))
12664                }
12665
12666                Action::ArrayAggWithinGroupFilter => {
12667                    let wg = if let Expression::WithinGroup(w) = e {
12668                        *w
12669                    } else {
12670                        unreachable!("action only triggered for WithinGroup expressions")
12671                    };
12672                    if let Expression::ArrayAgg(inner_agg) = wg.this {
12673                        let col = inner_agg.this.clone();
12674                        let filter = Expression::IsNull(Box::new(IsNull {
12675                            this: col,
12676                            not: true,
12677                            postfix_form: false,
12678                        }));
12679                        // For DuckDB, add explicit NULLS FIRST for DESC ordering
12680                        let order_by = if matches!(target, DialectType::DuckDB) {
12681                            wg.order_by
12682                                .into_iter()
12683                                .map(|mut o| {
12684                                    if o.desc && o.nulls_first.is_none() {
12685                                        o.nulls_first = Some(true);
12686                                    }
12687                                    o
12688                                })
12689                                .collect()
12690                        } else {
12691                            wg.order_by
12692                        };
12693                        Ok(Expression::ArrayAgg(Box::new(AggFunc {
12694                            this: inner_agg.this,
12695                            distinct: inner_agg.distinct,
12696                            filter: Some(filter),
12697                            order_by,
12698                            name: inner_agg.name,
12699                            ignore_nulls: inner_agg.ignore_nulls,
12700                            having_max: inner_agg.having_max,
12701                            limit: inner_agg.limit,
12702                            inferred_type: None,
12703                        })))
12704                    } else {
12705                        Ok(Expression::WithinGroup(Box::new(wg)))
12706                    }
12707                }
12708
12709                Action::ArrayAggFilter => {
12710                    let agg = if let Expression::ArrayAgg(a) = e {
12711                        *a
12712                    } else {
12713                        unreachable!("action only triggered for ArrayAgg expressions")
12714                    };
12715                    let col = agg.this.clone();
12716                    let filter = Expression::IsNull(Box::new(IsNull {
12717                        this: col,
12718                        not: true,
12719                        postfix_form: false,
12720                    }));
12721                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
12722                        filter: Some(filter),
12723                        ..agg
12724                    })))
12725                }
12726
12727                Action::ArrayAggNullFilter => {
12728                    // ARRAY_AGG(x) FILTER(WHERE cond) -> ARRAY_AGG(x) FILTER(WHERE cond AND NOT x IS NULL)
12729                    // For source dialects that exclude NULLs (Spark/Hive) targeting DuckDB which includes them
12730                    let agg = if let Expression::ArrayAgg(a) = e {
12731                        *a
12732                    } else {
12733                        unreachable!("action only triggered for ArrayAgg expressions")
12734                    };
12735                    let col = agg.this.clone();
12736                    let not_null = Expression::IsNull(Box::new(IsNull {
12737                        this: col,
12738                        not: true,
12739                        postfix_form: true, // Use "NOT x IS NULL" form (prefix NOT)
12740                    }));
12741                    let new_filter = if let Some(existing_filter) = agg.filter {
12742                        // AND the NOT IS NULL with existing filter
12743                        Expression::And(Box::new(crate::expressions::BinaryOp::new(
12744                            existing_filter,
12745                            not_null,
12746                        )))
12747                    } else {
12748                        not_null
12749                    };
12750                    Ok(Expression::ArrayAgg(Box::new(AggFunc {
12751                        filter: Some(new_filter),
12752                        ..agg
12753                    })))
12754                }
12755
12756                Action::BigQueryArraySelectAsStructToSnowflake => {
12757                    // ARRAY(SELECT AS STRUCT x1 AS x1, x2 AS x2 FROM t)
12758                    // -> (SELECT ARRAY_AGG(OBJECT_CONSTRUCT('x1', x1, 'x2', x2)) FROM t)
12759                    if let Expression::Function(mut f) = e {
12760                        let is_match = f.args.len() == 1
12761                            && matches!(&f.args[0], Expression::Select(s) if s.kind.as_deref() == Some("STRUCT"));
12762                        if is_match {
12763                            let inner_select = match f.args.remove(0) {
12764                                Expression::Select(s) => *s,
12765                                _ => unreachable!(
12766                                    "argument already verified to be a Select expression"
12767                                ),
12768                            };
12769                            // Build OBJECT_CONSTRUCT args from SELECT expressions
12770                            let mut oc_args = Vec::new();
12771                            for expr in &inner_select.expressions {
12772                                match expr {
12773                                    Expression::Alias(a) => {
12774                                        let key = Expression::Literal(Box::new(Literal::String(
12775                                            a.alias.name.clone(),
12776                                        )));
12777                                        let value = a.this.clone();
12778                                        oc_args.push(key);
12779                                        oc_args.push(value);
12780                                    }
12781                                    Expression::Column(c) => {
12782                                        let key = Expression::Literal(Box::new(Literal::String(
12783                                            c.name.name.clone(),
12784                                        )));
12785                                        oc_args.push(key);
12786                                        oc_args.push(expr.clone());
12787                                    }
12788                                    _ => {
12789                                        oc_args.push(expr.clone());
12790                                    }
12791                                }
12792                            }
12793                            let object_construct = Expression::Function(Box::new(Function::new(
12794                                "OBJECT_CONSTRUCT".to_string(),
12795                                oc_args,
12796                            )));
12797                            let array_agg = Expression::Function(Box::new(Function::new(
12798                                "ARRAY_AGG".to_string(),
12799                                vec![object_construct],
12800                            )));
12801                            let mut new_select = crate::expressions::Select::new();
12802                            new_select.expressions = vec![array_agg];
12803                            new_select.from = inner_select.from.clone();
12804                            new_select.where_clause = inner_select.where_clause.clone();
12805                            new_select.group_by = inner_select.group_by.clone();
12806                            new_select.having = inner_select.having.clone();
12807                            new_select.joins = inner_select.joins.clone();
12808                            Ok(Expression::Subquery(Box::new(
12809                                crate::expressions::Subquery {
12810                                    this: Expression::Select(Box::new(new_select)),
12811                                    alias: None,
12812                                    column_aliases: Vec::new(),
12813                                    alias_explicit_as: false,
12814                                    alias_keyword: None,
12815                                    order_by: None,
12816                                    limit: None,
12817                                    offset: None,
12818                                    distribute_by: None,
12819                                    sort_by: None,
12820                                    cluster_by: None,
12821                                    lateral: false,
12822                                    modifiers_inside: false,
12823                                    trailing_comments: Vec::new(),
12824                                    inferred_type: None,
12825                                },
12826                            )))
12827                        } else {
12828                            Ok(Expression::Function(f))
12829                        }
12830                    } else {
12831                        Ok(e)
12832                    }
12833                }
12834
12835                Action::BigQueryPercentileContToDuckDB => {
12836                    // PERCENTILE_CONT(x, frac [RESPECT NULLS]) -> QUANTILE_CONT(x, frac) for DuckDB
12837                    if let Expression::AggregateFunction(mut af) = e {
12838                        af.name = "QUANTILE_CONT".to_string();
12839                        af.ignore_nulls = None; // Strip RESPECT/IGNORE NULLS
12840                                                // Keep only first 2 args
12841                        if af.args.len() > 2 {
12842                            af.args.truncate(2);
12843                        }
12844                        Ok(Expression::AggregateFunction(af))
12845                    } else {
12846                        Ok(e)
12847                    }
12848                }
12849
12850                Action::ArrayAggIgnoreNullsDuckDB => {
12851                    // ARRAY_AGG(x IGNORE NULLS ORDER BY a, b DESC) -> ARRAY_AGG(x ORDER BY a NULLS FIRST, b DESC)
12852                    // Strip IGNORE NULLS, add NULLS FIRST to first ORDER BY column
12853                    let mut agg = if let Expression::ArrayAgg(a) = e {
12854                        *a
12855                    } else {
12856                        unreachable!("action only triggered for ArrayAgg expressions")
12857                    };
12858                    agg.ignore_nulls = None; // Strip IGNORE NULLS
12859                    if !agg.order_by.is_empty() {
12860                        agg.order_by[0].nulls_first = Some(true);
12861                    }
12862                    Ok(Expression::ArrayAgg(Box::new(agg)))
12863                }
12864
12865                Action::CountDistinctMultiArg => {
12866                    // COUNT(DISTINCT a, b) -> COUNT(DISTINCT CASE WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END)
12867                    if let Expression::Count(c) = e {
12868                        if let Some(Expression::Tuple(t)) = c.this {
12869                            let args = t.expressions;
12870                            // Build CASE expression:
12871                            // WHEN a IS NULL THEN NULL WHEN b IS NULL THEN NULL ELSE (a, b) END
12872                            let mut whens = Vec::new();
12873                            for arg in &args {
12874                                whens.push((
12875                                    Expression::IsNull(Box::new(IsNull {
12876                                        this: arg.clone(),
12877                                        not: false,
12878                                        postfix_form: false,
12879                                    })),
12880                                    Expression::Null(crate::expressions::Null),
12881                                ));
12882                            }
12883                            // Build the tuple for ELSE
12884                            let tuple_expr =
12885                                Expression::Tuple(Box::new(crate::expressions::Tuple {
12886                                    expressions: args,
12887                                }));
12888                            let case_expr = Expression::Case(Box::new(crate::expressions::Case {
12889                                operand: None,
12890                                whens,
12891                                else_: Some(tuple_expr),
12892                                comments: Vec::new(),
12893                                inferred_type: None,
12894                            }));
12895                            Ok(Expression::Count(Box::new(crate::expressions::CountFunc {
12896                                this: Some(case_expr),
12897                                star: false,
12898                                distinct: true,
12899                                filter: c.filter,
12900                                ignore_nulls: c.ignore_nulls,
12901                                original_name: c.original_name,
12902                                inferred_type: None,
12903                            })))
12904                        } else {
12905                            Ok(Expression::Count(c))
12906                        }
12907                    } else {
12908                        Ok(e)
12909                    }
12910                }
12911
12912                Action::CastTimestampToDatetime => {
12913                    let c = if let Expression::Cast(c) = e {
12914                        *c
12915                    } else {
12916                        unreachable!("action only triggered for Cast expressions")
12917                    };
12918                    Ok(Expression::Cast(Box::new(Cast {
12919                        to: DataType::Custom {
12920                            name: "DATETIME".to_string(),
12921                        },
12922                        ..c
12923                    })))
12924                }
12925
12926                Action::CastTimestampStripTz => {
12927                    // CAST(x AS TIMESTAMP(n) WITH TIME ZONE) -> CAST(x AS TIMESTAMP) for Hive/Spark/BigQuery
12928                    let c = if let Expression::Cast(c) = e {
12929                        *c
12930                    } else {
12931                        unreachable!("action only triggered for Cast expressions")
12932                    };
12933                    Ok(Expression::Cast(Box::new(Cast {
12934                        to: DataType::Timestamp {
12935                            precision: None,
12936                            timezone: false,
12937                        },
12938                        ..c
12939                    })))
12940                }
12941
12942                Action::CastTimestamptzToFunc => {
12943                    // CAST(x AS TIMESTAMPTZ) -> TIMESTAMP(x) function for MySQL/StarRocks
12944                    let c = if let Expression::Cast(c) = e {
12945                        *c
12946                    } else {
12947                        unreachable!("action only triggered for Cast expressions")
12948                    };
12949                    Ok(Expression::Function(Box::new(Function::new(
12950                        "TIMESTAMP".to_string(),
12951                        vec![c.this],
12952                    ))))
12953                }
12954
12955                Action::ToDateToCast => {
12956                    // Convert TO_DATE(x) -> CAST(x AS DATE) for DuckDB
12957                    if let Expression::Function(f) = e {
12958                        let arg = f.args.into_iter().next().unwrap();
12959                        Ok(Expression::Cast(Box::new(Cast {
12960                            this: arg,
12961                            to: DataType::Date,
12962                            double_colon_syntax: false,
12963                            trailing_comments: vec![],
12964                            format: None,
12965                            default: None,
12966                            inferred_type: None,
12967                        })))
12968                    } else {
12969                        Ok(e)
12970                    }
12971                }
12972                Action::DateTruncWrapCast => {
12973                    // Handle both Expression::DateTrunc/TimestampTrunc and
12974                    // Expression::Function("DATE_TRUNC", [unit, expr])
12975                    match e {
12976                        Expression::DateTrunc(d) | Expression::TimestampTrunc(d) => {
12977                            let input_type = match &d.this {
12978                                Expression::Cast(c) => Some(c.to.clone()),
12979                                _ => None,
12980                            };
12981                            if let Some(cast_type) = input_type {
12982                                let is_time = matches!(cast_type, DataType::Time { .. });
12983                                if is_time {
12984                                    let date_expr = Expression::Cast(Box::new(Cast {
12985                                        this: Expression::Literal(Box::new(
12986                                            crate::expressions::Literal::String(
12987                                                "1970-01-01".to_string(),
12988                                            ),
12989                                        )),
12990                                        to: DataType::Date,
12991                                        double_colon_syntax: false,
12992                                        trailing_comments: vec![],
12993                                        format: None,
12994                                        default: None,
12995                                        inferred_type: None,
12996                                    }));
12997                                    let add_expr =
12998                                        Expression::Add(Box::new(BinaryOp::new(date_expr, d.this)));
12999                                    let inner = Expression::DateTrunc(Box::new(DateTruncFunc {
13000                                        this: add_expr,
13001                                        unit: d.unit,
13002                                    }));
13003                                    Ok(Expression::Cast(Box::new(Cast {
13004                                        this: inner,
13005                                        to: cast_type,
13006                                        double_colon_syntax: false,
13007                                        trailing_comments: vec![],
13008                                        format: None,
13009                                        default: None,
13010                                        inferred_type: None,
13011                                    })))
13012                                } else {
13013                                    let inner = Expression::DateTrunc(Box::new(*d));
13014                                    Ok(Expression::Cast(Box::new(Cast {
13015                                        this: inner,
13016                                        to: cast_type,
13017                                        double_colon_syntax: false,
13018                                        trailing_comments: vec![],
13019                                        format: None,
13020                                        default: None,
13021                                        inferred_type: None,
13022                                    })))
13023                                }
13024                            } else {
13025                                Ok(Expression::DateTrunc(d))
13026                            }
13027                        }
13028                        Expression::Function(f) if f.args.len() == 2 => {
13029                            // Function-based DATE_TRUNC(unit, expr)
13030                            let input_type = match &f.args[1] {
13031                                Expression::Cast(c) => Some(c.to.clone()),
13032                                _ => None,
13033                            };
13034                            if let Some(cast_type) = input_type {
13035                                let is_time = matches!(cast_type, DataType::Time { .. });
13036                                if is_time {
13037                                    let date_expr = Expression::Cast(Box::new(Cast {
13038                                        this: Expression::Literal(Box::new(
13039                                            crate::expressions::Literal::String(
13040                                                "1970-01-01".to_string(),
13041                                            ),
13042                                        )),
13043                                        to: DataType::Date,
13044                                        double_colon_syntax: false,
13045                                        trailing_comments: vec![],
13046                                        format: None,
13047                                        default: None,
13048                                        inferred_type: None,
13049                                    }));
13050                                    let mut args = f.args;
13051                                    let unit_arg = args.remove(0);
13052                                    let time_expr = args.remove(0);
13053                                    let add_expr = Expression::Add(Box::new(BinaryOp::new(
13054                                        date_expr, time_expr,
13055                                    )));
13056                                    let inner = Expression::Function(Box::new(Function::new(
13057                                        "DATE_TRUNC".to_string(),
13058                                        vec![unit_arg, add_expr],
13059                                    )));
13060                                    Ok(Expression::Cast(Box::new(Cast {
13061                                        this: inner,
13062                                        to: cast_type,
13063                                        double_colon_syntax: false,
13064                                        trailing_comments: vec![],
13065                                        format: None,
13066                                        default: None,
13067                                        inferred_type: None,
13068                                    })))
13069                                } else {
13070                                    // Wrap the function in CAST
13071                                    Ok(Expression::Cast(Box::new(Cast {
13072                                        this: Expression::Function(f),
13073                                        to: cast_type,
13074                                        double_colon_syntax: false,
13075                                        trailing_comments: vec![],
13076                                        format: None,
13077                                        default: None,
13078                                        inferred_type: None,
13079                                    })))
13080                                }
13081                            } else {
13082                                Ok(Expression::Function(f))
13083                            }
13084                        }
13085                        other => Ok(other),
13086                    }
13087                }
13088
13089                Action::RegexpReplaceSnowflakeToDuckDB => {
13090                    // Snowflake REGEXP_REPLACE(s, p, r, position) -> REGEXP_REPLACE(s, p, r, 'g')
13091                    if let Expression::Function(f) = e {
13092                        let mut args = f.args;
13093                        let subject = args.remove(0);
13094                        let pattern = args.remove(0);
13095                        let replacement = args.remove(0);
13096                        Ok(Expression::Function(Box::new(Function::new(
13097                            "REGEXP_REPLACE".to_string(),
13098                            vec![
13099                                subject,
13100                                pattern,
13101                                replacement,
13102                                Expression::Literal(Box::new(crate::expressions::Literal::String(
13103                                    "g".to_string(),
13104                                ))),
13105                            ],
13106                        ))))
13107                    } else {
13108                        Ok(e)
13109                    }
13110                }
13111
13112                Action::RegexpReplacePositionSnowflakeToDuckDB => {
13113                    // Snowflake REGEXP_REPLACE(s, p, r, pos, occ) -> DuckDB form
13114                    // pos=1, occ=1 -> REGEXP_REPLACE(s, p, r) (single replace, no 'g')
13115                    // pos>1, occ=0 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r, 'g')
13116                    // pos>1, occ=1 -> SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r)
13117                    // pos=1, occ=0 -> REGEXP_REPLACE(s, p, r, 'g') (replace all)
13118                    if let Expression::Function(f) = e {
13119                        let mut args = f.args;
13120                        let subject = args.remove(0);
13121                        let pattern = args.remove(0);
13122                        let replacement = args.remove(0);
13123                        let position = args.remove(0);
13124                        let occurrence = args.remove(0);
13125
13126                        let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13127                        let is_occ_0 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13128                        let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13129
13130                        if is_pos_1 && is_occ_1 {
13131                            // REGEXP_REPLACE(s, p, r) - single replace, no flags
13132                            Ok(Expression::Function(Box::new(Function::new(
13133                                "REGEXP_REPLACE".to_string(),
13134                                vec![subject, pattern, replacement],
13135                            ))))
13136                        } else if is_pos_1 && is_occ_0 {
13137                            // REGEXP_REPLACE(s, p, r, 'g') - global replace
13138                            Ok(Expression::Function(Box::new(Function::new(
13139                                "REGEXP_REPLACE".to_string(),
13140                                vec![
13141                                    subject,
13142                                    pattern,
13143                                    replacement,
13144                                    Expression::Literal(Box::new(Literal::String("g".to_string()))),
13145                                ],
13146                            ))))
13147                        } else {
13148                            // pos>1: SUBSTRING(s, 1, pos-1) || REGEXP_REPLACE(SUBSTRING(s, pos), p, r[, 'g'])
13149                            // Pre-compute pos-1 when position is a numeric literal
13150                            let pos_minus_1 = if let Expression::Literal(ref lit) = position {
13151                                if let Literal::Number(ref n) = lit.as_ref() {
13152                                    if let Ok(val) = n.parse::<i64>() {
13153                                        Expression::number(val - 1)
13154                                    } else {
13155                                        Expression::Sub(Box::new(BinaryOp::new(
13156                                            position.clone(),
13157                                            Expression::number(1),
13158                                        )))
13159                                    }
13160                                } else {
13161                                    position.clone()
13162                                }
13163                            } else {
13164                                Expression::Sub(Box::new(BinaryOp::new(
13165                                    position.clone(),
13166                                    Expression::number(1),
13167                                )))
13168                            };
13169                            let prefix = Expression::Function(Box::new(Function::new(
13170                                "SUBSTRING".to_string(),
13171                                vec![subject.clone(), Expression::number(1), pos_minus_1],
13172                            )));
13173                            let suffix_subject = Expression::Function(Box::new(Function::new(
13174                                "SUBSTRING".to_string(),
13175                                vec![subject, position],
13176                            )));
13177                            let mut replace_args = vec![suffix_subject, pattern, replacement];
13178                            if is_occ_0 {
13179                                replace_args.push(Expression::Literal(Box::new(Literal::String(
13180                                    "g".to_string(),
13181                                ))));
13182                            }
13183                            let replace_expr = Expression::Function(Box::new(Function::new(
13184                                "REGEXP_REPLACE".to_string(),
13185                                replace_args,
13186                            )));
13187                            Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
13188                                this: Box::new(prefix),
13189                                expression: Box::new(replace_expr),
13190                                safe: None,
13191                            })))
13192                        }
13193                    } else {
13194                        Ok(e)
13195                    }
13196                }
13197
13198                Action::RegexpSubstrSnowflakeToDuckDB => {
13199                    // Snowflake REGEXP_SUBSTR -> DuckDB REGEXP_EXTRACT variants
13200                    if let Expression::Function(f) = e {
13201                        let mut args = f.args;
13202                        let arg_count = args.len();
13203                        match arg_count {
13204                            // REGEXP_SUBSTR(s, p) -> REGEXP_EXTRACT(s, p)
13205                            0..=2 => Ok(Expression::Function(Box::new(Function::new(
13206                                "REGEXP_EXTRACT".to_string(),
13207                                args,
13208                            )))),
13209                            // REGEXP_SUBSTR(s, p, pos) -> REGEXP_EXTRACT(NULLIF(SUBSTRING(s, pos), ''), p)
13210                            3 => {
13211                                let subject = args.remove(0);
13212                                let pattern = args.remove(0);
13213                                let position = args.remove(0);
13214                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13215                                if is_pos_1 {
13216                                    Ok(Expression::Function(Box::new(Function::new(
13217                                        "REGEXP_EXTRACT".to_string(),
13218                                        vec![subject, pattern],
13219                                    ))))
13220                                } else {
13221                                    let substring_expr =
13222                                        Expression::Function(Box::new(Function::new(
13223                                            "SUBSTRING".to_string(),
13224                                            vec![subject, position],
13225                                        )));
13226                                    let nullif_expr =
13227                                        Expression::Function(Box::new(Function::new(
13228                                            "NULLIF".to_string(),
13229                                            vec![
13230                                                substring_expr,
13231                                                Expression::Literal(Box::new(Literal::String(
13232                                                    String::new(),
13233                                                ))),
13234                                            ],
13235                                        )));
13236                                    Ok(Expression::Function(Box::new(Function::new(
13237                                        "REGEXP_EXTRACT".to_string(),
13238                                        vec![nullif_expr, pattern],
13239                                    ))))
13240                                }
13241                            }
13242                            // REGEXP_SUBSTR(s, p, pos, occ) -> depends on pos and occ
13243                            4 => {
13244                                let subject = args.remove(0);
13245                                let pattern = args.remove(0);
13246                                let position = args.remove(0);
13247                                let occurrence = args.remove(0);
13248                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13249                                let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13250
13251                                let effective_subject = if is_pos_1 {
13252                                    subject
13253                                } else {
13254                                    let substring_expr =
13255                                        Expression::Function(Box::new(Function::new(
13256                                            "SUBSTRING".to_string(),
13257                                            vec![subject, position],
13258                                        )));
13259                                    Expression::Function(Box::new(Function::new(
13260                                        "NULLIF".to_string(),
13261                                        vec![
13262                                            substring_expr,
13263                                            Expression::Literal(Box::new(Literal::String(
13264                                                String::new(),
13265                                            ))),
13266                                        ],
13267                                    )))
13268                                };
13269
13270                                if is_occ_1 {
13271                                    Ok(Expression::Function(Box::new(Function::new(
13272                                        "REGEXP_EXTRACT".to_string(),
13273                                        vec![effective_subject, pattern],
13274                                    ))))
13275                                } else {
13276                                    // ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, p), occ)
13277                                    let extract_all =
13278                                        Expression::Function(Box::new(Function::new(
13279                                            "REGEXP_EXTRACT_ALL".to_string(),
13280                                            vec![effective_subject, pattern],
13281                                        )));
13282                                    Ok(Expression::Function(Box::new(Function::new(
13283                                        "ARRAY_EXTRACT".to_string(),
13284                                        vec![extract_all, occurrence],
13285                                    ))))
13286                                }
13287                            }
13288                            // REGEXP_SUBSTR(s, p, 1, 1, 'e') -> REGEXP_EXTRACT(s, p)
13289                            5 => {
13290                                let subject = args.remove(0);
13291                                let pattern = args.remove(0);
13292                                let _position = args.remove(0);
13293                                let _occurrence = args.remove(0);
13294                                let _flags = args.remove(0);
13295                                // Strip 'e' flag, convert to REGEXP_EXTRACT
13296                                Ok(Expression::Function(Box::new(Function::new(
13297                                    "REGEXP_EXTRACT".to_string(),
13298                                    vec![subject, pattern],
13299                                ))))
13300                            }
13301                            // REGEXP_SUBSTR(s, p, 1, 1, 'e', group) -> REGEXP_EXTRACT(s, p[, group])
13302                            _ => {
13303                                let subject = args.remove(0);
13304                                let pattern = args.remove(0);
13305                                let _position = args.remove(0);
13306                                let _occurrence = args.remove(0);
13307                                let _flags = args.remove(0);
13308                                let group = args.remove(0);
13309                                let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13310                                if is_group_0 {
13311                                    // Strip group=0 (default)
13312                                    Ok(Expression::Function(Box::new(Function::new(
13313                                        "REGEXP_EXTRACT".to_string(),
13314                                        vec![subject, pattern],
13315                                    ))))
13316                                } else {
13317                                    Ok(Expression::Function(Box::new(Function::new(
13318                                        "REGEXP_EXTRACT".to_string(),
13319                                        vec![subject, pattern, group],
13320                                    ))))
13321                                }
13322                            }
13323                        }
13324                    } else {
13325                        Ok(e)
13326                    }
13327                }
13328
13329                Action::RegexpSubstrSnowflakeIdentity => {
13330                    // Snowflake→Snowflake: REGEXP_SUBSTR/REGEXP_SUBSTR_ALL with 6 args
13331                    // Strip trailing group=0
13332                    if let Expression::Function(f) = e {
13333                        let func_name = f.name.clone();
13334                        let mut args = f.args;
13335                        if args.len() == 6 {
13336                            let is_group_0 = matches!(&args[5], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13337                            if is_group_0 {
13338                                args.truncate(5);
13339                            }
13340                        }
13341                        Ok(Expression::Function(Box::new(Function::new(
13342                            func_name, args,
13343                        ))))
13344                    } else {
13345                        Ok(e)
13346                    }
13347                }
13348
13349                Action::RegexpSubstrAllSnowflakeToDuckDB => {
13350                    // Snowflake REGEXP_SUBSTR_ALL -> DuckDB REGEXP_EXTRACT_ALL variants
13351                    if let Expression::Function(f) = e {
13352                        let mut args = f.args;
13353                        let arg_count = args.len();
13354                        match arg_count {
13355                            // REGEXP_SUBSTR_ALL(s, p) -> REGEXP_EXTRACT_ALL(s, p)
13356                            0..=2 => Ok(Expression::Function(Box::new(Function::new(
13357                                "REGEXP_EXTRACT_ALL".to_string(),
13358                                args,
13359                            )))),
13360                            // REGEXP_SUBSTR_ALL(s, p, pos) -> REGEXP_EXTRACT_ALL(SUBSTRING(s, pos), p)
13361                            3 => {
13362                                let subject = args.remove(0);
13363                                let pattern = args.remove(0);
13364                                let position = args.remove(0);
13365                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13366                                if is_pos_1 {
13367                                    Ok(Expression::Function(Box::new(Function::new(
13368                                        "REGEXP_EXTRACT_ALL".to_string(),
13369                                        vec![subject, pattern],
13370                                    ))))
13371                                } else {
13372                                    let substring_expr =
13373                                        Expression::Function(Box::new(Function::new(
13374                                            "SUBSTRING".to_string(),
13375                                            vec![subject, position],
13376                                        )));
13377                                    Ok(Expression::Function(Box::new(Function::new(
13378                                        "REGEXP_EXTRACT_ALL".to_string(),
13379                                        vec![substring_expr, pattern],
13380                                    ))))
13381                                }
13382                            }
13383                            // REGEXP_SUBSTR_ALL(s, p, 1, occ) -> REGEXP_EXTRACT_ALL(s, p)[occ:]
13384                            4 => {
13385                                let subject = args.remove(0);
13386                                let pattern = args.remove(0);
13387                                let position = args.remove(0);
13388                                let occurrence = args.remove(0);
13389                                let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13390                                let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
13391
13392                                let effective_subject = if is_pos_1 {
13393                                    subject
13394                                } else {
13395                                    Expression::Function(Box::new(Function::new(
13396                                        "SUBSTRING".to_string(),
13397                                        vec![subject, position],
13398                                    )))
13399                                };
13400
13401                                if is_occ_1 {
13402                                    Ok(Expression::Function(Box::new(Function::new(
13403                                        "REGEXP_EXTRACT_ALL".to_string(),
13404                                        vec![effective_subject, pattern],
13405                                    ))))
13406                                } else {
13407                                    // REGEXP_EXTRACT_ALL(s, p)[occ:]
13408                                    let extract_all =
13409                                        Expression::Function(Box::new(Function::new(
13410                                            "REGEXP_EXTRACT_ALL".to_string(),
13411                                            vec![effective_subject, pattern],
13412                                        )));
13413                                    Ok(Expression::ArraySlice(Box::new(
13414                                        crate::expressions::ArraySlice {
13415                                            this: extract_all,
13416                                            start: Some(occurrence),
13417                                            end: None,
13418                                        },
13419                                    )))
13420                                }
13421                            }
13422                            // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e') -> REGEXP_EXTRACT_ALL(s, p)
13423                            5 => {
13424                                let subject = args.remove(0);
13425                                let pattern = args.remove(0);
13426                                let _position = args.remove(0);
13427                                let _occurrence = args.remove(0);
13428                                let _flags = args.remove(0);
13429                                Ok(Expression::Function(Box::new(Function::new(
13430                                    "REGEXP_EXTRACT_ALL".to_string(),
13431                                    vec![subject, pattern],
13432                                ))))
13433                            }
13434                            // REGEXP_SUBSTR_ALL(s, p, 1, 1, 'e', 0) -> REGEXP_EXTRACT_ALL(s, p)
13435                            _ => {
13436                                let subject = args.remove(0);
13437                                let pattern = args.remove(0);
13438                                let _position = args.remove(0);
13439                                let _occurrence = args.remove(0);
13440                                let _flags = args.remove(0);
13441                                let group = args.remove(0);
13442                                let is_group_0 = matches!(&group, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "0"));
13443                                if is_group_0 {
13444                                    Ok(Expression::Function(Box::new(Function::new(
13445                                        "REGEXP_EXTRACT_ALL".to_string(),
13446                                        vec![subject, pattern],
13447                                    ))))
13448                                } else {
13449                                    Ok(Expression::Function(Box::new(Function::new(
13450                                        "REGEXP_EXTRACT_ALL".to_string(),
13451                                        vec![subject, pattern, group],
13452                                    ))))
13453                                }
13454                            }
13455                        }
13456                    } else {
13457                        Ok(e)
13458                    }
13459                }
13460
13461                Action::RegexpCountSnowflakeToDuckDB => {
13462                    // Snowflake REGEXP_COUNT(s, p[, pos[, flags]]) ->
13463                    // DuckDB: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
13464                    if let Expression::Function(f) = e {
13465                        let mut args = f.args;
13466                        let arg_count = args.len();
13467                        let subject = args.remove(0);
13468                        let pattern = args.remove(0);
13469
13470                        // Handle position arg
13471                        let effective_subject = if arg_count >= 3 {
13472                            let position = args.remove(0);
13473                            Expression::Function(Box::new(Function::new(
13474                                "SUBSTRING".to_string(),
13475                                vec![subject, position],
13476                            )))
13477                        } else {
13478                            subject
13479                        };
13480
13481                        // Handle flags arg -> embed as (?flags) prefix in pattern
13482                        let effective_pattern = if arg_count >= 4 {
13483                            let flags = args.remove(0);
13484                            match &flags {
13485                                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(f_str) if !f_str.is_empty()) =>
13486                                {
13487                                    let Literal::String(f_str) = lit.as_ref() else {
13488                                        unreachable!()
13489                                    };
13490                                    // Always use concatenation: '(?flags)' || pattern
13491                                    let prefix = Expression::Literal(Box::new(Literal::String(
13492                                        format!("(?{})", f_str),
13493                                    )));
13494                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
13495                                        this: Box::new(prefix),
13496                                        expression: Box::new(pattern.clone()),
13497                                        safe: None,
13498                                    }))
13499                                }
13500                                _ => pattern.clone(),
13501                            }
13502                        } else {
13503                            pattern.clone()
13504                        };
13505
13506                        // Build: CASE WHEN p = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, p)) END
13507                        let extract_all = Expression::Function(Box::new(Function::new(
13508                            "REGEXP_EXTRACT_ALL".to_string(),
13509                            vec![effective_subject, effective_pattern.clone()],
13510                        )));
13511                        let length_expr =
13512                            Expression::Length(Box::new(crate::expressions::UnaryFunc {
13513                                this: extract_all,
13514                                original_name: None,
13515                                inferred_type: None,
13516                            }));
13517                        let condition = Expression::Eq(Box::new(BinaryOp::new(
13518                            effective_pattern,
13519                            Expression::Literal(Box::new(Literal::String(String::new()))),
13520                        )));
13521                        Ok(Expression::Case(Box::new(Case {
13522                            operand: None,
13523                            whens: vec![(condition, Expression::number(0))],
13524                            else_: Some(length_expr),
13525                            comments: vec![],
13526                            inferred_type: None,
13527                        })))
13528                    } else {
13529                        Ok(e)
13530                    }
13531                }
13532
13533                Action::RegexpInstrSnowflakeToDuckDB => {
13534                    // Snowflake REGEXP_INSTR(s, p[, pos[, occ[, option[, flags[, group]]]]]) ->
13535                    // DuckDB: CASE WHEN s IS NULL OR p IS NULL [OR ...] THEN NULL
13536                    //              WHEN p = '' THEN 0
13537                    //              WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
13538                    //              ELSE 1 + COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(eff_s, eff_p)[1:occ], x -> LENGTH(x))), 0)
13539                    //                     + COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(eff_s, eff_p)[1:occ - 1], x -> LENGTH(x))), 0)
13540                    //                     + pos_offset
13541                    //         END
13542                    if let Expression::Function(f) = e {
13543                        let mut args = f.args;
13544                        let subject = args.remove(0);
13545                        let pattern = if !args.is_empty() {
13546                            args.remove(0)
13547                        } else {
13548                            Expression::Literal(Box::new(Literal::String(String::new())))
13549                        };
13550
13551                        // Collect all original args for NULL checks
13552                        let position = if !args.is_empty() {
13553                            Some(args.remove(0))
13554                        } else {
13555                            None
13556                        };
13557                        let occurrence = if !args.is_empty() {
13558                            Some(args.remove(0))
13559                        } else {
13560                            None
13561                        };
13562                        let option = if !args.is_empty() {
13563                            Some(args.remove(0))
13564                        } else {
13565                            None
13566                        };
13567                        let flags = if !args.is_empty() {
13568                            Some(args.remove(0))
13569                        } else {
13570                            None
13571                        };
13572                        let _group = if !args.is_empty() {
13573                            Some(args.remove(0))
13574                        } else {
13575                            None
13576                        };
13577
13578                        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")));
13579                        let occurrence_expr = occurrence.clone().unwrap_or(Expression::number(1));
13580
13581                        // Build NULL check: subject IS NULL OR pattern IS NULL [OR pos IS NULL ...]
13582                        let mut null_checks: Vec<Expression> = vec![
13583                            Expression::Is(Box::new(BinaryOp::new(
13584                                subject.clone(),
13585                                Expression::Null(Null),
13586                            ))),
13587                            Expression::Is(Box::new(BinaryOp::new(
13588                                pattern.clone(),
13589                                Expression::Null(Null),
13590                            ))),
13591                        ];
13592                        // Add NULL checks for all provided optional args
13593                        for opt_arg in [&position, &occurrence, &option, &flags].iter() {
13594                            if let Some(arg) = opt_arg {
13595                                null_checks.push(Expression::Is(Box::new(BinaryOp::new(
13596                                    (*arg).clone(),
13597                                    Expression::Null(Null),
13598                                ))));
13599                            }
13600                        }
13601                        // Chain with OR
13602                        let null_condition = null_checks
13603                            .into_iter()
13604                            .reduce(|a, b| Expression::Or(Box::new(BinaryOp::new(a, b))))
13605                            .unwrap();
13606
13607                        // Effective subject (apply position offset)
13608                        let effective_subject = if is_pos_1 {
13609                            subject.clone()
13610                        } else {
13611                            let pos = position.clone().unwrap_or(Expression::number(1));
13612                            Expression::Function(Box::new(Function::new(
13613                                "SUBSTRING".to_string(),
13614                                vec![subject.clone(), pos],
13615                            )))
13616                        };
13617
13618                        // Effective pattern (apply flags if present)
13619                        let effective_pattern = if let Some(ref fl) = flags {
13620                            if let Expression::Literal(lit) = fl {
13621                                if let Literal::String(f_str) = lit.as_ref() {
13622                                    if !f_str.is_empty() {
13623                                        let prefix = Expression::Literal(Box::new(
13624                                            Literal::String(format!("(?{})", f_str)),
13625                                        ));
13626                                        Expression::DPipe(Box::new(crate::expressions::DPipe {
13627                                            this: Box::new(prefix),
13628                                            expression: Box::new(pattern.clone()),
13629                                            safe: None,
13630                                        }))
13631                                    } else {
13632                                        pattern.clone()
13633                                    }
13634                                } else {
13635                                    fl.clone()
13636                                }
13637                            } else {
13638                                pattern.clone()
13639                            }
13640                        } else {
13641                            pattern.clone()
13642                        };
13643
13644                        // WHEN pattern = '' THEN 0
13645                        let empty_pattern_check = Expression::Eq(Box::new(BinaryOp::new(
13646                            effective_pattern.clone(),
13647                            Expression::Literal(Box::new(Literal::String(String::new()))),
13648                        )));
13649
13650                        // WHEN LENGTH(REGEXP_EXTRACT_ALL(eff_s, eff_p)) < occ THEN 0
13651                        let match_count_check = Expression::Lt(Box::new(BinaryOp::new(
13652                            Expression::Length(Box::new(crate::expressions::UnaryFunc {
13653                                this: Expression::Function(Box::new(Function::new(
13654                                    "REGEXP_EXTRACT_ALL".to_string(),
13655                                    vec![effective_subject.clone(), effective_pattern.clone()],
13656                                ))),
13657                                original_name: None,
13658                                inferred_type: None,
13659                            })),
13660                            occurrence_expr.clone(),
13661                        )));
13662
13663                        // Helper: build LENGTH lambda for LIST_TRANSFORM
13664                        let make_len_lambda = || {
13665                            Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
13666                                parameters: vec![crate::expressions::Identifier::new("x")],
13667                                body: Expression::Length(Box::new(crate::expressions::UnaryFunc {
13668                                    this: Expression::Identifier(
13669                                        crate::expressions::Identifier::new("x"),
13670                                    ),
13671                                    original_name: None,
13672                                    inferred_type: None,
13673                                })),
13674                                colon: false,
13675                                parameter_types: vec![],
13676                            }))
13677                        };
13678
13679                        // COALESCE(LIST_SUM(LIST_TRANSFORM(STRING_SPLIT_REGEX(s, p)[1:occ], x -> LENGTH(x))), 0)
13680                        let split_sliced =
13681                            Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
13682                                this: Expression::Function(Box::new(Function::new(
13683                                    "STRING_SPLIT_REGEX".to_string(),
13684                                    vec![effective_subject.clone(), effective_pattern.clone()],
13685                                ))),
13686                                start: Some(Expression::number(1)),
13687                                end: Some(occurrence_expr.clone()),
13688                            }));
13689                        let split_sum = Expression::Function(Box::new(Function::new(
13690                            "COALESCE".to_string(),
13691                            vec![
13692                                Expression::Function(Box::new(Function::new(
13693                                    "LIST_SUM".to_string(),
13694                                    vec![Expression::Function(Box::new(Function::new(
13695                                        "LIST_TRANSFORM".to_string(),
13696                                        vec![split_sliced, make_len_lambda()],
13697                                    )))],
13698                                ))),
13699                                Expression::number(0),
13700                            ],
13701                        )));
13702
13703                        // COALESCE(LIST_SUM(LIST_TRANSFORM(REGEXP_EXTRACT_ALL(s, p)[1:occ - 1], x -> LENGTH(x))), 0)
13704                        let extract_sliced =
13705                            Expression::ArraySlice(Box::new(crate::expressions::ArraySlice {
13706                                this: Expression::Function(Box::new(Function::new(
13707                                    "REGEXP_EXTRACT_ALL".to_string(),
13708                                    vec![effective_subject.clone(), effective_pattern.clone()],
13709                                ))),
13710                                start: Some(Expression::number(1)),
13711                                end: Some(Expression::Sub(Box::new(BinaryOp::new(
13712                                    occurrence_expr.clone(),
13713                                    Expression::number(1),
13714                                )))),
13715                            }));
13716                        let extract_sum = Expression::Function(Box::new(Function::new(
13717                            "COALESCE".to_string(),
13718                            vec![
13719                                Expression::Function(Box::new(Function::new(
13720                                    "LIST_SUM".to_string(),
13721                                    vec![Expression::Function(Box::new(Function::new(
13722                                        "LIST_TRANSFORM".to_string(),
13723                                        vec![extract_sliced, make_len_lambda()],
13724                                    )))],
13725                                ))),
13726                                Expression::number(0),
13727                            ],
13728                        )));
13729
13730                        // Position offset: pos - 1 when pos > 1, else 0
13731                        let pos_offset: Expression = if !is_pos_1 {
13732                            let pos = position.clone().unwrap_or(Expression::number(1));
13733                            Expression::Sub(Box::new(BinaryOp::new(pos, Expression::number(1))))
13734                        } else {
13735                            Expression::number(0)
13736                        };
13737
13738                        // ELSE: 1 + split_sum + extract_sum + pos_offset
13739                        let else_expr = Expression::Add(Box::new(BinaryOp::new(
13740                            Expression::Add(Box::new(BinaryOp::new(
13741                                Expression::Add(Box::new(BinaryOp::new(
13742                                    Expression::number(1),
13743                                    split_sum,
13744                                ))),
13745                                extract_sum,
13746                            ))),
13747                            pos_offset,
13748                        )));
13749
13750                        Ok(Expression::Case(Box::new(Case {
13751                            operand: None,
13752                            whens: vec![
13753                                (null_condition, Expression::Null(Null)),
13754                                (empty_pattern_check, Expression::number(0)),
13755                                (match_count_check, Expression::number(0)),
13756                            ],
13757                            else_: Some(else_expr),
13758                            comments: vec![],
13759                            inferred_type: None,
13760                        })))
13761                    } else {
13762                        Ok(e)
13763                    }
13764                }
13765
13766                Action::RlikeSnowflakeToDuckDB => {
13767                    // Snowflake RLIKE(a, b[, flags]) -> DuckDB REGEXP_FULL_MATCH(a, b[, flags])
13768                    // Both do full-string matching, so no anchoring needed
13769                    let (subject, pattern, flags) = match e {
13770                        Expression::RegexpLike(ref rl) => {
13771                            (rl.this.clone(), rl.pattern.clone(), rl.flags.clone())
13772                        }
13773                        Expression::Function(ref f) if f.args.len() >= 2 => {
13774                            let s = f.args[0].clone();
13775                            let p = f.args[1].clone();
13776                            let fl = f.args.get(2).cloned();
13777                            (s, p, fl)
13778                        }
13779                        _ => return Ok(e),
13780                    };
13781
13782                    let mut result_args = vec![subject, pattern];
13783                    if let Some(fl) = flags {
13784                        result_args.push(fl);
13785                    }
13786                    Ok(Expression::Function(Box::new(Function::new(
13787                        "REGEXP_FULL_MATCH".to_string(),
13788                        result_args,
13789                    ))))
13790                }
13791
13792                Action::RegexpExtractAllToSnowflake => {
13793                    // BigQuery REGEXP_EXTRACT_ALL(s, p) -> Snowflake REGEXP_SUBSTR_ALL(s, p)
13794                    // With capture group: REGEXP_SUBSTR_ALL(s, p, 1, 1, 'c', 1)
13795                    if let Expression::Function(f) = e {
13796                        let mut args = f.args;
13797                        if args.len() >= 2 {
13798                            let str_expr = args.remove(0);
13799                            let pattern = args.remove(0);
13800
13801                            let has_groups = match &pattern {
13802                                Expression::Literal(lit)
13803                                    if matches!(lit.as_ref(), Literal::String(_)) =>
13804                                {
13805                                    let Literal::String(s) = lit.as_ref() else {
13806                                        unreachable!()
13807                                    };
13808                                    s.contains('(') && s.contains(')')
13809                                }
13810                                _ => false,
13811                            };
13812
13813                            if has_groups {
13814                                Ok(Expression::Function(Box::new(Function::new(
13815                                    "REGEXP_SUBSTR_ALL".to_string(),
13816                                    vec![
13817                                        str_expr,
13818                                        pattern,
13819                                        Expression::number(1),
13820                                        Expression::number(1),
13821                                        Expression::Literal(Box::new(Literal::String(
13822                                            "c".to_string(),
13823                                        ))),
13824                                        Expression::number(1),
13825                                    ],
13826                                ))))
13827                            } else {
13828                                Ok(Expression::Function(Box::new(Function::new(
13829                                    "REGEXP_SUBSTR_ALL".to_string(),
13830                                    vec![str_expr, pattern],
13831                                ))))
13832                            }
13833                        } else {
13834                            Ok(Expression::Function(Box::new(Function::new(
13835                                "REGEXP_SUBSTR_ALL".to_string(),
13836                                args,
13837                            ))))
13838                        }
13839                    } else {
13840                        Ok(e)
13841                    }
13842                }
13843
13844                Action::SetToVariable => {
13845                    // For DuckDB: SET a = 1 -> SET VARIABLE a = 1
13846                    if let Expression::SetStatement(mut s) = e {
13847                        for item in &mut s.items {
13848                            if item.kind.is_none() {
13849                                // Check if name already has VARIABLE prefix (from DuckDB source parsing)
13850                                let already_variable = match &item.name {
13851                                    Expression::Identifier(id) => id.name.starts_with("VARIABLE "),
13852                                    _ => false,
13853                                };
13854                                if already_variable {
13855                                    // Extract the actual name and set kind
13856                                    if let Expression::Identifier(ref mut id) = item.name {
13857                                        let actual_name = id.name["VARIABLE ".len()..].to_string();
13858                                        id.name = actual_name;
13859                                    }
13860                                }
13861                                item.kind = Some("VARIABLE".to_string());
13862                            }
13863                        }
13864                        Ok(Expression::SetStatement(s))
13865                    } else {
13866                        Ok(e)
13867                    }
13868                }
13869
13870                Action::ConvertTimezoneToExpr => {
13871                    // Convert Function("CONVERT_TIMEZONE", args) to Expression::ConvertTimezone
13872                    // This prevents Redshift's transform_expr from expanding 2-arg to 3-arg with 'UTC'
13873                    if let Expression::Function(f) = e {
13874                        if f.args.len() == 2 {
13875                            let mut args = f.args;
13876                            let target_tz = args.remove(0);
13877                            let timestamp = args.remove(0);
13878                            Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
13879                                source_tz: None,
13880                                target_tz: Some(Box::new(target_tz)),
13881                                timestamp: Some(Box::new(timestamp)),
13882                                options: vec![],
13883                            })))
13884                        } else if f.args.len() == 3 {
13885                            let mut args = f.args;
13886                            let source_tz = args.remove(0);
13887                            let target_tz = args.remove(0);
13888                            let timestamp = args.remove(0);
13889                            Ok(Expression::ConvertTimezone(Box::new(ConvertTimezone {
13890                                source_tz: Some(Box::new(source_tz)),
13891                                target_tz: Some(Box::new(target_tz)),
13892                                timestamp: Some(Box::new(timestamp)),
13893                                options: vec![],
13894                            })))
13895                        } else {
13896                            Ok(Expression::Function(f))
13897                        }
13898                    } else {
13899                        Ok(e)
13900                    }
13901                }
13902
13903                Action::BigQueryCastType => {
13904                    // Convert BigQuery types to standard SQL types
13905                    if let Expression::DataType(dt) = e {
13906                        match dt {
13907                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("INT64") => {
13908                                Ok(Expression::DataType(DataType::BigInt { length: None }))
13909                            }
13910                            DataType::Custom { ref name }
13911                                if name.eq_ignore_ascii_case("FLOAT64") =>
13912                            {
13913                                Ok(Expression::DataType(DataType::Double {
13914                                    precision: None,
13915                                    scale: None,
13916                                }))
13917                            }
13918                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("BOOL") => {
13919                                Ok(Expression::DataType(DataType::Boolean))
13920                            }
13921                            DataType::Custom { ref name } if name.eq_ignore_ascii_case("BYTES") => {
13922                                Ok(Expression::DataType(DataType::VarBinary { length: None }))
13923                            }
13924                            DataType::Custom { ref name }
13925                                if name.eq_ignore_ascii_case("NUMERIC") =>
13926                            {
13927                                // For DuckDB target, use Custom("DECIMAL") to avoid DuckDB's
13928                                // default precision (18, 3) being added to bare DECIMAL
13929                                if matches!(target, DialectType::DuckDB) {
13930                                    Ok(Expression::DataType(DataType::Custom {
13931                                        name: "DECIMAL".to_string(),
13932                                    }))
13933                                } else {
13934                                    Ok(Expression::DataType(DataType::Decimal {
13935                                        precision: None,
13936                                        scale: None,
13937                                    }))
13938                                }
13939                            }
13940                            DataType::Custom { ref name }
13941                                if name.eq_ignore_ascii_case("STRING") =>
13942                            {
13943                                Ok(Expression::DataType(DataType::String { length: None }))
13944                            }
13945                            DataType::Custom { ref name }
13946                                if name.eq_ignore_ascii_case("DATETIME") =>
13947                            {
13948                                Ok(Expression::DataType(DataType::Timestamp {
13949                                    precision: None,
13950                                    timezone: false,
13951                                }))
13952                            }
13953                            _ => Ok(Expression::DataType(dt)),
13954                        }
13955                    } else {
13956                        Ok(e)
13957                    }
13958                }
13959
13960                Action::BigQuerySafeDivide => {
13961                    // Convert SafeDivide expression to IF/CASE form for most targets
13962                    if let Expression::SafeDivide(sd) = e {
13963                        let x = *sd.this;
13964                        let y = *sd.expression;
13965                        // Wrap x and y in parens if they're complex expressions
13966                        let y_ref = match &y {
13967                            Expression::Column(_)
13968                            | Expression::Literal(_)
13969                            | Expression::Identifier(_) => y.clone(),
13970                            _ => Expression::Paren(Box::new(Paren {
13971                                this: y.clone(),
13972                                trailing_comments: vec![],
13973                            })),
13974                        };
13975                        let x_ref = match &x {
13976                            Expression::Column(_)
13977                            | Expression::Literal(_)
13978                            | Expression::Identifier(_) => x.clone(),
13979                            _ => Expression::Paren(Box::new(Paren {
13980                                this: x.clone(),
13981                                trailing_comments: vec![],
13982                            })),
13983                        };
13984                        let condition = Expression::Neq(Box::new(BinaryOp::new(
13985                            y_ref.clone(),
13986                            Expression::number(0),
13987                        )));
13988                        let div_expr = Expression::Div(Box::new(BinaryOp::new(x_ref, y_ref)));
13989
13990                        if matches!(target, DialectType::Spark | DialectType::Databricks) {
13991                            Ok(Expression::Function(Box::new(Function::new(
13992                                "TRY_DIVIDE".to_string(),
13993                                vec![x, y],
13994                            ))))
13995                        } else if matches!(target, DialectType::Presto | DialectType::Trino) {
13996                            // Presto/Trino: IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
13997                            let cast_x = Expression::Cast(Box::new(Cast {
13998                                this: match &x {
13999                                    Expression::Column(_)
14000                                    | Expression::Literal(_)
14001                                    | Expression::Identifier(_) => x,
14002                                    _ => Expression::Paren(Box::new(Paren {
14003                                        this: x,
14004                                        trailing_comments: vec![],
14005                                    })),
14006                                },
14007                                to: DataType::Double {
14008                                    precision: None,
14009                                    scale: None,
14010                                },
14011                                trailing_comments: vec![],
14012                                double_colon_syntax: false,
14013                                format: None,
14014                                default: None,
14015                                inferred_type: None,
14016                            }));
14017                            let cast_div = Expression::Div(Box::new(BinaryOp::new(
14018                                cast_x,
14019                                match &y {
14020                                    Expression::Column(_)
14021                                    | Expression::Literal(_)
14022                                    | Expression::Identifier(_) => y,
14023                                    _ => Expression::Paren(Box::new(Paren {
14024                                        this: y,
14025                                        trailing_comments: vec![],
14026                                    })),
14027                                },
14028                            )));
14029                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14030                                condition,
14031                                true_value: cast_div,
14032                                false_value: Some(Expression::Null(Null)),
14033                                original_name: None,
14034                                inferred_type: None,
14035                            })))
14036                        } else if matches!(target, DialectType::PostgreSQL) {
14037                            // PostgreSQL: CASE WHEN y <> 0 THEN CAST(x AS DOUBLE PRECISION) / y ELSE NULL END
14038                            let cast_x = Expression::Cast(Box::new(Cast {
14039                                this: match &x {
14040                                    Expression::Column(_)
14041                                    | Expression::Literal(_)
14042                                    | Expression::Identifier(_) => x,
14043                                    _ => Expression::Paren(Box::new(Paren {
14044                                        this: x,
14045                                        trailing_comments: vec![],
14046                                    })),
14047                                },
14048                                to: DataType::Custom {
14049                                    name: "DOUBLE PRECISION".to_string(),
14050                                },
14051                                trailing_comments: vec![],
14052                                double_colon_syntax: false,
14053                                format: None,
14054                                default: None,
14055                                inferred_type: None,
14056                            }));
14057                            let y_paren = match &y {
14058                                Expression::Column(_)
14059                                | Expression::Literal(_)
14060                                | Expression::Identifier(_) => y,
14061                                _ => Expression::Paren(Box::new(Paren {
14062                                    this: y,
14063                                    trailing_comments: vec![],
14064                                })),
14065                            };
14066                            let cast_div =
14067                                Expression::Div(Box::new(BinaryOp::new(cast_x, y_paren)));
14068                            Ok(Expression::Case(Box::new(Case {
14069                                operand: None,
14070                                whens: vec![(condition, cast_div)],
14071                                else_: Some(Expression::Null(Null)),
14072                                comments: Vec::new(),
14073                                inferred_type: None,
14074                            })))
14075                        } else if matches!(target, DialectType::DuckDB) {
14076                            // DuckDB: CASE WHEN y <> 0 THEN x / y ELSE NULL END
14077                            Ok(Expression::Case(Box::new(Case {
14078                                operand: None,
14079                                whens: vec![(condition, div_expr)],
14080                                else_: Some(Expression::Null(Null)),
14081                                comments: Vec::new(),
14082                                inferred_type: None,
14083                            })))
14084                        } else if matches!(target, DialectType::Snowflake) {
14085                            // Snowflake: IFF(y <> 0, x / y, NULL)
14086                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14087                                condition,
14088                                true_value: div_expr,
14089                                false_value: Some(Expression::Null(Null)),
14090                                original_name: Some("IFF".to_string()),
14091                                inferred_type: None,
14092                            })))
14093                        } else {
14094                            // All others: IF(y <> 0, x / y, NULL)
14095                            Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
14096                                condition,
14097                                true_value: div_expr,
14098                                false_value: Some(Expression::Null(Null)),
14099                                original_name: None,
14100                                inferred_type: None,
14101                            })))
14102                        }
14103                    } else {
14104                        Ok(e)
14105                    }
14106                }
14107
14108                Action::BigQueryLastDayStripUnit => {
14109                    if let Expression::LastDay(mut ld) = e {
14110                        ld.unit = None; // Strip the unit (MONTH is default)
14111                        match target {
14112                            DialectType::PostgreSQL => {
14113                                // LAST_DAY(date) -> CAST(DATE_TRUNC('MONTH', date) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
14114                                let date_trunc = Expression::Function(Box::new(Function::new(
14115                                    "DATE_TRUNC".to_string(),
14116                                    vec![
14117                                        Expression::Literal(Box::new(
14118                                            crate::expressions::Literal::String(
14119                                                "MONTH".to_string(),
14120                                            ),
14121                                        )),
14122                                        ld.this.clone(),
14123                                    ],
14124                                )));
14125                                let plus_month =
14126                                    Expression::Add(Box::new(crate::expressions::BinaryOp::new(
14127                                        date_trunc,
14128                                        Expression::Interval(Box::new(
14129                                            crate::expressions::Interval {
14130                                                this: Some(Expression::Literal(Box::new(
14131                                                    crate::expressions::Literal::String(
14132                                                        "1 MONTH".to_string(),
14133                                                    ),
14134                                                ))),
14135                                                unit: None,
14136                                            },
14137                                        )),
14138                                    )));
14139                                let minus_day =
14140                                    Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
14141                                        plus_month,
14142                                        Expression::Interval(Box::new(
14143                                            crate::expressions::Interval {
14144                                                this: Some(Expression::Literal(Box::new(
14145                                                    crate::expressions::Literal::String(
14146                                                        "1 DAY".to_string(),
14147                                                    ),
14148                                                ))),
14149                                                unit: None,
14150                                            },
14151                                        )),
14152                                    )));
14153                                Ok(Expression::Cast(Box::new(Cast {
14154                                    this: minus_day,
14155                                    to: DataType::Date,
14156                                    trailing_comments: vec![],
14157                                    double_colon_syntax: false,
14158                                    format: None,
14159                                    default: None,
14160                                    inferred_type: None,
14161                                })))
14162                            }
14163                            DialectType::Presto => {
14164                                // LAST_DAY(date) -> LAST_DAY_OF_MONTH(date)
14165                                Ok(Expression::Function(Box::new(Function::new(
14166                                    "LAST_DAY_OF_MONTH".to_string(),
14167                                    vec![ld.this],
14168                                ))))
14169                            }
14170                            DialectType::ClickHouse => {
14171                                // ClickHouse LAST_DAY(CAST(x AS Nullable(DATE)))
14172                                // Need to wrap the DATE type in Nullable
14173                                let nullable_date = match ld.this {
14174                                    Expression::Cast(mut c) => {
14175                                        c.to = DataType::Nullable {
14176                                            inner: Box::new(DataType::Date),
14177                                        };
14178                                        Expression::Cast(c)
14179                                    }
14180                                    other => other,
14181                                };
14182                                ld.this = nullable_date;
14183                                Ok(Expression::LastDay(ld))
14184                            }
14185                            _ => Ok(Expression::LastDay(ld)),
14186                        }
14187                    } else {
14188                        Ok(e)
14189                    }
14190                }
14191
14192                Action::BigQueryCastFormat => {
14193                    // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE('%m/%d/%Y', x) for BigQuery
14194                    // CAST(x AS TIMESTAMP FORMAT 'fmt') -> PARSE_TIMESTAMP(...) for BigQuery
14195                    // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, ...) AS DATE) for DuckDB
14196                    let (this, to, format_expr, is_safe) = match e {
14197                        Expression::Cast(ref c) if c.format.is_some() => (
14198                            c.this.clone(),
14199                            c.to.clone(),
14200                            c.format.as_ref().unwrap().as_ref().clone(),
14201                            false,
14202                        ),
14203                        Expression::SafeCast(ref c) if c.format.is_some() => (
14204                            c.this.clone(),
14205                            c.to.clone(),
14206                            c.format.as_ref().unwrap().as_ref().clone(),
14207                            true,
14208                        ),
14209                        _ => return Ok(e),
14210                    };
14211                    // For CAST(x AS STRING FORMAT ...) when target is BigQuery, keep as-is
14212                    if matches!(target, DialectType::BigQuery) {
14213                        match &to {
14214                            DataType::String { .. } | DataType::VarChar { .. } | DataType::Text => {
14215                                // CAST(x AS STRING FORMAT 'fmt') stays as CAST expression for BigQuery
14216                                return Ok(e);
14217                            }
14218                            _ => {}
14219                        }
14220                    }
14221                    // Extract timezone from format if AT TIME ZONE is present
14222                    let (actual_format_expr, timezone) = match &format_expr {
14223                        Expression::AtTimeZone(ref atz) => {
14224                            (atz.this.clone(), Some(atz.zone.clone()))
14225                        }
14226                        _ => (format_expr.clone(), None),
14227                    };
14228                    let strftime_fmt = Self::bq_cast_format_to_strftime(&actual_format_expr);
14229                    match target {
14230                        DialectType::BigQuery => {
14231                            // CAST(x AS DATE FORMAT 'fmt') -> PARSE_DATE(strftime_fmt, x)
14232                            // CAST(x AS TIMESTAMP FORMAT 'fmt' AT TIME ZONE 'tz') -> PARSE_TIMESTAMP(strftime_fmt, x, tz)
14233                            let func_name = match &to {
14234                                DataType::Date => "PARSE_DATE",
14235                                DataType::Timestamp { .. } => "PARSE_TIMESTAMP",
14236                                DataType::Time { .. } => "PARSE_TIMESTAMP",
14237                                _ => "PARSE_TIMESTAMP",
14238                            };
14239                            let mut func_args = vec![strftime_fmt, this];
14240                            if let Some(tz) = timezone {
14241                                func_args.push(tz);
14242                            }
14243                            Ok(Expression::Function(Box::new(Function::new(
14244                                func_name.to_string(),
14245                                func_args,
14246                            ))))
14247                        }
14248                        DialectType::DuckDB => {
14249                            // SAFE_CAST(x AS DATE FORMAT 'fmt') -> CAST(TRY_STRPTIME(x, fmt) AS DATE)
14250                            // CAST(x AS DATE FORMAT 'fmt') -> CAST(STRPTIME(x, fmt) AS DATE)
14251                            let duck_fmt = Self::bq_format_to_duckdb(&strftime_fmt);
14252                            let parse_fn_name = if is_safe { "TRY_STRPTIME" } else { "STRPTIME" };
14253                            let parse_call = Expression::Function(Box::new(Function::new(
14254                                parse_fn_name.to_string(),
14255                                vec![this, duck_fmt],
14256                            )));
14257                            Ok(Expression::Cast(Box::new(Cast {
14258                                this: parse_call,
14259                                to,
14260                                trailing_comments: vec![],
14261                                double_colon_syntax: false,
14262                                format: None,
14263                                default: None,
14264                                inferred_type: None,
14265                            })))
14266                        }
14267                        _ => Ok(e),
14268                    }
14269                }
14270
14271                Action::BigQueryFunctionNormalize => {
14272                    Self::normalize_bigquery_function(e, source, target)
14273                }
14274
14275                Action::BigQueryToHexBare => {
14276                    // Not used anymore - handled directly in normalize_bigquery_function
14277                    Ok(e)
14278                }
14279
14280                Action::BigQueryToHexLower => {
14281                    if let Expression::Lower(uf) = e {
14282                        match uf.this {
14283                            // BQ->BQ: LOWER(TO_HEX(x)) -> TO_HEX(x)
14284                            Expression::Function(f)
14285                                if matches!(target, DialectType::BigQuery)
14286                                    && f.name == "TO_HEX" =>
14287                            {
14288                                Ok(Expression::Function(f))
14289                            }
14290                            // LOWER(LOWER(HEX/TO_HEX(x))) patterns
14291                            Expression::Lower(inner_uf) => {
14292                                if matches!(target, DialectType::BigQuery) {
14293                                    // BQ->BQ: extract TO_HEX
14294                                    if let Expression::Function(f) = inner_uf.this {
14295                                        Ok(Expression::Function(Box::new(Function::new(
14296                                            "TO_HEX".to_string(),
14297                                            f.args,
14298                                        ))))
14299                                    } else {
14300                                        Ok(Expression::Lower(inner_uf))
14301                                    }
14302                                } else {
14303                                    // Flatten: LOWER(LOWER(x)) -> LOWER(x)
14304                                    Ok(Expression::Lower(inner_uf))
14305                                }
14306                            }
14307                            other => {
14308                                Ok(Expression::Lower(Box::new(crate::expressions::UnaryFunc {
14309                                    this: other,
14310                                    original_name: None,
14311                                    inferred_type: None,
14312                                })))
14313                            }
14314                        }
14315                    } else {
14316                        Ok(e)
14317                    }
14318                }
14319
14320                Action::BigQueryToHexUpper => {
14321                    // UPPER(LOWER(HEX(x))) -> HEX(x) (UPPER cancels LOWER, HEX is already uppercase)
14322                    // UPPER(LOWER(TO_HEX(x))) -> TO_HEX(x) for Presto/Trino
14323                    if let Expression::Upper(uf) = e {
14324                        if let Expression::Lower(inner_uf) = uf.this {
14325                            // For BQ->BQ: UPPER(TO_HEX(x)) should stay as UPPER(TO_HEX(x))
14326                            if matches!(target, DialectType::BigQuery) {
14327                                // Restore TO_HEX name in inner function
14328                                if let Expression::Function(f) = inner_uf.this {
14329                                    let restored = Expression::Function(Box::new(Function::new(
14330                                        "TO_HEX".to_string(),
14331                                        f.args,
14332                                    )));
14333                                    Ok(Expression::Upper(Box::new(
14334                                        crate::expressions::UnaryFunc::new(restored),
14335                                    )))
14336                                } else {
14337                                    Ok(Expression::Upper(inner_uf))
14338                                }
14339                            } else {
14340                                // Extract the inner HEX/TO_HEX function (UPPER(LOWER(x)) = x when HEX is uppercase)
14341                                Ok(inner_uf.this)
14342                            }
14343                        } else {
14344                            Ok(Expression::Upper(uf))
14345                        }
14346                    } else {
14347                        Ok(e)
14348                    }
14349                }
14350
14351                Action::BigQueryAnyValueHaving => {
14352                    // ANY_VALUE(x HAVING MAX y) -> ARG_MAX_NULL(x, y)
14353                    // ANY_VALUE(x HAVING MIN y) -> ARG_MIN_NULL(x, y)
14354                    if let Expression::AnyValue(agg) = e {
14355                        if let Some((having_expr, is_max)) = agg.having_max {
14356                            let func_name = if is_max {
14357                                "ARG_MAX_NULL"
14358                            } else {
14359                                "ARG_MIN_NULL"
14360                            };
14361                            Ok(Expression::Function(Box::new(Function::new(
14362                                func_name.to_string(),
14363                                vec![agg.this, *having_expr],
14364                            ))))
14365                        } else {
14366                            Ok(Expression::AnyValue(agg))
14367                        }
14368                    } else {
14369                        Ok(e)
14370                    }
14371                }
14372
14373                Action::BigQueryApproxQuantiles => {
14374                    // APPROX_QUANTILES(x, n) -> APPROX_QUANTILE(x, [0, 1/n, 2/n, ..., 1])
14375                    // APPROX_QUANTILES(DISTINCT x, n) -> APPROX_QUANTILE(DISTINCT x, [0, 1/n, ..., 1])
14376                    if let Expression::AggregateFunction(agg) = e {
14377                        if agg.args.len() >= 2 {
14378                            let x_expr = agg.args[0].clone();
14379                            let n_expr = &agg.args[1];
14380
14381                            // Extract the numeric value from n_expr
14382                            let n = match n_expr {
14383                                Expression::Literal(lit)
14384                                    if matches!(
14385                                        lit.as_ref(),
14386                                        crate::expressions::Literal::Number(_)
14387                                    ) =>
14388                                {
14389                                    let crate::expressions::Literal::Number(s) = lit.as_ref()
14390                                    else {
14391                                        unreachable!()
14392                                    };
14393                                    s.parse::<usize>().unwrap_or(2)
14394                                }
14395                                _ => 2,
14396                            };
14397
14398                            // Generate quantile array: [0, 1/n, 2/n, ..., 1]
14399                            let mut quantiles = Vec::new();
14400                            for i in 0..=n {
14401                                let q = i as f64 / n as f64;
14402                                // Format nicely: 0 -> 0, 0.25 -> 0.25, 1 -> 1
14403                                if q == 0.0 {
14404                                    quantiles.push(Expression::number(0));
14405                                } else if q == 1.0 {
14406                                    quantiles.push(Expression::number(1));
14407                                } else {
14408                                    quantiles.push(Expression::Literal(Box::new(
14409                                        crate::expressions::Literal::Number(format!("{}", q)),
14410                                    )));
14411                                }
14412                            }
14413
14414                            let array_expr =
14415                                Expression::Array(Box::new(crate::expressions::Array {
14416                                    expressions: quantiles,
14417                                }));
14418
14419                            // Preserve DISTINCT modifier
14420                            let mut new_func = Function::new(
14421                                "APPROX_QUANTILE".to_string(),
14422                                vec![x_expr, array_expr],
14423                            );
14424                            new_func.distinct = agg.distinct;
14425                            Ok(Expression::Function(Box::new(new_func)))
14426                        } else {
14427                            Ok(Expression::AggregateFunction(agg))
14428                        }
14429                    } else {
14430                        Ok(e)
14431                    }
14432                }
14433
14434                Action::GenericFunctionNormalize => {
14435                    // Helper closure to convert ARBITRARY to target-specific function
14436                    fn convert_arbitrary(arg: Expression, target: DialectType) -> Expression {
14437                        let name = match target {
14438                            DialectType::ClickHouse => "any",
14439                            DialectType::TSQL | DialectType::SQLite => "MAX",
14440                            DialectType::Hive => "FIRST",
14441                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
14442                                "ARBITRARY"
14443                            }
14444                            _ => "ANY_VALUE",
14445                        };
14446                        Expression::Function(Box::new(Function::new(name.to_string(), vec![arg])))
14447                    }
14448
14449                    if let Expression::Function(f) = e {
14450                        let name = f.name.to_ascii_uppercase();
14451                        match name.as_str() {
14452                            "ARBITRARY" if f.args.len() == 1 => {
14453                                let arg = f.args.into_iter().next().unwrap();
14454                                Ok(convert_arbitrary(arg, target))
14455                            }
14456                            "TO_NUMBER" if f.args.len() == 1 => {
14457                                let arg = f.args.into_iter().next().unwrap();
14458                                match target {
14459                                    DialectType::Oracle | DialectType::Snowflake => {
14460                                        Ok(Expression::Function(Box::new(Function::new(
14461                                            "TO_NUMBER".to_string(),
14462                                            vec![arg],
14463                                        ))))
14464                                    }
14465                                    _ => Ok(Expression::Cast(Box::new(crate::expressions::Cast {
14466                                        this: arg,
14467                                        to: crate::expressions::DataType::Double {
14468                                            precision: None,
14469                                            scale: None,
14470                                        },
14471                                        double_colon_syntax: false,
14472                                        trailing_comments: Vec::new(),
14473                                        format: None,
14474                                        default: None,
14475                                        inferred_type: None,
14476                                    }))),
14477                                }
14478                            }
14479                            "AGGREGATE" if f.args.len() >= 3 => match target {
14480                                DialectType::DuckDB
14481                                | DialectType::Hive
14482                                | DialectType::Presto
14483                                | DialectType::Trino => Ok(Expression::Function(Box::new(
14484                                    Function::new("REDUCE".to_string(), f.args),
14485                                ))),
14486                                _ => Ok(Expression::Function(f)),
14487                            },
14488                            // REGEXP_MATCHES(x, y) -> RegexpLike for most targets, keep as-is for DuckDB
14489                            "REGEXP_MATCHES" if f.args.len() >= 2 => {
14490                                if matches!(target, DialectType::DuckDB) {
14491                                    Ok(Expression::Function(f))
14492                                } else {
14493                                    let mut args = f.args;
14494                                    let this = args.remove(0);
14495                                    let pattern = args.remove(0);
14496                                    let flags = if args.is_empty() {
14497                                        None
14498                                    } else {
14499                                        Some(args.remove(0))
14500                                    };
14501                                    Ok(Expression::RegexpLike(Box::new(
14502                                        crate::expressions::RegexpFunc {
14503                                            this,
14504                                            pattern,
14505                                            flags,
14506                                        },
14507                                    )))
14508                                }
14509                            }
14510                            // REGEXP_FULL_MATCH (Hive REGEXP) -> RegexpLike
14511                            "REGEXP_FULL_MATCH" if f.args.len() >= 2 => {
14512                                if matches!(target, DialectType::DuckDB) {
14513                                    Ok(Expression::Function(f))
14514                                } else {
14515                                    let mut args = f.args;
14516                                    let this = args.remove(0);
14517                                    let pattern = args.remove(0);
14518                                    let flags = if args.is_empty() {
14519                                        None
14520                                    } else {
14521                                        Some(args.remove(0))
14522                                    };
14523                                    Ok(Expression::RegexpLike(Box::new(
14524                                        crate::expressions::RegexpFunc {
14525                                            this,
14526                                            pattern,
14527                                            flags,
14528                                        },
14529                                    )))
14530                                }
14531                            }
14532                            // STRUCT_EXTRACT(x, 'field') -> x.field (StructExtract expression)
14533                            "STRUCT_EXTRACT" if f.args.len() == 2 => {
14534                                let mut args = f.args;
14535                                let this = args.remove(0);
14536                                let field_expr = args.remove(0);
14537                                // Extract string literal to get field name
14538                                let field_name = match &field_expr {
14539                                    Expression::Literal(lit)
14540                                        if matches!(
14541                                            lit.as_ref(),
14542                                            crate::expressions::Literal::String(_)
14543                                        ) =>
14544                                    {
14545                                        let crate::expressions::Literal::String(s) = lit.as_ref()
14546                                        else {
14547                                            unreachable!()
14548                                        };
14549                                        s.clone()
14550                                    }
14551                                    Expression::Identifier(id) => id.name.clone(),
14552                                    _ => {
14553                                        return Ok(Expression::Function(Box::new(Function::new(
14554                                            "STRUCT_EXTRACT".to_string(),
14555                                            vec![this, field_expr],
14556                                        ))))
14557                                    }
14558                                };
14559                                Ok(Expression::StructExtract(Box::new(
14560                                    crate::expressions::StructExtractFunc {
14561                                        this,
14562                                        field: crate::expressions::Identifier::new(field_name),
14563                                    },
14564                                )))
14565                            }
14566                            // LIST_FILTER([4,5,6], x -> x > 4) -> FILTER(ARRAY(4,5,6), x -> x > 4)
14567                            "LIST_FILTER" if f.args.len() == 2 => {
14568                                let name = match target {
14569                                    DialectType::DuckDB => "LIST_FILTER",
14570                                    _ => "FILTER",
14571                                };
14572                                Ok(Expression::Function(Box::new(Function::new(
14573                                    name.to_string(),
14574                                    f.args,
14575                                ))))
14576                            }
14577                            // LIST_TRANSFORM(x, y -> y + 1) -> TRANSFORM(x, y -> y + 1)
14578                            "LIST_TRANSFORM" if f.args.len() == 2 => {
14579                                let name = match target {
14580                                    DialectType::DuckDB => "LIST_TRANSFORM",
14581                                    _ => "TRANSFORM",
14582                                };
14583                                Ok(Expression::Function(Box::new(Function::new(
14584                                    name.to_string(),
14585                                    f.args,
14586                                ))))
14587                            }
14588                            // LIST_SORT(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, SORT_ARRAY(x) for others
14589                            "LIST_SORT" if f.args.len() >= 1 => {
14590                                let name = match target {
14591                                    DialectType::DuckDB => "LIST_SORT",
14592                                    DialectType::Presto | DialectType::Trino => "ARRAY_SORT",
14593                                    _ => "SORT_ARRAY",
14594                                };
14595                                Ok(Expression::Function(Box::new(Function::new(
14596                                    name.to_string(),
14597                                    f.args,
14598                                ))))
14599                            }
14600                            // LIST_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
14601                            "LIST_REVERSE_SORT" if f.args.len() >= 1 => {
14602                                match target {
14603                                    DialectType::DuckDB => Ok(Expression::Function(Box::new(
14604                                        Function::new("ARRAY_REVERSE_SORT".to_string(), f.args),
14605                                    ))),
14606                                    DialectType::Spark
14607                                    | DialectType::Databricks
14608                                    | DialectType::Hive => {
14609                                        let mut args = f.args;
14610                                        args.push(Expression::Identifier(
14611                                            crate::expressions::Identifier::new("FALSE"),
14612                                        ));
14613                                        Ok(Expression::Function(Box::new(Function::new(
14614                                            "SORT_ARRAY".to_string(),
14615                                            args,
14616                                        ))))
14617                                    }
14618                                    DialectType::Presto
14619                                    | DialectType::Trino
14620                                    | DialectType::Athena => {
14621                                        // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
14622                                        let arr = f.args.into_iter().next().unwrap();
14623                                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
14624                                            parameters: vec![
14625                                                crate::expressions::Identifier::new("a"),
14626                                                crate::expressions::Identifier::new("b"),
14627                                            ],
14628                                            body: Expression::Case(Box::new(Case {
14629                                                operand: None,
14630                                                whens: vec![
14631                                                    (
14632                                                        Expression::Lt(Box::new(BinaryOp::new(
14633                                                            Expression::Identifier(crate::expressions::Identifier::new("a")),
14634                                                            Expression::Identifier(crate::expressions::Identifier::new("b")),
14635                                                        ))),
14636                                                        Expression::number(1),
14637                                                    ),
14638                                                    (
14639                                                        Expression::Gt(Box::new(BinaryOp::new(
14640                                                            Expression::Identifier(crate::expressions::Identifier::new("a")),
14641                                                            Expression::Identifier(crate::expressions::Identifier::new("b")),
14642                                                        ))),
14643                                                        Expression::Literal(Box::new(Literal::Number("-1".to_string()))),
14644                                                    ),
14645                                                ],
14646                                                else_: Some(Expression::number(0)),
14647                                                comments: Vec::new(),
14648                                                inferred_type: None,
14649                                            })),
14650                                            colon: false,
14651                                            parameter_types: Vec::new(),
14652                                        }));
14653                                        Ok(Expression::Function(Box::new(Function::new(
14654                                            "ARRAY_SORT".to_string(),
14655                                            vec![arr, lambda],
14656                                        ))))
14657                                    }
14658                                    _ => Ok(Expression::Function(Box::new(Function::new(
14659                                        "LIST_REVERSE_SORT".to_string(),
14660                                        f.args,
14661                                    )))),
14662                                }
14663                            }
14664                            // SPLIT_TO_ARRAY(x) with 1 arg -> add default ',' separator and rename
14665                            "SPLIT_TO_ARRAY" if f.args.len() == 1 => {
14666                                let mut args = f.args;
14667                                args.push(Expression::string(","));
14668                                let name = match target {
14669                                    DialectType::DuckDB => "STR_SPLIT",
14670                                    DialectType::Presto | DialectType::Trino => "SPLIT",
14671                                    DialectType::Spark
14672                                    | DialectType::Databricks
14673                                    | DialectType::Hive => "SPLIT",
14674                                    DialectType::PostgreSQL => "STRING_TO_ARRAY",
14675                                    DialectType::Redshift => "SPLIT_TO_ARRAY",
14676                                    _ => "SPLIT",
14677                                };
14678                                Ok(Expression::Function(Box::new(Function::new(
14679                                    name.to_string(),
14680                                    args,
14681                                ))))
14682                            }
14683                            // SPLIT_TO_ARRAY(x, sep) with 2 args -> rename based on target
14684                            "SPLIT_TO_ARRAY" if f.args.len() == 2 => {
14685                                let name = match target {
14686                                    DialectType::DuckDB => "STR_SPLIT",
14687                                    DialectType::Presto | DialectType::Trino => "SPLIT",
14688                                    DialectType::Spark
14689                                    | DialectType::Databricks
14690                                    | DialectType::Hive => "SPLIT",
14691                                    DialectType::PostgreSQL => "STRING_TO_ARRAY",
14692                                    DialectType::Redshift => "SPLIT_TO_ARRAY",
14693                                    _ => "SPLIT",
14694                                };
14695                                Ok(Expression::Function(Box::new(Function::new(
14696                                    name.to_string(),
14697                                    f.args,
14698                                ))))
14699                            }
14700                            // STRING_TO_ARRAY/STR_SPLIT -> target-specific split function
14701                            "STRING_TO_ARRAY" | "STR_SPLIT" if f.args.len() >= 2 => {
14702                                let name = match target {
14703                                    DialectType::DuckDB => "STR_SPLIT",
14704                                    DialectType::Presto | DialectType::Trino => "SPLIT",
14705                                    DialectType::Spark
14706                                    | DialectType::Databricks
14707                                    | DialectType::Hive => "SPLIT",
14708                                    DialectType::Doris | DialectType::StarRocks => {
14709                                        "SPLIT_BY_STRING"
14710                                    }
14711                                    DialectType::TSQL | DialectType::Fabric
14712                                        if name == "STRING_TO_ARRAY" =>
14713                                    {
14714                                        "STRING_TO_ARRAY"
14715                                    }
14716                                    DialectType::PostgreSQL | DialectType::Redshift => {
14717                                        "STRING_TO_ARRAY"
14718                                    }
14719                                    _ => "SPLIT",
14720                                };
14721                                // For Spark/Hive, SPLIT uses regex - need to escape literal with \Q...\E
14722                                if matches!(
14723                                    target,
14724                                    DialectType::Spark
14725                                        | DialectType::Databricks
14726                                        | DialectType::Hive
14727                                ) {
14728                                    let mut args = f.args;
14729                                    let x = args.remove(0);
14730                                    let sep = args.remove(0);
14731                                    // Wrap separator in CONCAT('\\Q', sep, '\\E')
14732                                    let escaped_sep =
14733                                        Expression::Function(Box::new(Function::new(
14734                                            "CONCAT".to_string(),
14735                                            vec![
14736                                                Expression::string("\\Q"),
14737                                                sep,
14738                                                Expression::string("\\E"),
14739                                            ],
14740                                        )));
14741                                    Ok(Expression::Function(Box::new(Function::new(
14742                                        name.to_string(),
14743                                        vec![x, escaped_sep],
14744                                    ))))
14745                                } else {
14746                                    Ok(Expression::Function(Box::new(Function::new(
14747                                        name.to_string(),
14748                                        f.args,
14749                                    ))))
14750                                }
14751                            }
14752                            // STR_SPLIT_REGEX(x, 'a') / REGEXP_SPLIT(x, 'a') -> target-specific regex split
14753                            "STR_SPLIT_REGEX" | "REGEXP_SPLIT" if f.args.len() == 2 => {
14754                                let name = match target {
14755                                    DialectType::DuckDB => "STR_SPLIT_REGEX",
14756                                    DialectType::Presto | DialectType::Trino => "REGEXP_SPLIT",
14757                                    DialectType::Spark
14758                                    | DialectType::Databricks
14759                                    | DialectType::Hive => "SPLIT",
14760                                    _ => "REGEXP_SPLIT",
14761                                };
14762                                Ok(Expression::Function(Box::new(Function::new(
14763                                    name.to_string(),
14764                                    f.args,
14765                                ))))
14766                            }
14767                            // SPLIT(str, delim) from Snowflake -> DuckDB with CASE wrapper
14768                            "SPLIT"
14769                                if f.args.len() == 2
14770                                    && matches!(source, DialectType::Snowflake)
14771                                    && matches!(target, DialectType::DuckDB) =>
14772                            {
14773                                let mut args = f.args;
14774                                let str_arg = args.remove(0);
14775                                let delim_arg = args.remove(0);
14776
14777                                // STR_SPLIT(str, delim) as the base
14778                                let base_func = Expression::Function(Box::new(Function::new(
14779                                    "STR_SPLIT".to_string(),
14780                                    vec![str_arg.clone(), delim_arg.clone()],
14781                                )));
14782
14783                                // [str] - array with single element
14784                                let array_with_input =
14785                                    Expression::Array(Box::new(crate::expressions::Array {
14786                                        expressions: vec![str_arg],
14787                                    }));
14788
14789                                // CASE
14790                                //   WHEN delim IS NULL THEN NULL
14791                                //   WHEN delim = '' THEN [str]
14792                                //   ELSE STR_SPLIT(str, delim)
14793                                // END
14794                                Ok(Expression::Case(Box::new(Case {
14795                                    operand: None,
14796                                    whens: vec![
14797                                        (
14798                                            Expression::Is(Box::new(BinaryOp {
14799                                                left: delim_arg.clone(),
14800                                                right: Expression::Null(Null),
14801                                                left_comments: vec![],
14802                                                operator_comments: vec![],
14803                                                trailing_comments: vec![],
14804                                                inferred_type: None,
14805                                            })),
14806                                            Expression::Null(Null),
14807                                        ),
14808                                        (
14809                                            Expression::Eq(Box::new(BinaryOp {
14810                                                left: delim_arg,
14811                                                right: Expression::string(""),
14812                                                left_comments: vec![],
14813                                                operator_comments: vec![],
14814                                                trailing_comments: vec![],
14815                                                inferred_type: None,
14816                                            })),
14817                                            array_with_input,
14818                                        ),
14819                                    ],
14820                                    else_: Some(base_func),
14821                                    comments: vec![],
14822                                    inferred_type: None,
14823                                })))
14824                            }
14825                            // SPLIT(x, sep) from Presto/StarRocks/Doris -> target-specific split with regex escaping for Hive/Spark
14826                            "SPLIT"
14827                                if f.args.len() == 2
14828                                    && matches!(
14829                                        source,
14830                                        DialectType::Presto
14831                                            | DialectType::Trino
14832                                            | DialectType::Athena
14833                                            | DialectType::StarRocks
14834                                            | DialectType::Doris
14835                                    )
14836                                    && matches!(
14837                                        target,
14838                                        DialectType::Spark
14839                                            | DialectType::Databricks
14840                                            | DialectType::Hive
14841                                    ) =>
14842                            {
14843                                // Presto/StarRocks SPLIT is literal, Hive/Spark SPLIT is regex
14844                                let mut args = f.args;
14845                                let x = args.remove(0);
14846                                let sep = args.remove(0);
14847                                let escaped_sep = Expression::Function(Box::new(Function::new(
14848                                    "CONCAT".to_string(),
14849                                    vec![Expression::string("\\Q"), sep, Expression::string("\\E")],
14850                                )));
14851                                Ok(Expression::Function(Box::new(Function::new(
14852                                    "SPLIT".to_string(),
14853                                    vec![x, escaped_sep],
14854                                ))))
14855                            }
14856                            // SUBSTRINGINDEX -> SUBSTRING_INDEX (ClickHouse camelCase to standard)
14857                            // For ClickHouse target, preserve original name to maintain camelCase
14858                            "SUBSTRINGINDEX" => {
14859                                let name = if matches!(target, DialectType::ClickHouse) {
14860                                    f.name.clone()
14861                                } else {
14862                                    "SUBSTRING_INDEX".to_string()
14863                                };
14864                                Ok(Expression::Function(Box::new(Function::new(name, f.args))))
14865                            }
14866                            // ARRAY_LENGTH/SIZE/CARDINALITY -> target-specific array length function
14867                            "ARRAY_LENGTH" | "SIZE" | "CARDINALITY" => {
14868                                // DuckDB source CARDINALITY -> DuckDB target: keep as CARDINALITY (used for maps)
14869                                if name == "CARDINALITY"
14870                                    && matches!(source, DialectType::DuckDB)
14871                                    && matches!(target, DialectType::DuckDB)
14872                                {
14873                                    return Ok(Expression::Function(f));
14874                                }
14875                                // Get the array argument (first arg, drop dimension args)
14876                                let mut args = f.args;
14877                                let arr = if args.is_empty() {
14878                                    return Ok(Expression::Function(Box::new(Function::new(
14879                                        name.to_string(),
14880                                        args,
14881                                    ))));
14882                                } else {
14883                                    args.remove(0)
14884                                };
14885                                let name =
14886                                    match target {
14887                                        DialectType::Spark
14888                                        | DialectType::Databricks
14889                                        | DialectType::Hive => "SIZE",
14890                                        DialectType::Presto | DialectType::Trino => "CARDINALITY",
14891                                        DialectType::BigQuery => "ARRAY_LENGTH",
14892                                        DialectType::DuckDB => {
14893                                            // DuckDB: use ARRAY_LENGTH with all args
14894                                            let mut all_args = vec![arr];
14895                                            all_args.extend(args);
14896                                            return Ok(Expression::Function(Box::new(
14897                                                Function::new("ARRAY_LENGTH".to_string(), all_args),
14898                                            )));
14899                                        }
14900                                        DialectType::PostgreSQL
14901                                        | DialectType::Redshift
14902                                        | DialectType::TSQL
14903                                        | DialectType::Fabric => {
14904                                            // Keep ARRAY_LENGTH with dimension args when there is
14905                                            // no safe target-specific array representation.
14906                                            let mut all_args = vec![arr];
14907                                            all_args.extend(args);
14908                                            return Ok(Expression::Function(Box::new(
14909                                                Function::new("ARRAY_LENGTH".to_string(), all_args),
14910                                            )));
14911                                        }
14912                                        DialectType::ClickHouse => "LENGTH",
14913                                        _ => "ARRAY_LENGTH",
14914                                    };
14915                                Ok(Expression::Function(Box::new(Function::new(
14916                                    name.to_string(),
14917                                    vec![arr],
14918                                ))))
14919                            }
14920                            // TO_VARIANT(x) -> CAST(x AS VARIANT) for DuckDB
14921                            "TO_VARIANT" if f.args.len() == 1 => match target {
14922                                DialectType::DuckDB => {
14923                                    let arg = f.args.into_iter().next().unwrap();
14924                                    Ok(Expression::Cast(Box::new(Cast {
14925                                        this: arg,
14926                                        to: DataType::Custom {
14927                                            name: "VARIANT".to_string(),
14928                                        },
14929                                        double_colon_syntax: false,
14930                                        trailing_comments: Vec::new(),
14931                                        format: None,
14932                                        default: None,
14933                                        inferred_type: None,
14934                                    })))
14935                                }
14936                                _ => Ok(Expression::Function(f)),
14937                            },
14938                            // JSON_GROUP_ARRAY(x) -> JSON_AGG(x) for PostgreSQL
14939                            "JSON_GROUP_ARRAY" if f.args.len() == 1 => match target {
14940                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
14941                                    Function::new("JSON_AGG".to_string(), f.args),
14942                                ))),
14943                                _ => Ok(Expression::Function(f)),
14944                            },
14945                            // JSON_GROUP_OBJECT(key, value) -> JSON_OBJECT_AGG(key, value) for PostgreSQL
14946                            "JSON_GROUP_OBJECT" if f.args.len() == 2 => match target {
14947                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
14948                                    Function::new("JSON_OBJECT_AGG".to_string(), f.args),
14949                                ))),
14950                                _ => Ok(Expression::Function(f)),
14951                            },
14952                            // UNICODE(x) -> target-specific codepoint function
14953                            "UNICODE" if f.args.len() == 1 => {
14954                                match target {
14955                                    DialectType::SQLite | DialectType::DuckDB => {
14956                                        Ok(Expression::Function(Box::new(Function::new(
14957                                            "UNICODE".to_string(),
14958                                            f.args,
14959                                        ))))
14960                                    }
14961                                    DialectType::Oracle => {
14962                                        // ASCII(UNISTR(x))
14963                                        let inner = Expression::Function(Box::new(Function::new(
14964                                            "UNISTR".to_string(),
14965                                            f.args,
14966                                        )));
14967                                        Ok(Expression::Function(Box::new(Function::new(
14968                                            "ASCII".to_string(),
14969                                            vec![inner],
14970                                        ))))
14971                                    }
14972                                    DialectType::MySQL => {
14973                                        // ORD(CONVERT(x USING utf32))
14974                                        let arg = f.args.into_iter().next().unwrap();
14975                                        let convert_expr = Expression::ConvertToCharset(Box::new(
14976                                            crate::expressions::ConvertToCharset {
14977                                                this: Box::new(arg),
14978                                                dest: Some(Box::new(Expression::Identifier(
14979                                                    crate::expressions::Identifier::new("utf32"),
14980                                                ))),
14981                                                source: None,
14982                                            },
14983                                        ));
14984                                        Ok(Expression::Function(Box::new(Function::new(
14985                                            "ORD".to_string(),
14986                                            vec![convert_expr],
14987                                        ))))
14988                                    }
14989                                    _ => Ok(Expression::Function(Box::new(Function::new(
14990                                        "ASCII".to_string(),
14991                                        f.args,
14992                                    )))),
14993                                }
14994                            }
14995                            // XOR(a, b, ...) -> a XOR b XOR ... for MySQL, BITWISE_XOR for Presto/Trino, # for PostgreSQL, ^ for BigQuery
14996                            "XOR" if f.args.len() >= 2 => {
14997                                match target {
14998                                    DialectType::ClickHouse => {
14999                                        // ClickHouse: keep as xor() function with lowercase name
15000                                        Ok(Expression::Function(Box::new(Function::new(
15001                                            "xor".to_string(),
15002                                            f.args,
15003                                        ))))
15004                                    }
15005                                    DialectType::Presto | DialectType::Trino => {
15006                                        if f.args.len() == 2 {
15007                                            Ok(Expression::Function(Box::new(Function::new(
15008                                                "BITWISE_XOR".to_string(),
15009                                                f.args,
15010                                            ))))
15011                                        } else {
15012                                            // Nest: BITWISE_XOR(BITWISE_XOR(a, b), c)
15013                                            let mut args = f.args;
15014                                            let first = args.remove(0);
15015                                            let second = args.remove(0);
15016                                            let mut result =
15017                                                Expression::Function(Box::new(Function::new(
15018                                                    "BITWISE_XOR".to_string(),
15019                                                    vec![first, second],
15020                                                )));
15021                                            for arg in args {
15022                                                result =
15023                                                    Expression::Function(Box::new(Function::new(
15024                                                        "BITWISE_XOR".to_string(),
15025                                                        vec![result, arg],
15026                                                    )));
15027                                            }
15028                                            Ok(result)
15029                                        }
15030                                    }
15031                                    DialectType::MySQL
15032                                    | DialectType::SingleStore
15033                                    | DialectType::Doris
15034                                    | DialectType::StarRocks => {
15035                                        // Convert XOR(a, b, c) -> Expression::Xor with expressions list
15036                                        let args = f.args;
15037                                        Ok(Expression::Xor(Box::new(crate::expressions::Xor {
15038                                            this: None,
15039                                            expression: None,
15040                                            expressions: args,
15041                                        })))
15042                                    }
15043                                    DialectType::PostgreSQL | DialectType::Redshift => {
15044                                        // PostgreSQL: a # b (hash operator for XOR)
15045                                        let mut args = f.args;
15046                                        let first = args.remove(0);
15047                                        let second = args.remove(0);
15048                                        let mut result = Expression::BitwiseXor(Box::new(
15049                                            BinaryOp::new(first, second),
15050                                        ));
15051                                        for arg in args {
15052                                            result = Expression::BitwiseXor(Box::new(
15053                                                BinaryOp::new(result, arg),
15054                                            ));
15055                                        }
15056                                        Ok(result)
15057                                    }
15058                                    DialectType::DuckDB => {
15059                                        // DuckDB: keep as XOR function (DuckDB ^ is Power, not XOR)
15060                                        Ok(Expression::Function(Box::new(Function::new(
15061                                            "XOR".to_string(),
15062                                            f.args,
15063                                        ))))
15064                                    }
15065                                    DialectType::BigQuery => {
15066                                        // BigQuery: a ^ b (caret operator for XOR)
15067                                        let mut args = f.args;
15068                                        let first = args.remove(0);
15069                                        let second = args.remove(0);
15070                                        let mut result = Expression::BitwiseXor(Box::new(
15071                                            BinaryOp::new(first, second),
15072                                        ));
15073                                        for arg in args {
15074                                            result = Expression::BitwiseXor(Box::new(
15075                                                BinaryOp::new(result, arg),
15076                                            ));
15077                                        }
15078                                        Ok(result)
15079                                    }
15080                                    _ => Ok(Expression::Function(Box::new(Function::new(
15081                                        "XOR".to_string(),
15082                                        f.args,
15083                                    )))),
15084                                }
15085                            }
15086                            // ARRAY_REVERSE_SORT(x) -> SORT_ARRAY(x, FALSE) for Spark/Hive, ARRAY_SORT(x, lambda) for Presto
15087                            "ARRAY_REVERSE_SORT" if f.args.len() >= 1 => {
15088                                match target {
15089                                    DialectType::Spark
15090                                    | DialectType::Databricks
15091                                    | DialectType::Hive => {
15092                                        let mut args = f.args;
15093                                        args.push(Expression::Identifier(
15094                                            crate::expressions::Identifier::new("FALSE"),
15095                                        ));
15096                                        Ok(Expression::Function(Box::new(Function::new(
15097                                            "SORT_ARRAY".to_string(),
15098                                            args,
15099                                        ))))
15100                                    }
15101                                    DialectType::Presto
15102                                    | DialectType::Trino
15103                                    | DialectType::Athena => {
15104                                        // ARRAY_SORT(x, (a, b) -> CASE WHEN a < b THEN 1 WHEN a > b THEN -1 ELSE 0 END)
15105                                        let arr = f.args.into_iter().next().unwrap();
15106                                        let lambda = Expression::Lambda(Box::new(
15107                                            crate::expressions::LambdaExpr {
15108                                                parameters: vec![
15109                                                    Identifier::new("a"),
15110                                                    Identifier::new("b"),
15111                                                ],
15112                                                colon: false,
15113                                                parameter_types: Vec::new(),
15114                                                body: Expression::Case(Box::new(Case {
15115                                                    operand: None,
15116                                                    whens: vec![
15117                                                        (
15118                                                            Expression::Lt(Box::new(
15119                                                                BinaryOp::new(
15120                                                                    Expression::Identifier(
15121                                                                        Identifier::new("a"),
15122                                                                    ),
15123                                                                    Expression::Identifier(
15124                                                                        Identifier::new("b"),
15125                                                                    ),
15126                                                                ),
15127                                                            )),
15128                                                            Expression::number(1),
15129                                                        ),
15130                                                        (
15131                                                            Expression::Gt(Box::new(
15132                                                                BinaryOp::new(
15133                                                                    Expression::Identifier(
15134                                                                        Identifier::new("a"),
15135                                                                    ),
15136                                                                    Expression::Identifier(
15137                                                                        Identifier::new("b"),
15138                                                                    ),
15139                                                                ),
15140                                                            )),
15141                                                            Expression::Neg(Box::new(
15142                                                                crate::expressions::UnaryOp {
15143                                                                    this: Expression::number(1),
15144                                                                    inferred_type: None,
15145                                                                },
15146                                                            )),
15147                                                        ),
15148                                                    ],
15149                                                    else_: Some(Expression::number(0)),
15150                                                    comments: Vec::new(),
15151                                                    inferred_type: None,
15152                                                })),
15153                                            },
15154                                        ));
15155                                        Ok(Expression::Function(Box::new(Function::new(
15156                                            "ARRAY_SORT".to_string(),
15157                                            vec![arr, lambda],
15158                                        ))))
15159                                    }
15160                                    _ => Ok(Expression::Function(Box::new(Function::new(
15161                                        "ARRAY_REVERSE_SORT".to_string(),
15162                                        f.args,
15163                                    )))),
15164                                }
15165                            }
15166                            // ENCODE(x) -> ENCODE(x, 'utf-8') for Spark/Hive, TO_UTF8(x) for Presto
15167                            "ENCODE" if f.args.len() == 1 => match target {
15168                                DialectType::Spark
15169                                | DialectType::Databricks
15170                                | DialectType::Hive => {
15171                                    let mut args = f.args;
15172                                    args.push(Expression::string("utf-8"));
15173                                    Ok(Expression::Function(Box::new(Function::new(
15174                                        "ENCODE".to_string(),
15175                                        args,
15176                                    ))))
15177                                }
15178                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
15179                                    Ok(Expression::Function(Box::new(Function::new(
15180                                        "TO_UTF8".to_string(),
15181                                        f.args,
15182                                    ))))
15183                                }
15184                                _ => Ok(Expression::Function(Box::new(Function::new(
15185                                    "ENCODE".to_string(),
15186                                    f.args,
15187                                )))),
15188                            },
15189                            // DECODE(x) -> DECODE(x, 'utf-8') for Spark/Hive, FROM_UTF8(x) for Presto
15190                            "DECODE" if f.args.len() == 1 => match target {
15191                                DialectType::Spark
15192                                | DialectType::Databricks
15193                                | DialectType::Hive => {
15194                                    let mut args = f.args;
15195                                    args.push(Expression::string("utf-8"));
15196                                    Ok(Expression::Function(Box::new(Function::new(
15197                                        "DECODE".to_string(),
15198                                        args,
15199                                    ))))
15200                                }
15201                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
15202                                    Ok(Expression::Function(Box::new(Function::new(
15203                                        "FROM_UTF8".to_string(),
15204                                        f.args,
15205                                    ))))
15206                                }
15207                                _ => Ok(Expression::Function(Box::new(Function::new(
15208                                    "DECODE".to_string(),
15209                                    f.args,
15210                                )))),
15211                            },
15212                            // QUANTILE(x, p) -> PERCENTILE(x, p) for Spark/Hive
15213                            "QUANTILE" if f.args.len() == 2 => {
15214                                let name = match target {
15215                                    DialectType::Spark
15216                                    | DialectType::Databricks
15217                                    | DialectType::Hive => "PERCENTILE",
15218                                    DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
15219                                    DialectType::BigQuery => "PERCENTILE_CONT",
15220                                    _ => "QUANTILE",
15221                                };
15222                                Ok(Expression::Function(Box::new(Function::new(
15223                                    name.to_string(),
15224                                    f.args,
15225                                ))))
15226                            }
15227                            // QUANTILE_CONT(x, q) -> PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
15228                            "QUANTILE_CONT" if f.args.len() == 2 => {
15229                                let mut args = f.args;
15230                                let column = args.remove(0);
15231                                let quantile = args.remove(0);
15232                                match target {
15233                                    DialectType::DuckDB => {
15234                                        Ok(Expression::Function(Box::new(Function::new(
15235                                            "QUANTILE_CONT".to_string(),
15236                                            vec![column, quantile],
15237                                        ))))
15238                                    }
15239                                    DialectType::PostgreSQL
15240                                    | DialectType::Redshift
15241                                    | DialectType::Snowflake => {
15242                                        // PERCENTILE_CONT(q) WITHIN GROUP (ORDER BY x)
15243                                        let inner = Expression::PercentileCont(Box::new(
15244                                            crate::expressions::PercentileFunc {
15245                                                this: column.clone(),
15246                                                percentile: quantile,
15247                                                order_by: None,
15248                                                filter: None,
15249                                            },
15250                                        ));
15251                                        Ok(Expression::WithinGroup(Box::new(
15252                                            crate::expressions::WithinGroup {
15253                                                this: inner,
15254                                                order_by: vec![crate::expressions::Ordered {
15255                                                    this: column,
15256                                                    desc: false,
15257                                                    nulls_first: None,
15258                                                    explicit_asc: false,
15259                                                    with_fill: None,
15260                                                }],
15261                                            },
15262                                        )))
15263                                    }
15264                                    _ => Ok(Expression::Function(Box::new(Function::new(
15265                                        "QUANTILE_CONT".to_string(),
15266                                        vec![column, quantile],
15267                                    )))),
15268                                }
15269                            }
15270                            // QUANTILE_DISC(x, q) -> PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x) for PostgreSQL/Snowflake
15271                            "QUANTILE_DISC" if f.args.len() == 2 => {
15272                                let mut args = f.args;
15273                                let column = args.remove(0);
15274                                let quantile = args.remove(0);
15275                                match target {
15276                                    DialectType::DuckDB => {
15277                                        Ok(Expression::Function(Box::new(Function::new(
15278                                            "QUANTILE_DISC".to_string(),
15279                                            vec![column, quantile],
15280                                        ))))
15281                                    }
15282                                    DialectType::PostgreSQL
15283                                    | DialectType::Redshift
15284                                    | DialectType::Snowflake => {
15285                                        // PERCENTILE_DISC(q) WITHIN GROUP (ORDER BY x)
15286                                        let inner = Expression::PercentileDisc(Box::new(
15287                                            crate::expressions::PercentileFunc {
15288                                                this: column.clone(),
15289                                                percentile: quantile,
15290                                                order_by: None,
15291                                                filter: None,
15292                                            },
15293                                        ));
15294                                        Ok(Expression::WithinGroup(Box::new(
15295                                            crate::expressions::WithinGroup {
15296                                                this: inner,
15297                                                order_by: vec![crate::expressions::Ordered {
15298                                                    this: column,
15299                                                    desc: false,
15300                                                    nulls_first: None,
15301                                                    explicit_asc: false,
15302                                                    with_fill: None,
15303                                                }],
15304                                            },
15305                                        )))
15306                                    }
15307                                    _ => Ok(Expression::Function(Box::new(Function::new(
15308                                        "QUANTILE_DISC".to_string(),
15309                                        vec![column, quantile],
15310                                    )))),
15311                                }
15312                            }
15313                            // PERCENTILE_APPROX(x, p) / APPROX_PERCENTILE(x, p) -> target-specific
15314                            "PERCENTILE_APPROX" | "APPROX_PERCENTILE" if f.args.len() >= 2 => {
15315                                let name = match target {
15316                                    DialectType::Presto
15317                                    | DialectType::Trino
15318                                    | DialectType::Athena => "APPROX_PERCENTILE",
15319                                    DialectType::Spark
15320                                    | DialectType::Databricks
15321                                    | DialectType::Hive => "PERCENTILE_APPROX",
15322                                    DialectType::DuckDB => "APPROX_QUANTILE",
15323                                    DialectType::PostgreSQL | DialectType::Redshift => {
15324                                        "PERCENTILE_CONT"
15325                                    }
15326                                    _ => &f.name,
15327                                };
15328                                Ok(Expression::Function(Box::new(Function::new(
15329                                    name.to_string(),
15330                                    f.args,
15331                                ))))
15332                            }
15333                            // EPOCH(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
15334                            "EPOCH" if f.args.len() == 1 => {
15335                                let name = match target {
15336                                    DialectType::Spark
15337                                    | DialectType::Databricks
15338                                    | DialectType::Hive => "UNIX_TIMESTAMP",
15339                                    DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
15340                                    _ => "EPOCH",
15341                                };
15342                                Ok(Expression::Function(Box::new(Function::new(
15343                                    name.to_string(),
15344                                    f.args,
15345                                ))))
15346                            }
15347                            // EPOCH_MS(x) -> target-specific epoch milliseconds conversion
15348                            "EPOCH_MS" if f.args.len() == 1 => {
15349                                match target {
15350                                    DialectType::Spark | DialectType::Databricks => {
15351                                        Ok(Expression::Function(Box::new(Function::new(
15352                                            "TIMESTAMP_MILLIS".to_string(),
15353                                            f.args,
15354                                        ))))
15355                                    }
15356                                    DialectType::Hive => {
15357                                        // Hive: FROM_UNIXTIME(x / 1000)
15358                                        let arg = f.args.into_iter().next().unwrap();
15359                                        let div_expr = Expression::Div(Box::new(
15360                                            crate::expressions::BinaryOp::new(
15361                                                arg,
15362                                                Expression::number(1000),
15363                                            ),
15364                                        ));
15365                                        Ok(Expression::Function(Box::new(Function::new(
15366                                            "FROM_UNIXTIME".to_string(),
15367                                            vec![div_expr],
15368                                        ))))
15369                                    }
15370                                    DialectType::Presto | DialectType::Trino => {
15371                                        Ok(Expression::Function(Box::new(Function::new(
15372                                            "FROM_UNIXTIME".to_string(),
15373                                            vec![Expression::Div(Box::new(
15374                                                crate::expressions::BinaryOp::new(
15375                                                    f.args.into_iter().next().unwrap(),
15376                                                    Expression::number(1000),
15377                                                ),
15378                                            ))],
15379                                        ))))
15380                                    }
15381                                    _ => Ok(Expression::Function(Box::new(Function::new(
15382                                        "EPOCH_MS".to_string(),
15383                                        f.args,
15384                                    )))),
15385                                }
15386                            }
15387                            // HASHBYTES('algorithm', x) -> target-specific hash function
15388                            "HASHBYTES" if f.args.len() == 2 => {
15389                                // Keep HASHBYTES as-is for TSQL target
15390                                if matches!(target, DialectType::TSQL) {
15391                                    return Ok(Expression::Function(f));
15392                                }
15393                                let algo_expr = &f.args[0];
15394                                let algo = match algo_expr {
15395                                    Expression::Literal(lit)
15396                                        if matches!(
15397                                            lit.as_ref(),
15398                                            crate::expressions::Literal::String(_)
15399                                        ) =>
15400                                    {
15401                                        let crate::expressions::Literal::String(s) = lit.as_ref()
15402                                        else {
15403                                            unreachable!()
15404                                        };
15405                                        s.to_ascii_uppercase()
15406                                    }
15407                                    _ => return Ok(Expression::Function(f)),
15408                                };
15409                                let data_arg = f.args.into_iter().nth(1).unwrap();
15410                                match algo.as_str() {
15411                                    "SHA1" => {
15412                                        let name = match target {
15413                                            DialectType::Spark | DialectType::Databricks => "SHA",
15414                                            DialectType::Hive => "SHA1",
15415                                            _ => "SHA1",
15416                                        };
15417                                        Ok(Expression::Function(Box::new(Function::new(
15418                                            name.to_string(),
15419                                            vec![data_arg],
15420                                        ))))
15421                                    }
15422                                    "SHA2_256" => {
15423                                        Ok(Expression::Function(Box::new(Function::new(
15424                                            "SHA2".to_string(),
15425                                            vec![data_arg, Expression::number(256)],
15426                                        ))))
15427                                    }
15428                                    "SHA2_512" => {
15429                                        Ok(Expression::Function(Box::new(Function::new(
15430                                            "SHA2".to_string(),
15431                                            vec![data_arg, Expression::number(512)],
15432                                        ))))
15433                                    }
15434                                    "MD5" => Ok(Expression::Function(Box::new(Function::new(
15435                                        "MD5".to_string(),
15436                                        vec![data_arg],
15437                                    )))),
15438                                    _ => Ok(Expression::Function(Box::new(Function::new(
15439                                        "HASHBYTES".to_string(),
15440                                        vec![Expression::string(&algo), data_arg],
15441                                    )))),
15442                                }
15443                            }
15444                            // JSON_EXTRACT_PATH(json, key1, key2, ...) -> target-specific JSON extraction
15445                            "JSON_EXTRACT_PATH" | "JSON_EXTRACT_PATH_TEXT" if f.args.len() >= 2 => {
15446                                let is_text = name == "JSON_EXTRACT_PATH_TEXT";
15447                                let mut args = f.args;
15448                                let json_expr = args.remove(0);
15449                                // Build JSON path from remaining keys: $.key1.key2 or $.key1[0]
15450                                let mut json_path = "$".to_string();
15451                                for a in &args {
15452                                    match a {
15453                                        Expression::Literal(lit)
15454                                            if matches!(
15455                                                lit.as_ref(),
15456                                                crate::expressions::Literal::String(_)
15457                                            ) =>
15458                                        {
15459                                            let crate::expressions::Literal::String(s) =
15460                                                lit.as_ref()
15461                                            else {
15462                                                unreachable!()
15463                                            };
15464                                            // Numeric string keys become array indices: [0]
15465                                            if s.chars().all(|c| c.is_ascii_digit()) {
15466                                                json_path.push('[');
15467                                                json_path.push_str(s);
15468                                                json_path.push(']');
15469                                            } else {
15470                                                json_path.push('.');
15471                                                json_path.push_str(s);
15472                                            }
15473                                        }
15474                                        _ => {
15475                                            json_path.push_str(".?");
15476                                        }
15477                                    }
15478                                }
15479                                match target {
15480                                    DialectType::Spark
15481                                    | DialectType::Databricks
15482                                    | DialectType::Hive => {
15483                                        Ok(Expression::Function(Box::new(Function::new(
15484                                            "GET_JSON_OBJECT".to_string(),
15485                                            vec![json_expr, Expression::string(&json_path)],
15486                                        ))))
15487                                    }
15488                                    DialectType::Presto | DialectType::Trino => {
15489                                        let func_name = if is_text {
15490                                            "JSON_EXTRACT_SCALAR"
15491                                        } else {
15492                                            "JSON_EXTRACT"
15493                                        };
15494                                        Ok(Expression::Function(Box::new(Function::new(
15495                                            func_name.to_string(),
15496                                            vec![json_expr, Expression::string(&json_path)],
15497                                        ))))
15498                                    }
15499                                    DialectType::BigQuery | DialectType::MySQL => {
15500                                        let func_name = if is_text {
15501                                            "JSON_EXTRACT_SCALAR"
15502                                        } else {
15503                                            "JSON_EXTRACT"
15504                                        };
15505                                        Ok(Expression::Function(Box::new(Function::new(
15506                                            func_name.to_string(),
15507                                            vec![json_expr, Expression::string(&json_path)],
15508                                        ))))
15509                                    }
15510                                    DialectType::PostgreSQL | DialectType::Materialize => {
15511                                        // Keep as JSON_EXTRACT_PATH_TEXT / JSON_EXTRACT_PATH for PostgreSQL/Materialize
15512                                        let func_name = if is_text {
15513                                            "JSON_EXTRACT_PATH_TEXT"
15514                                        } else {
15515                                            "JSON_EXTRACT_PATH"
15516                                        };
15517                                        let mut new_args = vec![json_expr];
15518                                        new_args.extend(args);
15519                                        Ok(Expression::Function(Box::new(Function::new(
15520                                            func_name.to_string(),
15521                                            new_args,
15522                                        ))))
15523                                    }
15524                                    DialectType::DuckDB | DialectType::SQLite => {
15525                                        // Use -> for JSON_EXTRACT_PATH, ->> for JSON_EXTRACT_PATH_TEXT
15526                                        if is_text {
15527                                            Ok(Expression::JsonExtractScalar(Box::new(
15528                                                crate::expressions::JsonExtractFunc {
15529                                                    this: json_expr,
15530                                                    path: Expression::string(&json_path),
15531                                                    returning: None,
15532                                                    arrow_syntax: true,
15533                                                    hash_arrow_syntax: false,
15534                                                    wrapper_option: None,
15535                                                    quotes_option: None,
15536                                                    on_scalar_string: false,
15537                                                    on_error: None,
15538                                                },
15539                                            )))
15540                                        } else {
15541                                            Ok(Expression::JsonExtract(Box::new(
15542                                                crate::expressions::JsonExtractFunc {
15543                                                    this: json_expr,
15544                                                    path: Expression::string(&json_path),
15545                                                    returning: None,
15546                                                    arrow_syntax: true,
15547                                                    hash_arrow_syntax: false,
15548                                                    wrapper_option: None,
15549                                                    quotes_option: None,
15550                                                    on_scalar_string: false,
15551                                                    on_error: None,
15552                                                },
15553                                            )))
15554                                        }
15555                                    }
15556                                    DialectType::Redshift => {
15557                                        // Keep as JSON_EXTRACT_PATH_TEXT for Redshift
15558                                        let mut new_args = vec![json_expr];
15559                                        new_args.extend(args);
15560                                        Ok(Expression::Function(Box::new(Function::new(
15561                                            "JSON_EXTRACT_PATH_TEXT".to_string(),
15562                                            new_args,
15563                                        ))))
15564                                    }
15565                                    DialectType::TSQL | DialectType::Fabric => {
15566                                        // ISNULL(JSON_QUERY(json, '$.path'), JSON_VALUE(json, '$.path'))
15567                                        let jq = Expression::Function(Box::new(Function::new(
15568                                            "JSON_QUERY".to_string(),
15569                                            vec![json_expr.clone(), Expression::string(&json_path)],
15570                                        )));
15571                                        let jv = Expression::Function(Box::new(Function::new(
15572                                            "JSON_VALUE".to_string(),
15573                                            vec![json_expr, Expression::string(&json_path)],
15574                                        )));
15575                                        Ok(Expression::Function(Box::new(Function::new(
15576                                            "ISNULL".to_string(),
15577                                            vec![jq, jv],
15578                                        ))))
15579                                    }
15580                                    DialectType::ClickHouse => {
15581                                        let func_name = if is_text {
15582                                            "JSONExtractString"
15583                                        } else {
15584                                            "JSONExtractRaw"
15585                                        };
15586                                        let mut new_args = vec![json_expr];
15587                                        new_args.extend(args);
15588                                        Ok(Expression::Function(Box::new(Function::new(
15589                                            func_name.to_string(),
15590                                            new_args,
15591                                        ))))
15592                                    }
15593                                    _ => {
15594                                        let func_name = if is_text {
15595                                            "JSON_EXTRACT_SCALAR"
15596                                        } else {
15597                                            "JSON_EXTRACT"
15598                                        };
15599                                        Ok(Expression::Function(Box::new(Function::new(
15600                                            func_name.to_string(),
15601                                            vec![json_expr, Expression::string(&json_path)],
15602                                        ))))
15603                                    }
15604                                }
15605                            }
15606                            // APPROX_DISTINCT(x) -> APPROX_COUNT_DISTINCT(x) for Spark/Hive/BigQuery
15607                            "APPROX_DISTINCT" if f.args.len() >= 1 => {
15608                                let name = match target {
15609                                    DialectType::Spark
15610                                    | DialectType::Databricks
15611                                    | DialectType::Hive
15612                                    | DialectType::BigQuery => "APPROX_COUNT_DISTINCT",
15613                                    _ => "APPROX_DISTINCT",
15614                                };
15615                                let mut args = f.args;
15616                                // Hive doesn't support the accuracy parameter
15617                                if name == "APPROX_COUNT_DISTINCT"
15618                                    && matches!(target, DialectType::Hive)
15619                                {
15620                                    args.truncate(1);
15621                                }
15622                                Ok(Expression::Function(Box::new(Function::new(
15623                                    name.to_string(),
15624                                    args,
15625                                ))))
15626                            }
15627                            // REGEXP_EXTRACT(x, pattern) - normalize default group index
15628                            "REGEXP_EXTRACT" if f.args.len() == 2 => {
15629                                // Determine source default group index
15630                                let source_default = match source {
15631                                    DialectType::Presto
15632                                    | DialectType::Trino
15633                                    | DialectType::DuckDB => 0,
15634                                    _ => 1, // Hive/Spark/Databricks default = 1
15635                                };
15636                                // Determine target default group index
15637                                let target_default = match target {
15638                                    DialectType::Presto
15639                                    | DialectType::Trino
15640                                    | DialectType::DuckDB
15641                                    | DialectType::BigQuery => 0,
15642                                    DialectType::Snowflake => {
15643                                        // Snowflake uses REGEXP_SUBSTR
15644                                        return Ok(Expression::Function(Box::new(Function::new(
15645                                            "REGEXP_SUBSTR".to_string(),
15646                                            f.args,
15647                                        ))));
15648                                    }
15649                                    _ => 1, // Hive/Spark/Databricks default = 1
15650                                };
15651                                if source_default != target_default {
15652                                    let mut args = f.args;
15653                                    args.push(Expression::number(source_default));
15654                                    Ok(Expression::Function(Box::new(Function::new(
15655                                        "REGEXP_EXTRACT".to_string(),
15656                                        args,
15657                                    ))))
15658                                } else {
15659                                    Ok(Expression::Function(Box::new(Function::new(
15660                                        "REGEXP_EXTRACT".to_string(),
15661                                        f.args,
15662                                    ))))
15663                                }
15664                            }
15665                            // RLIKE(str, pattern) -> RegexpLike expression (generates as target-specific form)
15666                            "RLIKE" if f.args.len() == 2 => {
15667                                let mut args = f.args;
15668                                let str_expr = args.remove(0);
15669                                let pattern = args.remove(0);
15670                                match target {
15671                                    DialectType::DuckDB => {
15672                                        // REGEXP_MATCHES(str, pattern)
15673                                        Ok(Expression::Function(Box::new(Function::new(
15674                                            "REGEXP_MATCHES".to_string(),
15675                                            vec![str_expr, pattern],
15676                                        ))))
15677                                    }
15678                                    _ => {
15679                                        // Convert to RegexpLike which generates as RLIKE/~/REGEXP_LIKE per dialect
15680                                        Ok(Expression::RegexpLike(Box::new(
15681                                            crate::expressions::RegexpFunc {
15682                                                this: str_expr,
15683                                                pattern,
15684                                                flags: None,
15685                                            },
15686                                        )))
15687                                    }
15688                                }
15689                            }
15690                            // EOMONTH(date[, month_offset]) -> target-specific
15691                            "EOMONTH" if f.args.len() >= 1 => {
15692                                let mut args = f.args;
15693                                let date_arg = args.remove(0);
15694                                let month_offset = if !args.is_empty() {
15695                                    Some(args.remove(0))
15696                                } else {
15697                                    None
15698                                };
15699
15700                                // Helper: wrap date in CAST to DATE
15701                                let cast_to_date = |e: Expression| -> Expression {
15702                                    Expression::Cast(Box::new(Cast {
15703                                        this: e,
15704                                        to: DataType::Date,
15705                                        trailing_comments: vec![],
15706                                        double_colon_syntax: false,
15707                                        format: None,
15708                                        default: None,
15709                                        inferred_type: None,
15710                                    }))
15711                                };
15712
15713                                match target {
15714                                    DialectType::TSQL | DialectType::Fabric => {
15715                                        // TSQL: EOMONTH(CAST(date AS DATE)) or EOMONTH(DATEADD(MONTH, offset, CAST(date AS DATE)))
15716                                        let date = cast_to_date(date_arg);
15717                                        let date = if let Some(offset) = month_offset {
15718                                            Expression::Function(Box::new(Function::new(
15719                                                "DATEADD".to_string(),
15720                                                vec![
15721                                                    Expression::Identifier(Identifier::new(
15722                                                        "MONTH",
15723                                                    )),
15724                                                    offset,
15725                                                    date,
15726                                                ],
15727                                            )))
15728                                        } else {
15729                                            date
15730                                        };
15731                                        Ok(Expression::Function(Box::new(Function::new(
15732                                            "EOMONTH".to_string(),
15733                                            vec![date],
15734                                        ))))
15735                                    }
15736                                    DialectType::Presto
15737                                    | DialectType::Trino
15738                                    | DialectType::Athena => {
15739                                        // Presto: LAST_DAY_OF_MONTH(CAST(CAST(date AS TIMESTAMP) AS DATE))
15740                                        // or with offset: LAST_DAY_OF_MONTH(DATE_ADD('MONTH', offset, CAST(CAST(date AS TIMESTAMP) AS DATE)))
15741                                        let cast_ts = Expression::Cast(Box::new(Cast {
15742                                            this: date_arg,
15743                                            to: DataType::Timestamp {
15744                                                timezone: false,
15745                                                precision: None,
15746                                            },
15747                                            trailing_comments: vec![],
15748                                            double_colon_syntax: false,
15749                                            format: None,
15750                                            default: None,
15751                                            inferred_type: None,
15752                                        }));
15753                                        let date = cast_to_date(cast_ts);
15754                                        let date = if let Some(offset) = month_offset {
15755                                            Expression::Function(Box::new(Function::new(
15756                                                "DATE_ADD".to_string(),
15757                                                vec![Expression::string("MONTH"), offset, date],
15758                                            )))
15759                                        } else {
15760                                            date
15761                                        };
15762                                        Ok(Expression::Function(Box::new(Function::new(
15763                                            "LAST_DAY_OF_MONTH".to_string(),
15764                                            vec![date],
15765                                        ))))
15766                                    }
15767                                    DialectType::PostgreSQL => {
15768                                        // PostgreSQL: CAST(DATE_TRUNC('MONTH', CAST(date AS DATE) [+ INTERVAL 'offset MONTH']) + INTERVAL '1 MONTH' - INTERVAL '1 DAY' AS DATE)
15769                                        let date = cast_to_date(date_arg);
15770                                        let date = if let Some(offset) = month_offset {
15771                                            let interval_str = format!(
15772                                                "{} MONTH",
15773                                                Self::expr_to_string_static(&offset)
15774                                            );
15775                                            Expression::Add(Box::new(
15776                                                crate::expressions::BinaryOp::new(
15777                                                    date,
15778                                                    Expression::Interval(Box::new(
15779                                                        crate::expressions::Interval {
15780                                                            this: Some(Expression::string(
15781                                                                &interval_str,
15782                                                            )),
15783                                                            unit: None,
15784                                                        },
15785                                                    )),
15786                                                ),
15787                                            ))
15788                                        } else {
15789                                            date
15790                                        };
15791                                        let truncated =
15792                                            Expression::Function(Box::new(Function::new(
15793                                                "DATE_TRUNC".to_string(),
15794                                                vec![Expression::string("MONTH"), date],
15795                                            )));
15796                                        let plus_month = Expression::Add(Box::new(
15797                                            crate::expressions::BinaryOp::new(
15798                                                truncated,
15799                                                Expression::Interval(Box::new(
15800                                                    crate::expressions::Interval {
15801                                                        this: Some(Expression::string("1 MONTH")),
15802                                                        unit: None,
15803                                                    },
15804                                                )),
15805                                            ),
15806                                        ));
15807                                        let minus_day = Expression::Sub(Box::new(
15808                                            crate::expressions::BinaryOp::new(
15809                                                plus_month,
15810                                                Expression::Interval(Box::new(
15811                                                    crate::expressions::Interval {
15812                                                        this: Some(Expression::string("1 DAY")),
15813                                                        unit: None,
15814                                                    },
15815                                                )),
15816                                            ),
15817                                        ));
15818                                        Ok(Expression::Cast(Box::new(Cast {
15819                                            this: minus_day,
15820                                            to: DataType::Date,
15821                                            trailing_comments: vec![],
15822                                            double_colon_syntax: false,
15823                                            format: None,
15824                                            default: None,
15825                                            inferred_type: None,
15826                                        })))
15827                                    }
15828                                    DialectType::DuckDB => {
15829                                        // DuckDB: LAST_DAY(CAST(date AS DATE) [+ INTERVAL (offset) MONTH])
15830                                        let date = cast_to_date(date_arg);
15831                                        let date = if let Some(offset) = month_offset {
15832                                            // Wrap negative numbers in parentheses for DuckDB INTERVAL
15833                                            let interval_val =
15834                                                if matches!(&offset, Expression::Neg(_)) {
15835                                                    Expression::Paren(Box::new(
15836                                                        crate::expressions::Paren {
15837                                                            this: offset,
15838                                                            trailing_comments: Vec::new(),
15839                                                        },
15840                                                    ))
15841                                                } else {
15842                                                    offset
15843                                                };
15844                                            Expression::Add(Box::new(crate::expressions::BinaryOp::new(
15845                                                date,
15846                                                Expression::Interval(Box::new(crate::expressions::Interval {
15847                                                    this: Some(interval_val),
15848                                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
15849                                                        unit: crate::expressions::IntervalUnit::Month,
15850                                                        use_plural: false,
15851                                                    }),
15852                                                })),
15853                                            )))
15854                                        } else {
15855                                            date
15856                                        };
15857                                        Ok(Expression::Function(Box::new(Function::new(
15858                                            "LAST_DAY".to_string(),
15859                                            vec![date],
15860                                        ))))
15861                                    }
15862                                    DialectType::Snowflake | DialectType::Redshift => {
15863                                        // Snowflake/Redshift: LAST_DAY(TO_DATE(date) or CAST(date AS DATE))
15864                                        // With offset: LAST_DAY(DATEADD(MONTH, offset, TO_DATE(date)))
15865                                        let date = if matches!(target, DialectType::Snowflake) {
15866                                            Expression::Function(Box::new(Function::new(
15867                                                "TO_DATE".to_string(),
15868                                                vec![date_arg],
15869                                            )))
15870                                        } else {
15871                                            cast_to_date(date_arg)
15872                                        };
15873                                        let date = if let Some(offset) = month_offset {
15874                                            Expression::Function(Box::new(Function::new(
15875                                                "DATEADD".to_string(),
15876                                                vec![
15877                                                    Expression::Identifier(Identifier::new(
15878                                                        "MONTH",
15879                                                    )),
15880                                                    offset,
15881                                                    date,
15882                                                ],
15883                                            )))
15884                                        } else {
15885                                            date
15886                                        };
15887                                        Ok(Expression::Function(Box::new(Function::new(
15888                                            "LAST_DAY".to_string(),
15889                                            vec![date],
15890                                        ))))
15891                                    }
15892                                    DialectType::Spark | DialectType::Databricks => {
15893                                        // Spark: LAST_DAY(TO_DATE(date))
15894                                        // With offset: LAST_DAY(ADD_MONTHS(TO_DATE(date), offset))
15895                                        let date = Expression::Function(Box::new(Function::new(
15896                                            "TO_DATE".to_string(),
15897                                            vec![date_arg],
15898                                        )));
15899                                        let date = if let Some(offset) = month_offset {
15900                                            Expression::Function(Box::new(Function::new(
15901                                                "ADD_MONTHS".to_string(),
15902                                                vec![date, offset],
15903                                            )))
15904                                        } else {
15905                                            date
15906                                        };
15907                                        Ok(Expression::Function(Box::new(Function::new(
15908                                            "LAST_DAY".to_string(),
15909                                            vec![date],
15910                                        ))))
15911                                    }
15912                                    DialectType::MySQL => {
15913                                        // MySQL: LAST_DAY(DATE(date)) - no offset
15914                                        // With offset: LAST_DAY(DATE_ADD(date, INTERVAL offset MONTH)) - no DATE() wrapper
15915                                        let date = if let Some(offset) = month_offset {
15916                                            let iu = crate::expressions::IntervalUnit::Month;
15917                                            Expression::DateAdd(Box::new(
15918                                                crate::expressions::DateAddFunc {
15919                                                    this: date_arg,
15920                                                    interval: offset,
15921                                                    unit: iu,
15922                                                },
15923                                            ))
15924                                        } else {
15925                                            Expression::Function(Box::new(Function::new(
15926                                                "DATE".to_string(),
15927                                                vec![date_arg],
15928                                            )))
15929                                        };
15930                                        Ok(Expression::Function(Box::new(Function::new(
15931                                            "LAST_DAY".to_string(),
15932                                            vec![date],
15933                                        ))))
15934                                    }
15935                                    DialectType::BigQuery => {
15936                                        // BigQuery: LAST_DAY(CAST(date AS DATE))
15937                                        // With offset: LAST_DAY(DATE_ADD(CAST(date AS DATE), INTERVAL offset MONTH))
15938                                        let date = cast_to_date(date_arg);
15939                                        let date = if let Some(offset) = month_offset {
15940                                            let interval = Expression::Interval(Box::new(crate::expressions::Interval {
15941                                                this: Some(offset),
15942                                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
15943                                                    unit: crate::expressions::IntervalUnit::Month,
15944                                                    use_plural: false,
15945                                                }),
15946                                            }));
15947                                            Expression::Function(Box::new(Function::new(
15948                                                "DATE_ADD".to_string(),
15949                                                vec![date, interval],
15950                                            )))
15951                                        } else {
15952                                            date
15953                                        };
15954                                        Ok(Expression::Function(Box::new(Function::new(
15955                                            "LAST_DAY".to_string(),
15956                                            vec![date],
15957                                        ))))
15958                                    }
15959                                    DialectType::ClickHouse => {
15960                                        // ClickHouse: LAST_DAY(CAST(date AS Nullable(DATE)))
15961                                        let date = Expression::Cast(Box::new(Cast {
15962                                            this: date_arg,
15963                                            to: DataType::Nullable {
15964                                                inner: Box::new(DataType::Date),
15965                                            },
15966                                            trailing_comments: vec![],
15967                                            double_colon_syntax: false,
15968                                            format: None,
15969                                            default: None,
15970                                            inferred_type: None,
15971                                        }));
15972                                        let date = if let Some(offset) = month_offset {
15973                                            Expression::Function(Box::new(Function::new(
15974                                                "DATE_ADD".to_string(),
15975                                                vec![
15976                                                    Expression::Identifier(Identifier::new(
15977                                                        "MONTH",
15978                                                    )),
15979                                                    offset,
15980                                                    date,
15981                                                ],
15982                                            )))
15983                                        } else {
15984                                            date
15985                                        };
15986                                        Ok(Expression::Function(Box::new(Function::new(
15987                                            "LAST_DAY".to_string(),
15988                                            vec![date],
15989                                        ))))
15990                                    }
15991                                    DialectType::Hive => {
15992                                        // Hive: LAST_DAY(date)
15993                                        let date = if let Some(offset) = month_offset {
15994                                            Expression::Function(Box::new(Function::new(
15995                                                "ADD_MONTHS".to_string(),
15996                                                vec![date_arg, offset],
15997                                            )))
15998                                        } else {
15999                                            date_arg
16000                                        };
16001                                        Ok(Expression::Function(Box::new(Function::new(
16002                                            "LAST_DAY".to_string(),
16003                                            vec![date],
16004                                        ))))
16005                                    }
16006                                    _ => {
16007                                        // Default: LAST_DAY(date)
16008                                        let date = if let Some(offset) = month_offset {
16009                                            let unit =
16010                                                Expression::Identifier(Identifier::new("MONTH"));
16011                                            Expression::Function(Box::new(Function::new(
16012                                                "DATEADD".to_string(),
16013                                                vec![unit, offset, date_arg],
16014                                            )))
16015                                        } else {
16016                                            date_arg
16017                                        };
16018                                        Ok(Expression::Function(Box::new(Function::new(
16019                                            "LAST_DAY".to_string(),
16020                                            vec![date],
16021                                        ))))
16022                                    }
16023                                }
16024                            }
16025                            // LAST_DAY(x) / LAST_DAY_OF_MONTH(x) -> target-specific
16026                            "LAST_DAY" | "LAST_DAY_OF_MONTH"
16027                                if !matches!(source, DialectType::BigQuery)
16028                                    && f.args.len() >= 1 =>
16029                            {
16030                                let first_arg = f.args.into_iter().next().unwrap();
16031                                match target {
16032                                    DialectType::TSQL | DialectType::Fabric => {
16033                                        Ok(Expression::Function(Box::new(Function::new(
16034                                            "EOMONTH".to_string(),
16035                                            vec![first_arg],
16036                                        ))))
16037                                    }
16038                                    DialectType::Presto
16039                                    | DialectType::Trino
16040                                    | DialectType::Athena => {
16041                                        Ok(Expression::Function(Box::new(Function::new(
16042                                            "LAST_DAY_OF_MONTH".to_string(),
16043                                            vec![first_arg],
16044                                        ))))
16045                                    }
16046                                    _ => Ok(Expression::Function(Box::new(Function::new(
16047                                        "LAST_DAY".to_string(),
16048                                        vec![first_arg],
16049                                    )))),
16050                                }
16051                            }
16052                            // BigQuery PARSE_DATETIME(format, value) -> target-specific parsing calls.
16053                            "PARSE_DATETIME"
16054                                if matches!(source, DialectType::BigQuery) && f.args.len() == 2 =>
16055                            {
16056                                fn expand_bigquery_datetime_format(expr: Expression) -> Expression {
16057                                    match expr {
16058                                        Expression::Literal(lit) => match lit.as_ref() {
16059                                            Literal::String(s) => Expression::string(
16060                                                s.replace("%F", "%Y-%m-%d")
16061                                                    .replace("%T", "%H:%M:%S"),
16062                                            ),
16063                                            _ => Expression::Literal(lit),
16064                                        },
16065                                        other => other,
16066                                    }
16067                                }
16068
16069                                let mut args = f.args;
16070                                let format = expand_bigquery_datetime_format(args.remove(0));
16071                                let value = args.remove(0);
16072                                match target {
16073                                    DialectType::DuckDB => {
16074                                        let value_with_year = Expression::Concat(Box::new(
16075                                            crate::expressions::BinaryOp::new(
16076                                                Expression::string("1970 "),
16077                                                value,
16078                                            ),
16079                                        ));
16080                                        let format_with_year = Expression::Concat(Box::new(
16081                                            crate::expressions::BinaryOp::new(
16082                                                Expression::string("%Y "),
16083                                                format,
16084                                            ),
16085                                        ));
16086                                        Ok(Expression::Function(Box::new(Function::new(
16087                                            "STRPTIME".to_string(),
16088                                            vec![value_with_year, format_with_year],
16089                                        ))))
16090                                    }
16091                                    DialectType::Snowflake => {
16092                                        Ok(Expression::Function(Box::new(Function::new(
16093                                            "PARSE_DATETIME".to_string(),
16094                                            vec![value, format],
16095                                        ))))
16096                                    }
16097                                    _ => Ok(Expression::Function(Box::new(Function::new(
16098                                        "PARSE_DATETIME".to_string(),
16099                                        vec![format, value],
16100                                    )))),
16101                                }
16102                            }
16103                            // Presto/Trino ISO-8601 helpers become casts outside Presto-family targets.
16104                            "FROM_ISO8601_TIMESTAMP"
16105                                if matches!(
16106                                    source,
16107                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
16108                                ) && f.args.len() == 1
16109                                    && !matches!(
16110                                        target,
16111                                        DialectType::Presto
16112                                            | DialectType::Trino
16113                                            | DialectType::Athena
16114                                    ) =>
16115                            {
16116                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
16117                                    this: f.args.into_iter().next().unwrap(),
16118                                    to: DataType::Timestamp {
16119                                        precision: None,
16120                                        timezone: matches!(
16121                                            target,
16122                                            DialectType::DuckDB | DialectType::Snowflake
16123                                        ),
16124                                    },
16125                                    trailing_comments: Vec::new(),
16126                                    double_colon_syntax: false,
16127                                    format: None,
16128                                    default: None,
16129                                    inferred_type: None,
16130                                })))
16131                            }
16132                            "FROM_ISO8601_DATE"
16133                                if matches!(
16134                                    source,
16135                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
16136                                ) && f.args.len() == 1
16137                                    && !matches!(
16138                                        target,
16139                                        DialectType::Presto
16140                                            | DialectType::Trino
16141                                            | DialectType::Athena
16142                                    ) =>
16143                            {
16144                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
16145                                    this: f.args.into_iter().next().unwrap(),
16146                                    to: DataType::Date,
16147                                    trailing_comments: Vec::new(),
16148                                    double_colon_syntax: false,
16149                                    format: None,
16150                                    default: None,
16151                                    inferred_type: None,
16152                                })))
16153                            }
16154                            // MAP(keys_array, vals_array) from Presto (2-arg form) -> target-specific
16155                            "MAP"
16156                                if f.args.len() == 2
16157                                    && matches!(
16158                                        source,
16159                                        DialectType::Presto
16160                                            | DialectType::Trino
16161                                            | DialectType::Athena
16162                                    ) =>
16163                            {
16164                                let keys_arg = f.args[0].clone();
16165                                let vals_arg = f.args[1].clone();
16166
16167                                // Helper: extract array elements from Array/ArrayFunc/Function("ARRAY") expressions
16168                                fn extract_array_elements(
16169                                    expr: &Expression,
16170                                ) -> Option<&Vec<Expression>> {
16171                                    match expr {
16172                                        Expression::Array(arr) => Some(&arr.expressions),
16173                                        Expression::ArrayFunc(arr) => Some(&arr.expressions),
16174                                        Expression::Function(f)
16175                                            if f.name.eq_ignore_ascii_case("ARRAY") =>
16176                                        {
16177                                            Some(&f.args)
16178                                        }
16179                                        _ => None,
16180                                    }
16181                                }
16182
16183                                match target {
16184                                    DialectType::Spark | DialectType::Databricks => {
16185                                        // Presto MAP(keys, vals) -> Spark MAP_FROM_ARRAYS(keys, vals)
16186                                        Ok(Expression::Function(Box::new(Function::new(
16187                                            "MAP_FROM_ARRAYS".to_string(),
16188                                            f.args,
16189                                        ))))
16190                                    }
16191                                    DialectType::Hive => {
16192                                        // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Hive MAP(k1, v1, k2, v2)
16193                                        if let (Some(keys), Some(vals)) = (
16194                                            extract_array_elements(&keys_arg),
16195                                            extract_array_elements(&vals_arg),
16196                                        ) {
16197                                            if keys.len() == vals.len() {
16198                                                let mut interleaved = Vec::new();
16199                                                for (k, v) in keys.iter().zip(vals.iter()) {
16200                                                    interleaved.push(k.clone());
16201                                                    interleaved.push(v.clone());
16202                                                }
16203                                                Ok(Expression::Function(Box::new(Function::new(
16204                                                    "MAP".to_string(),
16205                                                    interleaved,
16206                                                ))))
16207                                            } else {
16208                                                Ok(Expression::Function(Box::new(Function::new(
16209                                                    "MAP".to_string(),
16210                                                    f.args,
16211                                                ))))
16212                                            }
16213                                        } else {
16214                                            Ok(Expression::Function(Box::new(Function::new(
16215                                                "MAP".to_string(),
16216                                                f.args,
16217                                            ))))
16218                                        }
16219                                    }
16220                                    DialectType::Snowflake => {
16221                                        // Presto MAP(ARRAY[k1,k2], ARRAY[v1,v2]) -> Snowflake OBJECT_CONSTRUCT(k1, v1, k2, v2)
16222                                        if let (Some(keys), Some(vals)) = (
16223                                            extract_array_elements(&keys_arg),
16224                                            extract_array_elements(&vals_arg),
16225                                        ) {
16226                                            if keys.len() == vals.len() {
16227                                                let mut interleaved = Vec::new();
16228                                                for (k, v) in keys.iter().zip(vals.iter()) {
16229                                                    interleaved.push(k.clone());
16230                                                    interleaved.push(v.clone());
16231                                                }
16232                                                Ok(Expression::Function(Box::new(Function::new(
16233                                                    "OBJECT_CONSTRUCT".to_string(),
16234                                                    interleaved,
16235                                                ))))
16236                                            } else {
16237                                                Ok(Expression::Function(Box::new(Function::new(
16238                                                    "MAP".to_string(),
16239                                                    f.args,
16240                                                ))))
16241                                            }
16242                                        } else {
16243                                            Ok(Expression::Function(Box::new(Function::new(
16244                                                "MAP".to_string(),
16245                                                f.args,
16246                                            ))))
16247                                        }
16248                                    }
16249                                    _ => Ok(Expression::Function(f)),
16250                                }
16251                            }
16252                            // MAP() with 0 args from Spark -> MAP(ARRAY[], ARRAY[]) for Presto/Trino
16253                            "MAP"
16254                                if f.args.is_empty()
16255                                    && matches!(
16256                                        source,
16257                                        DialectType::Hive
16258                                            | DialectType::Spark
16259                                            | DialectType::Databricks
16260                                    )
16261                                    && matches!(
16262                                        target,
16263                                        DialectType::Presto
16264                                            | DialectType::Trino
16265                                            | DialectType::Athena
16266                                    ) =>
16267                            {
16268                                let empty_keys =
16269                                    Expression::Array(Box::new(crate::expressions::Array {
16270                                        expressions: vec![],
16271                                    }));
16272                                let empty_vals =
16273                                    Expression::Array(Box::new(crate::expressions::Array {
16274                                        expressions: vec![],
16275                                    }));
16276                                Ok(Expression::Function(Box::new(Function::new(
16277                                    "MAP".to_string(),
16278                                    vec![empty_keys, empty_vals],
16279                                ))))
16280                            }
16281                            // MAP(k1, v1, k2, v2, ...) from Hive/Spark -> target-specific
16282                            "MAP"
16283                                if f.args.len() >= 2
16284                                    && f.args.len() % 2 == 0
16285                                    && matches!(
16286                                        source,
16287                                        DialectType::Hive
16288                                            | DialectType::Spark
16289                                            | DialectType::Databricks
16290                                            | DialectType::ClickHouse
16291                                            | DialectType::StarRocks
16292                                    ) =>
16293                            {
16294                                let args = f.args;
16295                                match target {
16296                                    DialectType::DuckDB => {
16297                                        // MAP([k1, k2], [v1, v2])
16298                                        let mut keys = Vec::new();
16299                                        let mut vals = Vec::new();
16300                                        for (i, arg) in args.into_iter().enumerate() {
16301                                            if i % 2 == 0 {
16302                                                keys.push(arg);
16303                                            } else {
16304                                                vals.push(arg);
16305                                            }
16306                                        }
16307                                        let keys_arr = Expression::Array(Box::new(
16308                                            crate::expressions::Array { expressions: keys },
16309                                        ));
16310                                        let vals_arr = Expression::Array(Box::new(
16311                                            crate::expressions::Array { expressions: vals },
16312                                        ));
16313                                        Ok(Expression::Function(Box::new(Function::new(
16314                                            "MAP".to_string(),
16315                                            vec![keys_arr, vals_arr],
16316                                        ))))
16317                                    }
16318                                    DialectType::Presto | DialectType::Trino => {
16319                                        // MAP(ARRAY[k1, k2], ARRAY[v1, v2])
16320                                        let mut keys = Vec::new();
16321                                        let mut vals = Vec::new();
16322                                        for (i, arg) in args.into_iter().enumerate() {
16323                                            if i % 2 == 0 {
16324                                                keys.push(arg);
16325                                            } else {
16326                                                vals.push(arg);
16327                                            }
16328                                        }
16329                                        let keys_arr = Expression::Array(Box::new(
16330                                            crate::expressions::Array { expressions: keys },
16331                                        ));
16332                                        let vals_arr = Expression::Array(Box::new(
16333                                            crate::expressions::Array { expressions: vals },
16334                                        ));
16335                                        Ok(Expression::Function(Box::new(Function::new(
16336                                            "MAP".to_string(),
16337                                            vec![keys_arr, vals_arr],
16338                                        ))))
16339                                    }
16340                                    DialectType::Snowflake => Ok(Expression::Function(Box::new(
16341                                        Function::new("OBJECT_CONSTRUCT".to_string(), args),
16342                                    ))),
16343                                    DialectType::ClickHouse => Ok(Expression::Function(Box::new(
16344                                        Function::new("map".to_string(), args),
16345                                    ))),
16346                                    _ => Ok(Expression::Function(Box::new(Function::new(
16347                                        "MAP".to_string(),
16348                                        args,
16349                                    )))),
16350                                }
16351                            }
16352                            // COLLECT_LIST(x) -> ARRAY_AGG(x) for most targets
16353                            "COLLECT_LIST" if f.args.len() >= 1 => {
16354                                let name = match target {
16355                                    DialectType::Spark
16356                                    | DialectType::Databricks
16357                                    | DialectType::Hive => "COLLECT_LIST",
16358                                    DialectType::DuckDB
16359                                    | DialectType::PostgreSQL
16360                                    | DialectType::Redshift
16361                                    | DialectType::Snowflake
16362                                    | DialectType::BigQuery => "ARRAY_AGG",
16363                                    DialectType::Presto | DialectType::Trino => "ARRAY_AGG",
16364                                    _ => "ARRAY_AGG",
16365                                };
16366                                Ok(Expression::Function(Box::new(Function::new(
16367                                    name.to_string(),
16368                                    f.args,
16369                                ))))
16370                            }
16371                            // COLLECT_SET(x) -> target-specific distinct array aggregation
16372                            "COLLECT_SET" if f.args.len() >= 1 => {
16373                                let name = match target {
16374                                    DialectType::Spark
16375                                    | DialectType::Databricks
16376                                    | DialectType::Hive => "COLLECT_SET",
16377                                    DialectType::Presto
16378                                    | DialectType::Trino
16379                                    | DialectType::Athena => "SET_AGG",
16380                                    DialectType::Snowflake => "ARRAY_UNIQUE_AGG",
16381                                    _ => "ARRAY_AGG",
16382                                };
16383                                Ok(Expression::Function(Box::new(Function::new(
16384                                    name.to_string(),
16385                                    f.args,
16386                                ))))
16387                            }
16388                            // ISNAN(x) / IS_NAN(x) - normalize
16389                            "ISNAN" | "IS_NAN" => {
16390                                let name = match target {
16391                                    DialectType::Spark
16392                                    | DialectType::Databricks
16393                                    | DialectType::Hive => "ISNAN",
16394                                    DialectType::Presto
16395                                    | DialectType::Trino
16396                                    | DialectType::Athena => "IS_NAN",
16397                                    DialectType::BigQuery
16398                                    | DialectType::PostgreSQL
16399                                    | DialectType::Redshift => "IS_NAN",
16400                                    DialectType::ClickHouse => "IS_NAN",
16401                                    _ => "ISNAN",
16402                                };
16403                                Ok(Expression::Function(Box::new(Function::new(
16404                                    name.to_string(),
16405                                    f.args,
16406                                ))))
16407                            }
16408                            // SPLIT_PART(str, delim, index) -> target-specific
16409                            "SPLIT_PART" if f.args.len() == 3 => {
16410                                match target {
16411                                    DialectType::Spark | DialectType::Databricks => {
16412                                        // Keep as SPLIT_PART (Spark 3.4+)
16413                                        Ok(Expression::Function(Box::new(Function::new(
16414                                            "SPLIT_PART".to_string(),
16415                                            f.args,
16416                                        ))))
16417                                    }
16418                                    DialectType::DuckDB
16419                                        if matches!(source, DialectType::Snowflake) =>
16420                                    {
16421                                        // Snowflake SPLIT_PART -> DuckDB with CASE wrapper:
16422                                        // - part_index 0 treated as 1
16423                                        // - empty delimiter: return whole string if index 1 or -1, else ''
16424                                        let mut args = f.args;
16425                                        let str_arg = args.remove(0);
16426                                        let delim_arg = args.remove(0);
16427                                        let idx_arg = args.remove(0);
16428
16429                                        // (CASE WHEN idx = 0 THEN 1 ELSE idx END)
16430                                        let adjusted_idx = Expression::Paren(Box::new(Paren {
16431                                            this: Expression::Case(Box::new(Case {
16432                                                operand: None,
16433                                                whens: vec![(
16434                                                    Expression::Eq(Box::new(BinaryOp {
16435                                                        left: idx_arg.clone(),
16436                                                        right: Expression::number(0),
16437                                                        left_comments: vec![],
16438                                                        operator_comments: vec![],
16439                                                        trailing_comments: vec![],
16440                                                        inferred_type: None,
16441                                                    })),
16442                                                    Expression::number(1),
16443                                                )],
16444                                                else_: Some(idx_arg.clone()),
16445                                                comments: vec![],
16446                                                inferred_type: None,
16447                                            })),
16448                                            trailing_comments: vec![],
16449                                        }));
16450
16451                                        // SPLIT_PART(str, delim, adjusted_idx)
16452                                        let base_func =
16453                                            Expression::Function(Box::new(Function::new(
16454                                                "SPLIT_PART".to_string(),
16455                                                vec![
16456                                                    str_arg.clone(),
16457                                                    delim_arg.clone(),
16458                                                    adjusted_idx.clone(),
16459                                                ],
16460                                            )));
16461
16462                                        // (CASE WHEN adjusted_idx = 1 OR adjusted_idx = -1 THEN str ELSE '' END)
16463                                        let empty_delim_case = Expression::Paren(Box::new(Paren {
16464                                            this: Expression::Case(Box::new(Case {
16465                                                operand: None,
16466                                                whens: vec![(
16467                                                    Expression::Or(Box::new(BinaryOp {
16468                                                        left: Expression::Eq(Box::new(BinaryOp {
16469                                                            left: adjusted_idx.clone(),
16470                                                            right: Expression::number(1),
16471                                                            left_comments: vec![],
16472                                                            operator_comments: vec![],
16473                                                            trailing_comments: vec![],
16474                                                            inferred_type: None,
16475                                                        })),
16476                                                        right: Expression::Eq(Box::new(BinaryOp {
16477                                                            left: adjusted_idx,
16478                                                            right: Expression::number(-1),
16479                                                            left_comments: vec![],
16480                                                            operator_comments: vec![],
16481                                                            trailing_comments: vec![],
16482                                                            inferred_type: None,
16483                                                        })),
16484                                                        left_comments: vec![],
16485                                                        operator_comments: vec![],
16486                                                        trailing_comments: vec![],
16487                                                        inferred_type: None,
16488                                                    })),
16489                                                    str_arg,
16490                                                )],
16491                                                else_: Some(Expression::string("")),
16492                                                comments: vec![],
16493                                                inferred_type: None,
16494                                            })),
16495                                            trailing_comments: vec![],
16496                                        }));
16497
16498                                        // CASE WHEN delim = '' THEN (empty case) ELSE SPLIT_PART(...) END
16499                                        Ok(Expression::Case(Box::new(Case {
16500                                            operand: None,
16501                                            whens: vec![(
16502                                                Expression::Eq(Box::new(BinaryOp {
16503                                                    left: delim_arg,
16504                                                    right: Expression::string(""),
16505                                                    left_comments: vec![],
16506                                                    operator_comments: vec![],
16507                                                    trailing_comments: vec![],
16508                                                    inferred_type: None,
16509                                                })),
16510                                                empty_delim_case,
16511                                            )],
16512                                            else_: Some(base_func),
16513                                            comments: vec![],
16514                                            inferred_type: None,
16515                                        })))
16516                                    }
16517                                    DialectType::DuckDB
16518                                    | DialectType::PostgreSQL
16519                                    | DialectType::Snowflake
16520                                    | DialectType::Redshift
16521                                    | DialectType::Trino
16522                                    | DialectType::Presto => Ok(Expression::Function(Box::new(
16523                                        Function::new("SPLIT_PART".to_string(), f.args),
16524                                    ))),
16525                                    DialectType::Hive => {
16526                                        // SPLIT(str, delim)[index]
16527                                        // Complex conversion, just keep as-is for now
16528                                        Ok(Expression::Function(Box::new(Function::new(
16529                                            "SPLIT_PART".to_string(),
16530                                            f.args,
16531                                        ))))
16532                                    }
16533                                    _ => Ok(Expression::Function(Box::new(Function::new(
16534                                        "SPLIT_PART".to_string(),
16535                                        f.args,
16536                                    )))),
16537                                }
16538                            }
16539                            // JSON_EXTRACT(json, path) -> target-specific JSON extraction
16540                            "JSON_EXTRACT" | "JSON_EXTRACT_SCALAR" if f.args.len() == 2 => {
16541                                let is_scalar = name == "JSON_EXTRACT_SCALAR";
16542                                match target {
16543                                    DialectType::Spark
16544                                    | DialectType::Databricks
16545                                    | DialectType::Hive => {
16546                                        let mut args = f.args;
16547                                        // Spark/Hive don't support Presto's TRY(expr) wrapper form here.
16548                                        // Mirror sqlglot by unwrapping TRY(expr) to expr before GET_JSON_OBJECT.
16549                                        if let Some(Expression::Function(inner)) = args.first() {
16550                                            if inner.name.eq_ignore_ascii_case("TRY")
16551                                                && inner.args.len() == 1
16552                                            {
16553                                                let mut inner_args = inner.args.clone();
16554                                                args[0] = inner_args.remove(0);
16555                                            }
16556                                        }
16557                                        Ok(Expression::Function(Box::new(Function::new(
16558                                            "GET_JSON_OBJECT".to_string(),
16559                                            args,
16560                                        ))))
16561                                    }
16562                                    DialectType::DuckDB | DialectType::SQLite => {
16563                                        // json -> path syntax
16564                                        let mut args = f.args;
16565                                        let json_expr = args.remove(0);
16566                                        let path = args.remove(0);
16567                                        Ok(Expression::JsonExtract(Box::new(
16568                                            crate::expressions::JsonExtractFunc {
16569                                                this: json_expr,
16570                                                path,
16571                                                returning: None,
16572                                                arrow_syntax: true,
16573                                                hash_arrow_syntax: false,
16574                                                wrapper_option: None,
16575                                                quotes_option: None,
16576                                                on_scalar_string: false,
16577                                                on_error: None,
16578                                            },
16579                                        )))
16580                                    }
16581                                    DialectType::TSQL => {
16582                                        let func_name = if is_scalar {
16583                                            "JSON_VALUE"
16584                                        } else {
16585                                            "JSON_QUERY"
16586                                        };
16587                                        Ok(Expression::Function(Box::new(Function::new(
16588                                            func_name.to_string(),
16589                                            f.args,
16590                                        ))))
16591                                    }
16592                                    DialectType::PostgreSQL | DialectType::Redshift => {
16593                                        let func_name = if is_scalar {
16594                                            "JSON_EXTRACT_PATH_TEXT"
16595                                        } else {
16596                                            "JSON_EXTRACT_PATH"
16597                                        };
16598                                        Ok(Expression::Function(Box::new(Function::new(
16599                                            func_name.to_string(),
16600                                            f.args,
16601                                        ))))
16602                                    }
16603                                    _ => Ok(Expression::Function(Box::new(Function::new(
16604                                        name.to_string(),
16605                                        f.args,
16606                                    )))),
16607                                }
16608                            }
16609                            // MySQL JSON_SEARCH(json_doc, mode, search[, escape_char[, path]]) -> DuckDB json_tree-based lookup
16610                            "JSON_SEARCH"
16611                                if matches!(target, DialectType::DuckDB)
16612                                    && (3..=5).contains(&f.args.len()) =>
16613                            {
16614                                let args = &f.args;
16615
16616                                // Only rewrite deterministic modes and NULL/no escape-char variant.
16617                                let mode = match &args[1] {
16618                                    Expression::Literal(lit)
16619                                        if matches!(
16620                                            lit.as_ref(),
16621                                            crate::expressions::Literal::String(_)
16622                                        ) =>
16623                                    {
16624                                        let crate::expressions::Literal::String(s) = lit.as_ref()
16625                                        else {
16626                                            unreachable!()
16627                                        };
16628                                        s.to_ascii_lowercase()
16629                                    }
16630                                    _ => return Ok(Expression::Function(f)),
16631                                };
16632                                if mode != "one" && mode != "all" {
16633                                    return Ok(Expression::Function(f));
16634                                }
16635                                if args.len() >= 4 && !matches!(&args[3], Expression::Null(_)) {
16636                                    return Ok(Expression::Function(f));
16637                                }
16638
16639                                let json_doc_sql = match Generator::sql(&args[0]) {
16640                                    Ok(sql) => sql,
16641                                    Err(_) => return Ok(Expression::Function(f)),
16642                                };
16643                                let search_sql = match Generator::sql(&args[2]) {
16644                                    Ok(sql) => sql,
16645                                    Err(_) => return Ok(Expression::Function(f)),
16646                                };
16647                                let path_sql = if args.len() == 5 {
16648                                    match Generator::sql(&args[4]) {
16649                                        Ok(sql) => sql,
16650                                        Err(_) => return Ok(Expression::Function(f)),
16651                                    }
16652                                } else {
16653                                    "'$'".to_string()
16654                                };
16655
16656                                let rewrite_sql = if mode == "all" {
16657                                    format!(
16658                                        "(SELECT TO_JSON(LIST(__jt.fullkey)) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}))",
16659                                        json_doc_sql, path_sql, search_sql
16660                                    )
16661                                } else {
16662                                    format!(
16663                                        "(SELECT TO_JSON(__jt.fullkey) FROM json_tree({}, {}) AS __jt WHERE __jt.atom = TO_JSON({}) ORDER BY __jt.id LIMIT 1)",
16664                                        json_doc_sql, path_sql, search_sql
16665                                    )
16666                                };
16667
16668                                Ok(Expression::Raw(crate::expressions::Raw {
16669                                    sql: rewrite_sql,
16670                                }))
16671                            }
16672                            // SingleStore JSON_EXTRACT_JSON(json, key1, key2, ...) -> JSON_EXTRACT(json, '$.key1.key2' or '$.key1[key2]')
16673                            // BSON_EXTRACT_BSON(json, key1, ...) -> JSONB_EXTRACT(json, '$.key1')
16674                            "JSON_EXTRACT_JSON" | "BSON_EXTRACT_BSON"
16675                                if f.args.len() >= 2
16676                                    && matches!(source, DialectType::SingleStore) =>
16677                            {
16678                                let is_bson = name == "BSON_EXTRACT_BSON";
16679                                let mut args = f.args;
16680                                let json_expr = args.remove(0);
16681
16682                                // Build JSONPath from remaining arguments
16683                                let mut path = String::from("$");
16684                                for arg in &args {
16685                                    if let Expression::Literal(lit) = arg {
16686                                        if let crate::expressions::Literal::String(s) = lit.as_ref()
16687                                        {
16688                                            // Check if it's a numeric string (array index)
16689                                            if s.parse::<i64>().is_ok() {
16690                                                path.push('[');
16691                                                path.push_str(s);
16692                                                path.push(']');
16693                                            } else {
16694                                                path.push('.');
16695                                                path.push_str(s);
16696                                            }
16697                                        }
16698                                    }
16699                                }
16700
16701                                let target_func = if is_bson {
16702                                    "JSONB_EXTRACT"
16703                                } else {
16704                                    "JSON_EXTRACT"
16705                                };
16706                                Ok(Expression::Function(Box::new(Function::new(
16707                                    target_func.to_string(),
16708                                    vec![json_expr, Expression::string(&path)],
16709                                ))))
16710                            }
16711                            // ARRAY_SUM(lambda, array) from Doris -> ClickHouse arraySum
16712                            "ARRAY_SUM" if matches!(target, DialectType::ClickHouse) => {
16713                                Ok(Expression::Function(Box::new(Function {
16714                                    name: "arraySum".to_string(),
16715                                    args: f.args,
16716                                    distinct: f.distinct,
16717                                    trailing_comments: f.trailing_comments,
16718                                    use_bracket_syntax: f.use_bracket_syntax,
16719                                    no_parens: f.no_parens,
16720                                    quoted: f.quoted,
16721                                    span: None,
16722                                    inferred_type: None,
16723                                })))
16724                            }
16725                            // TSQL JSON_QUERY/JSON_VALUE -> target-specific
16726                            // Note: For TSQL->TSQL, JsonQuery stays as Expression::JsonQuery (source transform not called)
16727                            // and is handled by JsonQueryValueConvert action. This handles the case where
16728                            // TSQL read transform converted JsonQuery to Function("JSON_QUERY") for cross-dialect.
16729                            "JSON_QUERY" | "JSON_VALUE"
16730                                if f.args.len() == 2
16731                                    && matches!(
16732                                        source,
16733                                        DialectType::TSQL | DialectType::Fabric
16734                                    ) =>
16735                            {
16736                                match target {
16737                                    DialectType::Spark
16738                                    | DialectType::Databricks
16739                                    | DialectType::Hive => Ok(Expression::Function(Box::new(
16740                                        Function::new("GET_JSON_OBJECT".to_string(), f.args),
16741                                    ))),
16742                                    _ => Ok(Expression::Function(Box::new(Function::new(
16743                                        name.to_string(),
16744                                        f.args,
16745                                    )))),
16746                                }
16747                            }
16748                            // UNIX_TIMESTAMP(x) -> TO_UNIXTIME(x) for Presto
16749                            "UNIX_TIMESTAMP" if f.args.len() == 1 => {
16750                                let arg = f.args.into_iter().next().unwrap();
16751                                let is_hive_source = matches!(
16752                                    source,
16753                                    DialectType::Hive
16754                                        | DialectType::Spark
16755                                        | DialectType::Databricks
16756                                );
16757                                match target {
16758                                    DialectType::DuckDB if is_hive_source => {
16759                                        // DuckDB: EPOCH(STRPTIME(x, '%Y-%m-%d %H:%M:%S'))
16760                                        let strptime =
16761                                            Expression::Function(Box::new(Function::new(
16762                                                "STRPTIME".to_string(),
16763                                                vec![arg, Expression::string("%Y-%m-%d %H:%M:%S")],
16764                                            )));
16765                                        Ok(Expression::Function(Box::new(Function::new(
16766                                            "EPOCH".to_string(),
16767                                            vec![strptime],
16768                                        ))))
16769                                    }
16770                                    DialectType::Presto | DialectType::Trino if is_hive_source => {
16771                                        // 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')))
16772                                        let cast_varchar =
16773                                            Expression::Cast(Box::new(crate::expressions::Cast {
16774                                                this: arg.clone(),
16775                                                to: DataType::VarChar {
16776                                                    length: None,
16777                                                    parenthesized_length: false,
16778                                                },
16779                                                trailing_comments: vec![],
16780                                                double_colon_syntax: false,
16781                                                format: None,
16782                                                default: None,
16783                                                inferred_type: None,
16784                                            }));
16785                                        let date_parse =
16786                                            Expression::Function(Box::new(Function::new(
16787                                                "DATE_PARSE".to_string(),
16788                                                vec![
16789                                                    cast_varchar,
16790                                                    Expression::string("%Y-%m-%d %T"),
16791                                                ],
16792                                            )));
16793                                        let try_expr = Expression::Function(Box::new(
16794                                            Function::new("TRY".to_string(), vec![date_parse]),
16795                                        ));
16796                                        let date_format =
16797                                            Expression::Function(Box::new(Function::new(
16798                                                "DATE_FORMAT".to_string(),
16799                                                vec![arg, Expression::string("%Y-%m-%d %T")],
16800                                            )));
16801                                        let parse_datetime =
16802                                            Expression::Function(Box::new(Function::new(
16803                                                "PARSE_DATETIME".to_string(),
16804                                                vec![
16805                                                    date_format,
16806                                                    Expression::string("yyyy-MM-dd HH:mm:ss"),
16807                                                ],
16808                                            )));
16809                                        let coalesce =
16810                                            Expression::Function(Box::new(Function::new(
16811                                                "COALESCE".to_string(),
16812                                                vec![try_expr, parse_datetime],
16813                                            )));
16814                                        Ok(Expression::Function(Box::new(Function::new(
16815                                            "TO_UNIXTIME".to_string(),
16816                                            vec![coalesce],
16817                                        ))))
16818                                    }
16819                                    DialectType::Presto | DialectType::Trino => {
16820                                        Ok(Expression::Function(Box::new(Function::new(
16821                                            "TO_UNIXTIME".to_string(),
16822                                            vec![arg],
16823                                        ))))
16824                                    }
16825                                    _ => Ok(Expression::Function(Box::new(Function::new(
16826                                        "UNIX_TIMESTAMP".to_string(),
16827                                        vec![arg],
16828                                    )))),
16829                                }
16830                            }
16831                            // TO_UNIX_TIMESTAMP(x) -> UNIX_TIMESTAMP(x) for Spark/Hive
16832                            "TO_UNIX_TIMESTAMP" if f.args.len() >= 1 => match target {
16833                                DialectType::Spark
16834                                | DialectType::Databricks
16835                                | DialectType::Hive => Ok(Expression::Function(Box::new(
16836                                    Function::new("UNIX_TIMESTAMP".to_string(), f.args),
16837                                ))),
16838                                _ => Ok(Expression::Function(Box::new(Function::new(
16839                                    "TO_UNIX_TIMESTAMP".to_string(),
16840                                    f.args,
16841                                )))),
16842                            },
16843                            // CURDATE() -> CURRENT_DATE
16844                            "CURDATE" => {
16845                                Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
16846                            }
16847                            // CURTIME() -> CURRENT_TIME
16848                            "CURTIME" => {
16849                                Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
16850                                    precision: None,
16851                                }))
16852                            }
16853                            // ARRAY_SORT(x) or ARRAY_SORT(x, lambda) -> SORT_ARRAY(x) for Hive, LIST_SORT for DuckDB
16854                            "ARRAY_SORT" if f.args.len() >= 1 => {
16855                                match target {
16856                                    DialectType::Hive => {
16857                                        let mut args = f.args;
16858                                        args.truncate(1); // Drop lambda comparator
16859                                        Ok(Expression::Function(Box::new(Function::new(
16860                                            "SORT_ARRAY".to_string(),
16861                                            args,
16862                                        ))))
16863                                    }
16864                                    DialectType::DuckDB
16865                                        if matches!(source, DialectType::Snowflake) =>
16866                                    {
16867                                        // Snowflake ARRAY_SORT(arr[, asc_bool[, nulls_first_bool]]) -> DuckDB LIST_SORT(arr[, 'ASC'/'DESC'[, 'NULLS FIRST']])
16868                                        let mut args_iter = f.args.into_iter();
16869                                        let arr = args_iter.next().unwrap();
16870                                        let asc_arg = args_iter.next();
16871                                        let nulls_first_arg = args_iter.next();
16872
16873                                        let is_asc_bool = asc_arg
16874                                            .as_ref()
16875                                            .map(|a| matches!(a, Expression::Boolean(_)))
16876                                            .unwrap_or(false);
16877                                        let is_nf_bool = nulls_first_arg
16878                                            .as_ref()
16879                                            .map(|a| matches!(a, Expression::Boolean(_)))
16880                                            .unwrap_or(false);
16881
16882                                        // No boolean args: pass through as-is
16883                                        if !is_asc_bool && !is_nf_bool {
16884                                            let mut result_args = vec![arr];
16885                                            if let Some(asc) = asc_arg {
16886                                                result_args.push(asc);
16887                                                if let Some(nf) = nulls_first_arg {
16888                                                    result_args.push(nf);
16889                                                }
16890                                            }
16891                                            Ok(Expression::Function(Box::new(Function::new(
16892                                                "LIST_SORT".to_string(),
16893                                                result_args,
16894                                            ))))
16895                                        } else {
16896                                            // Has boolean args: convert to DuckDB LIST_SORT format
16897                                            let descending = matches!(&asc_arg, Some(Expression::Boolean(b)) if !b.value);
16898
16899                                            // Snowflake defaults: nulls_first = TRUE for DESC, FALSE for ASC
16900                                            let nulls_are_first = match &nulls_first_arg {
16901                                                Some(Expression::Boolean(b)) => b.value,
16902                                                None if is_asc_bool => descending, // Snowflake default
16903                                                _ => false,
16904                                            };
16905                                            let nulls_first_sql = if nulls_are_first {
16906                                                Some(Expression::string("NULLS FIRST"))
16907                                            } else {
16908                                                None
16909                                            };
16910
16911                                            if !is_asc_bool {
16912                                                // asc is non-boolean expression, nulls_first is boolean
16913                                                let mut result_args = vec![arr];
16914                                                if let Some(asc) = asc_arg {
16915                                                    result_args.push(asc);
16916                                                }
16917                                                if let Some(nf) = nulls_first_sql {
16918                                                    result_args.push(nf);
16919                                                }
16920                                                Ok(Expression::Function(Box::new(Function::new(
16921                                                    "LIST_SORT".to_string(),
16922                                                    result_args,
16923                                                ))))
16924                                            } else {
16925                                                if !descending && !nulls_are_first {
16926                                                    // ASC, NULLS LAST (default) -> LIST_SORT(arr)
16927                                                    Ok(Expression::Function(Box::new(
16928                                                        Function::new(
16929                                                            "LIST_SORT".to_string(),
16930                                                            vec![arr],
16931                                                        ),
16932                                                    )))
16933                                                } else if descending && !nulls_are_first {
16934                                                    // DESC, NULLS LAST -> ARRAY_REVERSE_SORT(arr)
16935                                                    Ok(Expression::Function(Box::new(
16936                                                        Function::new(
16937                                                            "ARRAY_REVERSE_SORT".to_string(),
16938                                                            vec![arr],
16939                                                        ),
16940                                                    )))
16941                                                } else {
16942                                                    // NULLS FIRST -> LIST_SORT(arr, 'ASC'/'DESC', 'NULLS FIRST')
16943                                                    let order_str =
16944                                                        if descending { "DESC" } else { "ASC" };
16945                                                    Ok(Expression::Function(Box::new(
16946                                                        Function::new(
16947                                                            "LIST_SORT".to_string(),
16948                                                            vec![
16949                                                                arr,
16950                                                                Expression::string(order_str),
16951                                                                Expression::string("NULLS FIRST"),
16952                                                            ],
16953                                                        ),
16954                                                    )))
16955                                                }
16956                                            }
16957                                        }
16958                                    }
16959                                    DialectType::DuckDB => {
16960                                        // Non-Snowflake source: ARRAY_SORT(x, lambda) -> ARRAY_SORT(x) (drop comparator)
16961                                        let mut args = f.args;
16962                                        args.truncate(1); // Drop lambda comparator for DuckDB
16963                                        Ok(Expression::Function(Box::new(Function::new(
16964                                            "ARRAY_SORT".to_string(),
16965                                            args,
16966                                        ))))
16967                                    }
16968                                    _ => Ok(Expression::Function(f)),
16969                                }
16970                            }
16971                            // SORT_ARRAY(x) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for Presto/Trino, keep for Hive/Spark
16972                            "SORT_ARRAY" if f.args.len() == 1 => match target {
16973                                DialectType::Hive
16974                                | DialectType::Spark
16975                                | DialectType::Databricks => Ok(Expression::Function(f)),
16976                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
16977                                    Function::new("LIST_SORT".to_string(), f.args),
16978                                ))),
16979                                _ => Ok(Expression::Function(Box::new(Function::new(
16980                                    "ARRAY_SORT".to_string(),
16981                                    f.args,
16982                                )))),
16983                            },
16984                            // SORT_ARRAY(x, FALSE) -> ARRAY_REVERSE_SORT(x) for DuckDB, ARRAY_SORT(x, lambda) for Presto
16985                            "SORT_ARRAY" if f.args.len() == 2 => {
16986                                let is_desc =
16987                                    matches!(&f.args[1], Expression::Boolean(b) if !b.value);
16988                                if is_desc {
16989                                    match target {
16990                                        DialectType::DuckDB => {
16991                                            Ok(Expression::Function(Box::new(Function::new(
16992                                                "ARRAY_REVERSE_SORT".to_string(),
16993                                                vec![f.args.into_iter().next().unwrap()],
16994                                            ))))
16995                                        }
16996                                        DialectType::Presto | DialectType::Trino => {
16997                                            let arr_arg = f.args.into_iter().next().unwrap();
16998                                            let a = Expression::Column(Box::new(
16999                                                crate::expressions::Column {
17000                                                    name: crate::expressions::Identifier::new("a"),
17001                                                    table: None,
17002                                                    join_mark: false,
17003                                                    trailing_comments: Vec::new(),
17004                                                    span: None,
17005                                                    inferred_type: None,
17006                                                },
17007                                            ));
17008                                            let b = Expression::Column(Box::new(
17009                                                crate::expressions::Column {
17010                                                    name: crate::expressions::Identifier::new("b"),
17011                                                    table: None,
17012                                                    join_mark: false,
17013                                                    trailing_comments: Vec::new(),
17014                                                    span: None,
17015                                                    inferred_type: None,
17016                                                },
17017                                            ));
17018                                            let case_expr = Expression::Case(Box::new(
17019                                                crate::expressions::Case {
17020                                                    operand: None,
17021                                                    whens: vec![
17022                                                        (
17023                                                            Expression::Lt(Box::new(
17024                                                                BinaryOp::new(a.clone(), b.clone()),
17025                                                            )),
17026                                                            Expression::Literal(Box::new(
17027                                                                Literal::Number("1".to_string()),
17028                                                            )),
17029                                                        ),
17030                                                        (
17031                                                            Expression::Gt(Box::new(
17032                                                                BinaryOp::new(a.clone(), b.clone()),
17033                                                            )),
17034                                                            Expression::Literal(Box::new(
17035                                                                Literal::Number("-1".to_string()),
17036                                                            )),
17037                                                        ),
17038                                                    ],
17039                                                    else_: Some(Expression::Literal(Box::new(
17040                                                        Literal::Number("0".to_string()),
17041                                                    ))),
17042                                                    comments: Vec::new(),
17043                                                    inferred_type: None,
17044                                                },
17045                                            ));
17046                                            let lambda = Expression::Lambda(Box::new(
17047                                                crate::expressions::LambdaExpr {
17048                                                    parameters: vec![
17049                                                        crate::expressions::Identifier::new("a"),
17050                                                        crate::expressions::Identifier::new("b"),
17051                                                    ],
17052                                                    body: case_expr,
17053                                                    colon: false,
17054                                                    parameter_types: Vec::new(),
17055                                                },
17056                                            ));
17057                                            Ok(Expression::Function(Box::new(Function::new(
17058                                                "ARRAY_SORT".to_string(),
17059                                                vec![arr_arg, lambda],
17060                                            ))))
17061                                        }
17062                                        _ => Ok(Expression::Function(f)),
17063                                    }
17064                                } else {
17065                                    // SORT_ARRAY(x, TRUE) -> LIST_SORT(x) for DuckDB, ARRAY_SORT(x) for others
17066                                    match target {
17067                                        DialectType::Hive => Ok(Expression::Function(f)),
17068                                        DialectType::DuckDB => {
17069                                            Ok(Expression::Function(Box::new(Function::new(
17070                                                "LIST_SORT".to_string(),
17071                                                vec![f.args.into_iter().next().unwrap()],
17072                                            ))))
17073                                        }
17074                                        _ => Ok(Expression::Function(Box::new(Function::new(
17075                                            "ARRAY_SORT".to_string(),
17076                                            vec![f.args.into_iter().next().unwrap()],
17077                                        )))),
17078                                    }
17079                                }
17080                            }
17081                            // LEFT(x, n), RIGHT(x, n) -> SUBSTRING for targets without LEFT/RIGHT
17082                            "LEFT" if f.args.len() == 2 => {
17083                                match target {
17084                                    DialectType::Hive
17085                                    | DialectType::Presto
17086                                    | DialectType::Trino
17087                                    | DialectType::Athena => {
17088                                        let x = f.args[0].clone();
17089                                        let n = f.args[1].clone();
17090                                        Ok(Expression::Function(Box::new(Function::new(
17091                                            "SUBSTRING".to_string(),
17092                                            vec![x, Expression::number(1), n],
17093                                        ))))
17094                                    }
17095                                    DialectType::Spark | DialectType::Databricks
17096                                        if matches!(
17097                                            source,
17098                                            DialectType::TSQL | DialectType::Fabric
17099                                        ) =>
17100                                    {
17101                                        // TSQL LEFT(x, n) -> LEFT(CAST(x AS STRING), n) for Spark
17102                                        let x = f.args[0].clone();
17103                                        let n = f.args[1].clone();
17104                                        let cast_x = Expression::Cast(Box::new(Cast {
17105                                            this: x,
17106                                            to: DataType::VarChar {
17107                                                length: None,
17108                                                parenthesized_length: false,
17109                                            },
17110                                            double_colon_syntax: false,
17111                                            trailing_comments: Vec::new(),
17112                                            format: None,
17113                                            default: None,
17114                                            inferred_type: None,
17115                                        }));
17116                                        Ok(Expression::Function(Box::new(Function::new(
17117                                            "LEFT".to_string(),
17118                                            vec![cast_x, n],
17119                                        ))))
17120                                    }
17121                                    _ => Ok(Expression::Function(f)),
17122                                }
17123                            }
17124                            "RIGHT" if f.args.len() == 2 => {
17125                                match target {
17126                                    DialectType::Hive
17127                                    | DialectType::Presto
17128                                    | DialectType::Trino
17129                                    | DialectType::Athena => {
17130                                        let x = f.args[0].clone();
17131                                        let n = f.args[1].clone();
17132                                        // SUBSTRING(x, LENGTH(x) - (n - 1))
17133                                        let len_x = Expression::Function(Box::new(Function::new(
17134                                            "LENGTH".to_string(),
17135                                            vec![x.clone()],
17136                                        )));
17137                                        let n_minus_1 = Expression::Sub(Box::new(
17138                                            crate::expressions::BinaryOp::new(
17139                                                n,
17140                                                Expression::number(1),
17141                                            ),
17142                                        ));
17143                                        let n_minus_1_paren = Expression::Paren(Box::new(
17144                                            crate::expressions::Paren {
17145                                                this: n_minus_1,
17146                                                trailing_comments: Vec::new(),
17147                                            },
17148                                        ));
17149                                        let offset = Expression::Sub(Box::new(
17150                                            crate::expressions::BinaryOp::new(
17151                                                len_x,
17152                                                n_minus_1_paren,
17153                                            ),
17154                                        ));
17155                                        Ok(Expression::Function(Box::new(Function::new(
17156                                            "SUBSTRING".to_string(),
17157                                            vec![x, offset],
17158                                        ))))
17159                                    }
17160                                    DialectType::Spark | DialectType::Databricks
17161                                        if matches!(
17162                                            source,
17163                                            DialectType::TSQL | DialectType::Fabric
17164                                        ) =>
17165                                    {
17166                                        // TSQL RIGHT(x, n) -> RIGHT(CAST(x AS STRING), n) for Spark
17167                                        let x = f.args[0].clone();
17168                                        let n = f.args[1].clone();
17169                                        let cast_x = Expression::Cast(Box::new(Cast {
17170                                            this: x,
17171                                            to: DataType::VarChar {
17172                                                length: None,
17173                                                parenthesized_length: false,
17174                                            },
17175                                            double_colon_syntax: false,
17176                                            trailing_comments: Vec::new(),
17177                                            format: None,
17178                                            default: None,
17179                                            inferred_type: None,
17180                                        }));
17181                                        Ok(Expression::Function(Box::new(Function::new(
17182                                            "RIGHT".to_string(),
17183                                            vec![cast_x, n],
17184                                        ))))
17185                                    }
17186                                    _ => Ok(Expression::Function(f)),
17187                                }
17188                            }
17189                            // MAP_FROM_ARRAYS(keys, vals) -> target-specific map construction
17190                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
17191                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
17192                                    Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
17193                                ))),
17194                                DialectType::Spark | DialectType::Databricks => {
17195                                    Ok(Expression::Function(Box::new(Function::new(
17196                                        "MAP_FROM_ARRAYS".to_string(),
17197                                        f.args,
17198                                    ))))
17199                                }
17200                                _ => Ok(Expression::Function(Box::new(Function::new(
17201                                    "MAP".to_string(),
17202                                    f.args,
17203                                )))),
17204                            },
17205                            // LIKE(foo, 'pat') -> foo LIKE 'pat'; LIKE(foo, 'pat', '!') -> foo LIKE 'pat' ESCAPE '!'
17206                            // SQLite uses LIKE(pattern, string[, escape]) with args in reverse order
17207                            "LIKE" if f.args.len() >= 2 => {
17208                                let (this, pattern) = if matches!(source, DialectType::SQLite) {
17209                                    // SQLite: LIKE(pattern, string) -> string LIKE pattern
17210                                    (f.args[1].clone(), f.args[0].clone())
17211                                } else {
17212                                    // Standard: LIKE(string, pattern) -> string LIKE pattern
17213                                    (f.args[0].clone(), f.args[1].clone())
17214                                };
17215                                let escape = if f.args.len() >= 3 {
17216                                    Some(f.args[2].clone())
17217                                } else {
17218                                    None
17219                                };
17220                                Ok(Expression::Like(Box::new(crate::expressions::LikeOp {
17221                                    left: this,
17222                                    right: pattern,
17223                                    escape,
17224                                    quantifier: None,
17225                                    inferred_type: None,
17226                                })))
17227                            }
17228                            // ILIKE(foo, 'pat') -> foo ILIKE 'pat'
17229                            "ILIKE" if f.args.len() >= 2 => {
17230                                let this = f.args[0].clone();
17231                                let pattern = f.args[1].clone();
17232                                let escape = if f.args.len() >= 3 {
17233                                    Some(f.args[2].clone())
17234                                } else {
17235                                    None
17236                                };
17237                                Ok(Expression::ILike(Box::new(crate::expressions::LikeOp {
17238                                    left: this,
17239                                    right: pattern,
17240                                    escape,
17241                                    quantifier: None,
17242                                    inferred_type: None,
17243                                })))
17244                            }
17245                            // CHAR(n) -> CHR(n) for non-MySQL/non-TSQL targets
17246                            "CHAR" if f.args.len() == 1 => match target {
17247                                DialectType::MySQL
17248                                | DialectType::SingleStore
17249                                | DialectType::TSQL => Ok(Expression::Function(f)),
17250                                _ => Ok(Expression::Function(Box::new(Function::new(
17251                                    "CHR".to_string(),
17252                                    f.args,
17253                                )))),
17254                            },
17255                            // CONCAT(a, b) -> a || b for PostgreSQL
17256                            "CONCAT"
17257                                if f.args.len() == 2
17258                                    && matches!(target, DialectType::PostgreSQL)
17259                                    && matches!(
17260                                        source,
17261                                        DialectType::ClickHouse | DialectType::MySQL
17262                                    ) =>
17263                            {
17264                                let mut args = f.args;
17265                                let right = args.pop().unwrap();
17266                                let left = args.pop().unwrap();
17267                                Ok(Expression::DPipe(Box::new(crate::expressions::DPipe {
17268                                    this: Box::new(left),
17269                                    expression: Box::new(right),
17270                                    safe: None,
17271                                })))
17272                            }
17273                            // ARRAY_TO_STRING(arr, delim) -> target-specific
17274                            "ARRAY_TO_STRING"
17275                                if f.args.len() == 2
17276                                    && matches!(target, DialectType::DuckDB)
17277                                    && matches!(source, DialectType::Snowflake) =>
17278                            {
17279                                let mut args = f.args;
17280                                let arr = args.remove(0);
17281                                let sep = args.remove(0);
17282                                // sep IS NULL
17283                                let sep_is_null = Expression::IsNull(Box::new(IsNull {
17284                                    this: sep.clone(),
17285                                    not: false,
17286                                    postfix_form: false,
17287                                }));
17288                                // COALESCE(CAST(x AS TEXT), '')
17289                                let cast_x = Expression::Cast(Box::new(Cast {
17290                                    this: Expression::Identifier(Identifier::new("x")),
17291                                    to: DataType::Text,
17292                                    trailing_comments: Vec::new(),
17293                                    double_colon_syntax: false,
17294                                    format: None,
17295                                    default: None,
17296                                    inferred_type: None,
17297                                }));
17298                                let coalesce = Expression::Coalesce(Box::new(
17299                                    crate::expressions::VarArgFunc {
17300                                        original_name: None,
17301                                        expressions: vec![
17302                                            cast_x,
17303                                            Expression::Literal(Box::new(Literal::String(
17304                                                String::new(),
17305                                            ))),
17306                                        ],
17307                                        inferred_type: None,
17308                                    },
17309                                ));
17310                                let lambda =
17311                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
17312                                        parameters: vec![Identifier::new("x")],
17313                                        body: coalesce,
17314                                        colon: false,
17315                                        parameter_types: Vec::new(),
17316                                    }));
17317                                let list_transform = Expression::Function(Box::new(Function::new(
17318                                    "LIST_TRANSFORM".to_string(),
17319                                    vec![arr, lambda],
17320                                )));
17321                                let array_to_string =
17322                                    Expression::Function(Box::new(Function::new(
17323                                        "ARRAY_TO_STRING".to_string(),
17324                                        vec![list_transform, sep],
17325                                    )));
17326                                Ok(Expression::Case(Box::new(Case {
17327                                    operand: None,
17328                                    whens: vec![(sep_is_null, Expression::Null(Null))],
17329                                    else_: Some(array_to_string),
17330                                    comments: Vec::new(),
17331                                    inferred_type: None,
17332                                })))
17333                            }
17334                            "ARRAY_TO_STRING" if f.args.len() >= 2 => match target {
17335                                DialectType::Presto | DialectType::Trino => {
17336                                    Ok(Expression::Function(Box::new(Function::new(
17337                                        "ARRAY_JOIN".to_string(),
17338                                        f.args,
17339                                    ))))
17340                                }
17341                                DialectType::TSQL | DialectType::Fabric
17342                                    if matches!(
17343                                        source,
17344                                        DialectType::PostgreSQL | DialectType::CockroachDB
17345                                    ) =>
17346                                {
17347                                    Ok(Expression::Function(f))
17348                                }
17349                                DialectType::TSQL => Ok(Expression::Function(Box::new(
17350                                    Function::new("STRING_AGG".to_string(), f.args),
17351                                ))),
17352                                _ => Ok(Expression::Function(f)),
17353                            },
17354                            // ARRAY_CONCAT / LIST_CONCAT -> target-specific
17355                            "ARRAY_CONCAT" | "LIST_CONCAT" if f.args.len() == 2 => match target {
17356                                DialectType::Spark
17357                                | DialectType::Databricks
17358                                | DialectType::Hive => Ok(Expression::Function(Box::new(
17359                                    Function::new("CONCAT".to_string(), f.args),
17360                                ))),
17361                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
17362                                    Function::new("ARRAY_CAT".to_string(), f.args),
17363                                ))),
17364                                DialectType::Redshift => Ok(Expression::Function(Box::new(
17365                                    Function::new("ARRAY_CONCAT".to_string(), f.args),
17366                                ))),
17367                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17368                                    Function::new("ARRAY_CAT".to_string(), f.args),
17369                                ))),
17370                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
17371                                    Function::new("LIST_CONCAT".to_string(), f.args),
17372                                ))),
17373                                DialectType::Presto | DialectType::Trino => {
17374                                    Ok(Expression::Function(Box::new(Function::new(
17375                                        "CONCAT".to_string(),
17376                                        f.args,
17377                                    ))))
17378                                }
17379                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
17380                                    Function::new("ARRAY_CONCAT".to_string(), f.args),
17381                                ))),
17382                                _ => Ok(Expression::Function(f)),
17383                            },
17384                            // ARRAY_CONTAINS(arr, x) / HAS(arr, x) / CONTAINS(arr, x) normalization
17385                            "HAS" if f.args.len() == 2 => match target {
17386                                DialectType::Spark
17387                                | DialectType::Databricks
17388                                | DialectType::Hive => Ok(Expression::Function(Box::new(
17389                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
17390                                ))),
17391                                DialectType::Presto | DialectType::Trino => {
17392                                    Ok(Expression::Function(Box::new(Function::new(
17393                                        "CONTAINS".to_string(),
17394                                        f.args,
17395                                    ))))
17396                                }
17397                                _ => Ok(Expression::Function(f)),
17398                            },
17399                            // NVL(a, b, c, d) -> COALESCE(a, b, c, d) - NVL should keep all args
17400                            "NVL" if f.args.len() > 2 => Ok(Expression::Function(Box::new(
17401                                Function::new("COALESCE".to_string(), f.args),
17402                            ))),
17403                            // ISNULL(x) in MySQL -> (x IS NULL)
17404                            "ISNULL"
17405                                if f.args.len() == 1
17406                                    && matches!(source, DialectType::MySQL)
17407                                    && matches!(target, DialectType::MySQL) =>
17408                            {
17409                                let arg = f.args.into_iter().next().unwrap();
17410                                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
17411                                    this: Expression::IsNull(Box::new(
17412                                        crate::expressions::IsNull {
17413                                            this: arg,
17414                                            not: false,
17415                                            postfix_form: false,
17416                                        },
17417                                    )),
17418                                    trailing_comments: Vec::new(),
17419                                })))
17420                            }
17421                            // MONTHNAME(x) -> DATE_FORMAT(x, '%M') for MySQL -> MySQL
17422                            "MONTHNAME"
17423                                if f.args.len() == 1 && matches!(target, DialectType::MySQL) =>
17424                            {
17425                                let arg = f.args.into_iter().next().unwrap();
17426                                Ok(Expression::Function(Box::new(Function::new(
17427                                    "DATE_FORMAT".to_string(),
17428                                    vec![arg, Expression::string("%M")],
17429                                ))))
17430                            }
17431                            // ClickHouse splitByString('s', x) -> DuckDB STR_SPLIT(x, 's') / Hive SPLIT(x, CONCAT('\\Q', 's', '\\E'))
17432                            "SPLITBYSTRING" if f.args.len() == 2 => {
17433                                let sep = f.args[0].clone();
17434                                let str_arg = f.args[1].clone();
17435                                match target {
17436                                    DialectType::DuckDB => Ok(Expression::Function(Box::new(
17437                                        Function::new("STR_SPLIT".to_string(), vec![str_arg, sep]),
17438                                    ))),
17439                                    DialectType::Doris => {
17440                                        Ok(Expression::Function(Box::new(Function::new(
17441                                            "SPLIT_BY_STRING".to_string(),
17442                                            vec![str_arg, sep],
17443                                        ))))
17444                                    }
17445                                    DialectType::Hive
17446                                    | DialectType::Spark
17447                                    | DialectType::Databricks => {
17448                                        // SPLIT(x, CONCAT('\\Q', sep, '\\E'))
17449                                        let escaped =
17450                                            Expression::Function(Box::new(Function::new(
17451                                                "CONCAT".to_string(),
17452                                                vec![
17453                                                    Expression::string("\\Q"),
17454                                                    sep,
17455                                                    Expression::string("\\E"),
17456                                                ],
17457                                            )));
17458                                        Ok(Expression::Function(Box::new(Function::new(
17459                                            "SPLIT".to_string(),
17460                                            vec![str_arg, escaped],
17461                                        ))))
17462                                    }
17463                                    _ => Ok(Expression::Function(f)),
17464                                }
17465                            }
17466                            // ClickHouse splitByRegexp('pattern', x) -> DuckDB STR_SPLIT_REGEX(x, 'pattern')
17467                            "SPLITBYREGEXP" if f.args.len() == 2 => {
17468                                let sep = f.args[0].clone();
17469                                let str_arg = f.args[1].clone();
17470                                match target {
17471                                    DialectType::DuckDB => {
17472                                        Ok(Expression::Function(Box::new(Function::new(
17473                                            "STR_SPLIT_REGEX".to_string(),
17474                                            vec![str_arg, sep],
17475                                        ))))
17476                                    }
17477                                    DialectType::Hive
17478                                    | DialectType::Spark
17479                                    | DialectType::Databricks => {
17480                                        Ok(Expression::Function(Box::new(Function::new(
17481                                            "SPLIT".to_string(),
17482                                            vec![str_arg, sep],
17483                                        ))))
17484                                    }
17485                                    _ => Ok(Expression::Function(f)),
17486                                }
17487                            }
17488                            // ClickHouse toMonday(x) -> DATE_TRUNC('WEEK', x) / DATE_TRUNC(x, 'WEEK') for Doris
17489                            "TOMONDAY" => {
17490                                if f.args.len() == 1 {
17491                                    let arg = f.args.into_iter().next().unwrap();
17492                                    match target {
17493                                        DialectType::Doris => {
17494                                            Ok(Expression::Function(Box::new(Function::new(
17495                                                "DATE_TRUNC".to_string(),
17496                                                vec![arg, Expression::string("WEEK")],
17497                                            ))))
17498                                        }
17499                                        _ => Ok(Expression::Function(Box::new(Function::new(
17500                                            "DATE_TRUNC".to_string(),
17501                                            vec![Expression::string("WEEK"), arg],
17502                                        )))),
17503                                    }
17504                                } else {
17505                                    Ok(Expression::Function(f))
17506                                }
17507                            }
17508                            // COLLECT_LIST with FILTER(WHERE x IS NOT NULL) for targets that need it
17509                            "COLLECT_LIST" if f.args.len() == 1 => match target {
17510                                DialectType::Spark
17511                                | DialectType::Databricks
17512                                | DialectType::Hive => Ok(Expression::Function(f)),
17513                                _ => Ok(Expression::Function(Box::new(Function::new(
17514                                    "ARRAY_AGG".to_string(),
17515                                    f.args,
17516                                )))),
17517                            },
17518                            // TO_CHAR(x) with 1 arg -> CAST(x AS STRING) for Doris
17519                            "TO_CHAR"
17520                                if f.args.len() == 1 && matches!(target, DialectType::Doris) =>
17521                            {
17522                                let arg = f.args.into_iter().next().unwrap();
17523                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
17524                                    this: arg,
17525                                    to: DataType::Custom {
17526                                        name: "STRING".to_string(),
17527                                    },
17528                                    double_colon_syntax: false,
17529                                    trailing_comments: Vec::new(),
17530                                    format: None,
17531                                    default: None,
17532                                    inferred_type: None,
17533                                })))
17534                            }
17535                            // DBMS_RANDOM.VALUE() -> RANDOM() for PostgreSQL
17536                            "DBMS_RANDOM.VALUE" if f.args.is_empty() => match target {
17537                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17538                                    Function::new("RANDOM".to_string(), vec![]),
17539                                ))),
17540                                _ => Ok(Expression::Function(f)),
17541                            },
17542                            // ClickHouse formatDateTime -> target-specific
17543                            "FORMATDATETIME" if f.args.len() >= 2 => match target {
17544                                DialectType::MySQL => Ok(Expression::Function(Box::new(
17545                                    Function::new("DATE_FORMAT".to_string(), f.args),
17546                                ))),
17547                                _ => Ok(Expression::Function(f)),
17548                            },
17549                            // REPLICATE('x', n) -> REPEAT('x', n) for non-TSQL targets
17550                            "REPLICATE" if f.args.len() == 2 => match target {
17551                                DialectType::TSQL => Ok(Expression::Function(f)),
17552                                _ => Ok(Expression::Function(Box::new(Function::new(
17553                                    "REPEAT".to_string(),
17554                                    f.args,
17555                                )))),
17556                            },
17557                            // LEN(x) -> LENGTH(x) for non-TSQL targets
17558                            // No CAST needed when arg is already a string literal
17559                            "LEN" if f.args.len() == 1 => {
17560                                match target {
17561                                    DialectType::TSQL => Ok(Expression::Function(f)),
17562                                    DialectType::Spark | DialectType::Databricks => {
17563                                        let arg = f.args.into_iter().next().unwrap();
17564                                        // Don't wrap string literals with CAST - they're already strings
17565                                        let is_string = matches!(
17566                                            &arg,
17567                                            Expression::Literal(lit) if matches!(lit.as_ref(), crate::expressions::Literal::String(_))
17568                                        );
17569                                        let final_arg = if is_string {
17570                                            arg
17571                                        } else {
17572                                            Expression::Cast(Box::new(Cast {
17573                                                this: arg,
17574                                                to: DataType::VarChar {
17575                                                    length: None,
17576                                                    parenthesized_length: false,
17577                                                },
17578                                                double_colon_syntax: false,
17579                                                trailing_comments: Vec::new(),
17580                                                format: None,
17581                                                default: None,
17582                                                inferred_type: None,
17583                                            }))
17584                                        };
17585                                        Ok(Expression::Function(Box::new(Function::new(
17586                                            "LENGTH".to_string(),
17587                                            vec![final_arg],
17588                                        ))))
17589                                    }
17590                                    _ => {
17591                                        let arg = f.args.into_iter().next().unwrap();
17592                                        Ok(Expression::Function(Box::new(Function::new(
17593                                            "LENGTH".to_string(),
17594                                            vec![arg],
17595                                        ))))
17596                                    }
17597                                }
17598                            }
17599                            // COUNT_BIG(x) -> COUNT(x) for non-TSQL targets
17600                            "COUNT_BIG" if f.args.len() == 1 => match target {
17601                                DialectType::TSQL => Ok(Expression::Function(f)),
17602                                _ => Ok(Expression::Function(Box::new(Function::new(
17603                                    "COUNT".to_string(),
17604                                    f.args,
17605                                )))),
17606                            },
17607                            // DATEFROMPARTS(y, m, d) -> MAKE_DATE(y, m, d) for non-TSQL targets
17608                            "DATEFROMPARTS" if f.args.len() == 3 => match target {
17609                                DialectType::TSQL => Ok(Expression::Function(f)),
17610                                _ => Ok(Expression::Function(Box::new(Function::new(
17611                                    "MAKE_DATE".to_string(),
17612                                    f.args,
17613                                )))),
17614                            },
17615                            // REGEXP_LIKE(str, pattern) -> RegexpLike expression (target-specific output)
17616                            "REGEXP_LIKE" if f.args.len() >= 2 => {
17617                                let str_expr = f.args[0].clone();
17618                                let pattern = f.args[1].clone();
17619                                let flags = if f.args.len() >= 3 {
17620                                    Some(f.args[2].clone())
17621                                } else {
17622                                    None
17623                                };
17624                                match target {
17625                                    DialectType::DuckDB => {
17626                                        let mut new_args = vec![str_expr, pattern];
17627                                        if let Some(fl) = flags {
17628                                            new_args.push(fl);
17629                                        }
17630                                        Ok(Expression::Function(Box::new(Function::new(
17631                                            "REGEXP_MATCHES".to_string(),
17632                                            new_args,
17633                                        ))))
17634                                    }
17635                                    DialectType::TSQL | DialectType::Fabric
17636                                        if flags.is_none()
17637                                            && matches!(
17638                                                source,
17639                                                DialectType::PostgreSQL | DialectType::CockroachDB
17640                                            ) =>
17641                                    {
17642                                        Ok(Self::build_tsql_regex_patindex_predicate(
17643                                            str_expr, pattern, false,
17644                                        ))
17645                                    }
17646                                    _ => Ok(Expression::RegexpLike(Box::new(
17647                                        crate::expressions::RegexpFunc {
17648                                            this: str_expr,
17649                                            pattern,
17650                                            flags,
17651                                        },
17652                                    ))),
17653                                }
17654                            }
17655                            // ClickHouse arrayJoin -> UNNEST for PostgreSQL
17656                            "ARRAYJOIN" if f.args.len() == 1 => match target {
17657                                DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
17658                                    Function::new("UNNEST".to_string(), f.args),
17659                                ))),
17660                                _ => Ok(Expression::Function(f)),
17661                            },
17662                            // DATETIMEFROMPARTS(y, m, d, h, mi, s, ms) -> MAKE_TIMESTAMP / TIMESTAMP_FROM_PARTS
17663                            "DATETIMEFROMPARTS" if f.args.len() == 7 => {
17664                                match target {
17665                                    DialectType::TSQL => Ok(Expression::Function(f)),
17666                                    DialectType::DuckDB => {
17667                                        // MAKE_TIMESTAMP(y, m, d, h, mi, s + (ms / 1000.0))
17668                                        let mut args = f.args;
17669                                        let ms = args.pop().unwrap();
17670                                        let s = args.pop().unwrap();
17671                                        // s + (ms / 1000.0)
17672                                        let ms_frac = Expression::Div(Box::new(BinaryOp::new(
17673                                            ms,
17674                                            Expression::Literal(Box::new(
17675                                                crate::expressions::Literal::Number(
17676                                                    "1000.0".to_string(),
17677                                                ),
17678                                            )),
17679                                        )));
17680                                        let s_with_ms = Expression::Add(Box::new(BinaryOp::new(
17681                                            s,
17682                                            Expression::Paren(Box::new(Paren {
17683                                                this: ms_frac,
17684                                                trailing_comments: vec![],
17685                                            })),
17686                                        )));
17687                                        args.push(s_with_ms);
17688                                        Ok(Expression::Function(Box::new(Function::new(
17689                                            "MAKE_TIMESTAMP".to_string(),
17690                                            args,
17691                                        ))))
17692                                    }
17693                                    DialectType::Snowflake => {
17694                                        // TIMESTAMP_FROM_PARTS(y, m, d, h, mi, s, ms * 1000000)
17695                                        let mut args = f.args;
17696                                        let ms = args.pop().unwrap();
17697                                        // ms * 1000000
17698                                        let ns = Expression::Mul(Box::new(BinaryOp::new(
17699                                            ms,
17700                                            Expression::number(1000000),
17701                                        )));
17702                                        args.push(ns);
17703                                        Ok(Expression::Function(Box::new(Function::new(
17704                                            "TIMESTAMP_FROM_PARTS".to_string(),
17705                                            args,
17706                                        ))))
17707                                    }
17708                                    _ => {
17709                                        // Default: keep function name for other targets
17710                                        Ok(Expression::Function(Box::new(Function::new(
17711                                            "DATETIMEFROMPARTS".to_string(),
17712                                            f.args,
17713                                        ))))
17714                                    }
17715                                }
17716                            }
17717                            // CONVERT(type, expr [, style]) -> CAST(expr AS type) for non-TSQL targets
17718                            // TRY_CONVERT(type, expr [, style]) -> TRY_CAST(expr AS type) for non-TSQL targets
17719                            "CONVERT" | "TRY_CONVERT" if f.args.len() >= 2 => {
17720                                let is_try = name == "TRY_CONVERT";
17721                                let type_expr = f.args[0].clone();
17722                                let value_expr = f.args[1].clone();
17723                                let style = if f.args.len() >= 3 {
17724                                    Some(&f.args[2])
17725                                } else {
17726                                    None
17727                                };
17728
17729                                // For TSQL->TSQL, normalize types and preserve CONVERT/TRY_CONVERT
17730                                if matches!(target, DialectType::TSQL) {
17731                                    let normalized_type = match &type_expr {
17732                                        Expression::DataType(dt) => {
17733                                            let new_dt = match dt {
17734                                                DataType::Int { .. } => DataType::Custom {
17735                                                    name: "INTEGER".to_string(),
17736                                                },
17737                                                _ => dt.clone(),
17738                                            };
17739                                            Expression::DataType(new_dt)
17740                                        }
17741                                        Expression::Identifier(id) => {
17742                                            if id.name.eq_ignore_ascii_case("INT") {
17743                                                Expression::Identifier(
17744                                                    crate::expressions::Identifier::new("INTEGER"),
17745                                                )
17746                                            } else {
17747                                                let upper = id.name.to_ascii_uppercase();
17748                                                Expression::Identifier(
17749                                                    crate::expressions::Identifier::new(upper),
17750                                                )
17751                                            }
17752                                        }
17753                                        Expression::Column(col) => {
17754                                            if col.name.name.eq_ignore_ascii_case("INT") {
17755                                                Expression::Identifier(
17756                                                    crate::expressions::Identifier::new("INTEGER"),
17757                                                )
17758                                            } else {
17759                                                let upper = col.name.name.to_ascii_uppercase();
17760                                                Expression::Identifier(
17761                                                    crate::expressions::Identifier::new(upper),
17762                                                )
17763                                            }
17764                                        }
17765                                        _ => type_expr.clone(),
17766                                    };
17767                                    let func_name = if is_try { "TRY_CONVERT" } else { "CONVERT" };
17768                                    let mut new_args = vec![normalized_type, value_expr];
17769                                    if let Some(s) = style {
17770                                        new_args.push(s.clone());
17771                                    }
17772                                    return Ok(Expression::Function(Box::new(Function::new(
17773                                        func_name.to_string(),
17774                                        new_args,
17775                                    ))));
17776                                }
17777
17778                                // For other targets: CONVERT(type, expr) -> CAST(expr AS type)
17779                                fn expr_to_datatype(e: &Expression) -> Option<DataType> {
17780                                    match e {
17781                                        Expression::DataType(dt) => {
17782                                            // Convert NVARCHAR/NCHAR Custom types to standard VarChar/Char
17783                                            match dt {
17784                                                DataType::Custom { name }
17785                                                    if name.starts_with("NVARCHAR(")
17786                                                        || name.starts_with("NCHAR(") =>
17787                                                {
17788                                                    // Extract the length from "NVARCHAR(200)" or "NCHAR(40)"
17789                                                    let inner = &name[name.find('(').unwrap() + 1
17790                                                        ..name.len() - 1];
17791                                                    if inner.eq_ignore_ascii_case("MAX") {
17792                                                        Some(DataType::Text)
17793                                                    } else if let Ok(len) = inner.parse::<u32>() {
17794                                                        if name.starts_with("NCHAR") {
17795                                                            Some(DataType::Char {
17796                                                                length: Some(len),
17797                                                            })
17798                                                        } else {
17799                                                            Some(DataType::VarChar {
17800                                                                length: Some(len),
17801                                                                parenthesized_length: false,
17802                                                            })
17803                                                        }
17804                                                    } else {
17805                                                        Some(dt.clone())
17806                                                    }
17807                                                }
17808                                                DataType::Custom { name } if name == "NVARCHAR" => {
17809                                                    Some(DataType::VarChar {
17810                                                        length: None,
17811                                                        parenthesized_length: false,
17812                                                    })
17813                                                }
17814                                                DataType::Custom { name } if name == "NCHAR" => {
17815                                                    Some(DataType::Char { length: None })
17816                                                }
17817                                                DataType::Custom { name }
17818                                                    if name == "NVARCHAR(MAX)"
17819                                                        || name == "VARCHAR(MAX)" =>
17820                                                {
17821                                                    Some(DataType::Text)
17822                                                }
17823                                                _ => Some(dt.clone()),
17824                                            }
17825                                        }
17826                                        Expression::Identifier(id) => {
17827                                            let name = id.name.to_ascii_uppercase();
17828                                            match name.as_str() {
17829                                                "INT" | "INTEGER" => Some(DataType::Int {
17830                                                    length: None,
17831                                                    integer_spelling: false,
17832                                                }),
17833                                                "BIGINT" => Some(DataType::BigInt { length: None }),
17834                                                "SMALLINT" => {
17835                                                    Some(DataType::SmallInt { length: None })
17836                                                }
17837                                                "TINYINT" => {
17838                                                    Some(DataType::TinyInt { length: None })
17839                                                }
17840                                                "FLOAT" => Some(DataType::Float {
17841                                                    precision: None,
17842                                                    scale: None,
17843                                                    real_spelling: false,
17844                                                }),
17845                                                "REAL" => Some(DataType::Float {
17846                                                    precision: None,
17847                                                    scale: None,
17848                                                    real_spelling: true,
17849                                                }),
17850                                                "DATETIME" | "DATETIME2" => {
17851                                                    Some(DataType::Timestamp {
17852                                                        timezone: false,
17853                                                        precision: None,
17854                                                    })
17855                                                }
17856                                                "DATE" => Some(DataType::Date),
17857                                                "BIT" => Some(DataType::Boolean),
17858                                                "TEXT" => Some(DataType::Text),
17859                                                "NUMERIC" => Some(DataType::Decimal {
17860                                                    precision: None,
17861                                                    scale: None,
17862                                                }),
17863                                                "MONEY" => Some(DataType::Decimal {
17864                                                    precision: Some(15),
17865                                                    scale: Some(4),
17866                                                }),
17867                                                "SMALLMONEY" => Some(DataType::Decimal {
17868                                                    precision: Some(6),
17869                                                    scale: Some(4),
17870                                                }),
17871                                                "VARCHAR" => Some(DataType::VarChar {
17872                                                    length: None,
17873                                                    parenthesized_length: false,
17874                                                }),
17875                                                "NVARCHAR" => Some(DataType::VarChar {
17876                                                    length: None,
17877                                                    parenthesized_length: false,
17878                                                }),
17879                                                "CHAR" => Some(DataType::Char { length: None }),
17880                                                "NCHAR" => Some(DataType::Char { length: None }),
17881                                                _ => Some(DataType::Custom { name }),
17882                                            }
17883                                        }
17884                                        Expression::Column(col) => {
17885                                            let name = col.name.name.to_ascii_uppercase();
17886                                            match name.as_str() {
17887                                                "INT" | "INTEGER" => Some(DataType::Int {
17888                                                    length: None,
17889                                                    integer_spelling: false,
17890                                                }),
17891                                                "BIGINT" => Some(DataType::BigInt { length: None }),
17892                                                "FLOAT" => Some(DataType::Float {
17893                                                    precision: None,
17894                                                    scale: None,
17895                                                    real_spelling: false,
17896                                                }),
17897                                                "DATETIME" | "DATETIME2" => {
17898                                                    Some(DataType::Timestamp {
17899                                                        timezone: false,
17900                                                        precision: None,
17901                                                    })
17902                                                }
17903                                                "DATE" => Some(DataType::Date),
17904                                                "NUMERIC" => Some(DataType::Decimal {
17905                                                    precision: None,
17906                                                    scale: None,
17907                                                }),
17908                                                "VARCHAR" => Some(DataType::VarChar {
17909                                                    length: None,
17910                                                    parenthesized_length: false,
17911                                                }),
17912                                                "NVARCHAR" => Some(DataType::VarChar {
17913                                                    length: None,
17914                                                    parenthesized_length: false,
17915                                                }),
17916                                                "CHAR" => Some(DataType::Char { length: None }),
17917                                                "NCHAR" => Some(DataType::Char { length: None }),
17918                                                _ => Some(DataType::Custom { name }),
17919                                            }
17920                                        }
17921                                        // NVARCHAR(200) parsed as Function("NVARCHAR", [200])
17922                                        Expression::Function(f) => {
17923                                            let fname = f.name.to_ascii_uppercase();
17924                                            match fname.as_str() {
17925                                                "VARCHAR" | "NVARCHAR" => {
17926                                                    let len = f.args.first().and_then(|a| {
17927                                                        if let Expression::Literal(lit) = a
17928                                                        {
17929                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17930                                                            n.parse::<u32>().ok()
17931                                                        } else { None }
17932                                                        } else if let Expression::Identifier(id) = a
17933                                                        {
17934                                                            if id.name.eq_ignore_ascii_case("MAX") {
17935                                                                None
17936                                                            } else {
17937                                                                None
17938                                                            }
17939                                                        } else {
17940                                                            None
17941                                                        }
17942                                                    });
17943                                                    // Check for VARCHAR(MAX) -> TEXT
17944                                                    let is_max = f.args.first().map_or(false, |a| {
17945                                                        matches!(a, Expression::Identifier(id) if id.name.eq_ignore_ascii_case("MAX"))
17946                                                        || matches!(a, Expression::Column(col) if col.name.name.eq_ignore_ascii_case("MAX"))
17947                                                    });
17948                                                    if is_max {
17949                                                        Some(DataType::Text)
17950                                                    } else {
17951                                                        Some(DataType::VarChar {
17952                                                            length: len,
17953                                                            parenthesized_length: false,
17954                                                        })
17955                                                    }
17956                                                }
17957                                                "NCHAR" | "CHAR" => {
17958                                                    let len = f.args.first().and_then(|a| {
17959                                                        if let Expression::Literal(lit) = a
17960                                                        {
17961                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17962                                                            n.parse::<u32>().ok()
17963                                                        } else { None }
17964                                                        } else {
17965                                                            None
17966                                                        }
17967                                                    });
17968                                                    Some(DataType::Char { length: len })
17969                                                }
17970                                                "NUMERIC" | "DECIMAL" => {
17971                                                    let precision = f.args.first().and_then(|a| {
17972                                                        if let Expression::Literal(lit) = a
17973                                                        {
17974                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17975                                                            n.parse::<u32>().ok()
17976                                                        } else { None }
17977                                                        } else {
17978                                                            None
17979                                                        }
17980                                                    });
17981                                                    let scale = f.args.get(1).and_then(|a| {
17982                                                        if let Expression::Literal(lit) = a
17983                                                        {
17984                                                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
17985                                                            n.parse::<u32>().ok()
17986                                                        } else { None }
17987                                                        } else {
17988                                                            None
17989                                                        }
17990                                                    });
17991                                                    Some(DataType::Decimal { precision, scale })
17992                                                }
17993                                                _ => None,
17994                                            }
17995                                        }
17996                                        _ => None,
17997                                    }
17998                                }
17999
18000                                if let Some(mut dt) = expr_to_datatype(&type_expr) {
18001                                    // For TSQL source: VARCHAR/CHAR without length defaults to 30
18002                                    let is_tsql_source =
18003                                        matches!(source, DialectType::TSQL | DialectType::Fabric);
18004                                    if is_tsql_source {
18005                                        match &dt {
18006                                            DataType::VarChar { length: None, .. } => {
18007                                                dt = DataType::VarChar {
18008                                                    length: Some(30),
18009                                                    parenthesized_length: false,
18010                                                };
18011                                            }
18012                                            DataType::Char { length: None } => {
18013                                                dt = DataType::Char { length: Some(30) };
18014                                            }
18015                                            _ => {}
18016                                        }
18017                                    }
18018
18019                                    // Determine if this is a string type
18020                                    let is_string_type = matches!(
18021                                        dt,
18022                                        DataType::VarChar { .. }
18023                                            | DataType::Char { .. }
18024                                            | DataType::Text
18025                                    ) || matches!(&dt, DataType::Custom { name } if name == "NVARCHAR" || name == "NCHAR"
18026                                            || name.starts_with("NVARCHAR(") || name.starts_with("NCHAR(")
18027                                            || name.starts_with("VARCHAR(") || name == "VARCHAR"
18028                                            || name == "STRING");
18029
18030                                    // Determine if this is a date/time type
18031                                    let is_datetime_type = matches!(
18032                                        dt,
18033                                        DataType::Timestamp { .. } | DataType::Date
18034                                    ) || matches!(&dt, DataType::Custom { name } if name == "DATETIME"
18035                                            || name == "DATETIME2" || name == "SMALLDATETIME");
18036
18037                                    // Check for date conversion with style
18038                                    if style.is_some() {
18039                                        let style_num = style.and_then(|s| {
18040                                            if let Expression::Literal(lit) = s {
18041                                                if let crate::expressions::Literal::Number(n) =
18042                                                    lit.as_ref()
18043                                                {
18044                                                    n.parse::<u32>().ok()
18045                                                } else {
18046                                                    None
18047                                                }
18048                                            } else {
18049                                                None
18050                                            }
18051                                        });
18052
18053                                        // TSQL CONVERT date styles (Java format)
18054                                        let format_str = style_num.and_then(|n| match n {
18055                                            101 => Some("MM/dd/yyyy"),
18056                                            102 => Some("yyyy.MM.dd"),
18057                                            103 => Some("dd/MM/yyyy"),
18058                                            104 => Some("dd.MM.yyyy"),
18059                                            105 => Some("dd-MM-yyyy"),
18060                                            108 => Some("HH:mm:ss"),
18061                                            110 => Some("MM-dd-yyyy"),
18062                                            112 => Some("yyyyMMdd"),
18063                                            120 | 20 => Some("yyyy-MM-dd HH:mm:ss"),
18064                                            121 | 21 => Some("yyyy-MM-dd HH:mm:ss.SSSSSS"),
18065                                            126 | 127 => Some("yyyy-MM-dd'T'HH:mm:ss.SSS"),
18066                                            _ => None,
18067                                        });
18068
18069                                        // Non-string, non-datetime types with style: just CAST, ignore the style
18070                                        if !is_string_type && !is_datetime_type {
18071                                            let cast_expr = if is_try {
18072                                                Expression::TryCast(Box::new(
18073                                                    crate::expressions::Cast {
18074                                                        this: value_expr,
18075                                                        to: dt,
18076                                                        trailing_comments: Vec::new(),
18077                                                        double_colon_syntax: false,
18078                                                        format: None,
18079                                                        default: None,
18080                                                        inferred_type: None,
18081                                                    },
18082                                                ))
18083                                            } else {
18084                                                Expression::Cast(Box::new(
18085                                                    crate::expressions::Cast {
18086                                                        this: value_expr,
18087                                                        to: dt,
18088                                                        trailing_comments: Vec::new(),
18089                                                        double_colon_syntax: false,
18090                                                        format: None,
18091                                                        default: None,
18092                                                        inferred_type: None,
18093                                                    },
18094                                                ))
18095                                            };
18096                                            return Ok(cast_expr);
18097                                        }
18098
18099                                        if let Some(java_fmt) = format_str {
18100                                            let c_fmt = java_fmt
18101                                                .replace("yyyy", "%Y")
18102                                                .replace("MM", "%m")
18103                                                .replace("dd", "%d")
18104                                                .replace("HH", "%H")
18105                                                .replace("mm", "%M")
18106                                                .replace("ss", "%S")
18107                                                .replace("SSSSSS", "%f")
18108                                                .replace("SSS", "%f")
18109                                                .replace("'T'", "T");
18110
18111                                            // For datetime target types: style is the INPUT format for parsing strings -> dates
18112                                            if is_datetime_type {
18113                                                match target {
18114                                                    DialectType::DuckDB => {
18115                                                        return Ok(Expression::Function(Box::new(
18116                                                            Function::new(
18117                                                                "STRPTIME".to_string(),
18118                                                                vec![
18119                                                                    value_expr,
18120                                                                    Expression::string(&c_fmt),
18121                                                                ],
18122                                                            ),
18123                                                        )));
18124                                                    }
18125                                                    DialectType::Spark
18126                                                    | DialectType::Databricks => {
18127                                                        // CONVERT(DATETIME, x, style) -> TO_TIMESTAMP(x, fmt)
18128                                                        // CONVERT(DATE, x, style) -> TO_DATE(x, fmt)
18129                                                        let func_name =
18130                                                            if matches!(dt, DataType::Date) {
18131                                                                "TO_DATE"
18132                                                            } else {
18133                                                                "TO_TIMESTAMP"
18134                                                            };
18135                                                        return Ok(Expression::Function(Box::new(
18136                                                            Function::new(
18137                                                                func_name.to_string(),
18138                                                                vec![
18139                                                                    value_expr,
18140                                                                    Expression::string(java_fmt),
18141                                                                ],
18142                                                            ),
18143                                                        )));
18144                                                    }
18145                                                    DialectType::Hive => {
18146                                                        return Ok(Expression::Function(Box::new(
18147                                                            Function::new(
18148                                                                "TO_TIMESTAMP".to_string(),
18149                                                                vec![
18150                                                                    value_expr,
18151                                                                    Expression::string(java_fmt),
18152                                                                ],
18153                                                            ),
18154                                                        )));
18155                                                    }
18156                                                    _ => {
18157                                                        return Ok(Expression::Cast(Box::new(
18158                                                            crate::expressions::Cast {
18159                                                                this: value_expr,
18160                                                                to: dt,
18161                                                                trailing_comments: Vec::new(),
18162                                                                double_colon_syntax: false,
18163                                                                format: None,
18164                                                                default: None,
18165                                                                inferred_type: None,
18166                                                            },
18167                                                        )));
18168                                                    }
18169                                                }
18170                                            }
18171
18172                                            // For string target types: style is the OUTPUT format for dates -> strings
18173                                            match target {
18174                                                DialectType::DuckDB => Ok(Expression::Function(
18175                                                    Box::new(Function::new(
18176                                                        "STRPTIME".to_string(),
18177                                                        vec![
18178                                                            value_expr,
18179                                                            Expression::string(&c_fmt),
18180                                                        ],
18181                                                    )),
18182                                                )),
18183                                                DialectType::Spark | DialectType::Databricks => {
18184                                                    // For string target types with style: CAST(DATE_FORMAT(x, fmt) AS type)
18185                                                    // Determine the target string type
18186                                                    let string_dt = match &dt {
18187                                                        DataType::VarChar {
18188                                                            length: Some(l),
18189                                                            ..
18190                                                        } => DataType::VarChar {
18191                                                            length: Some(*l),
18192                                                            parenthesized_length: false,
18193                                                        },
18194                                                        DataType::Text => DataType::Custom {
18195                                                            name: "STRING".to_string(),
18196                                                        },
18197                                                        _ => DataType::Custom {
18198                                                            name: "STRING".to_string(),
18199                                                        },
18200                                                    };
18201                                                    let date_format_expr = Expression::Function(
18202                                                        Box::new(Function::new(
18203                                                            "DATE_FORMAT".to_string(),
18204                                                            vec![
18205                                                                value_expr,
18206                                                                Expression::string(java_fmt),
18207                                                            ],
18208                                                        )),
18209                                                    );
18210                                                    let cast_expr = if is_try {
18211                                                        Expression::TryCast(Box::new(
18212                                                            crate::expressions::Cast {
18213                                                                this: date_format_expr,
18214                                                                to: string_dt,
18215                                                                trailing_comments: Vec::new(),
18216                                                                double_colon_syntax: false,
18217                                                                format: None,
18218                                                                default: None,
18219                                                                inferred_type: None,
18220                                                            },
18221                                                        ))
18222                                                    } else {
18223                                                        Expression::Cast(Box::new(
18224                                                            crate::expressions::Cast {
18225                                                                this: date_format_expr,
18226                                                                to: string_dt,
18227                                                                trailing_comments: Vec::new(),
18228                                                                double_colon_syntax: false,
18229                                                                format: None,
18230                                                                default: None,
18231                                                                inferred_type: None,
18232                                                            },
18233                                                        ))
18234                                                    };
18235                                                    Ok(cast_expr)
18236                                                }
18237                                                DialectType::MySQL | DialectType::SingleStore => {
18238                                                    // For MySQL: CAST(DATE_FORMAT(x, mysql_fmt) AS CHAR(n))
18239                                                    let mysql_fmt = java_fmt
18240                                                        .replace("yyyy", "%Y")
18241                                                        .replace("MM", "%m")
18242                                                        .replace("dd", "%d")
18243                                                        .replace("HH:mm:ss.SSSSSS", "%T")
18244                                                        .replace("HH:mm:ss", "%T")
18245                                                        .replace("HH", "%H")
18246                                                        .replace("mm", "%i")
18247                                                        .replace("ss", "%S");
18248                                                    let date_format_expr = Expression::Function(
18249                                                        Box::new(Function::new(
18250                                                            "DATE_FORMAT".to_string(),
18251                                                            vec![
18252                                                                value_expr,
18253                                                                Expression::string(&mysql_fmt),
18254                                                            ],
18255                                                        )),
18256                                                    );
18257                                                    // MySQL uses CHAR for string casts
18258                                                    let mysql_dt = match &dt {
18259                                                        DataType::VarChar { length, .. } => {
18260                                                            DataType::Char { length: *length }
18261                                                        }
18262                                                        _ => dt,
18263                                                    };
18264                                                    Ok(Expression::Cast(Box::new(
18265                                                        crate::expressions::Cast {
18266                                                            this: date_format_expr,
18267                                                            to: mysql_dt,
18268                                                            trailing_comments: Vec::new(),
18269                                                            double_colon_syntax: false,
18270                                                            format: None,
18271                                                            default: None,
18272                                                            inferred_type: None,
18273                                                        },
18274                                                    )))
18275                                                }
18276                                                DialectType::Hive => {
18277                                                    let func_name = "TO_TIMESTAMP";
18278                                                    Ok(Expression::Function(Box::new(
18279                                                        Function::new(
18280                                                            func_name.to_string(),
18281                                                            vec![
18282                                                                value_expr,
18283                                                                Expression::string(java_fmt),
18284                                                            ],
18285                                                        ),
18286                                                    )))
18287                                                }
18288                                                _ => Ok(Expression::Cast(Box::new(
18289                                                    crate::expressions::Cast {
18290                                                        this: value_expr,
18291                                                        to: dt,
18292                                                        trailing_comments: Vec::new(),
18293                                                        double_colon_syntax: false,
18294                                                        format: None,
18295                                                        default: None,
18296                                                        inferred_type: None,
18297                                                    },
18298                                                ))),
18299                                            }
18300                                        } else {
18301                                            // Unknown style, just CAST
18302                                            let cast_expr = if is_try {
18303                                                Expression::TryCast(Box::new(
18304                                                    crate::expressions::Cast {
18305                                                        this: value_expr,
18306                                                        to: dt,
18307                                                        trailing_comments: Vec::new(),
18308                                                        double_colon_syntax: false,
18309                                                        format: None,
18310                                                        default: None,
18311                                                        inferred_type: None,
18312                                                    },
18313                                                ))
18314                                            } else {
18315                                                Expression::Cast(Box::new(
18316                                                    crate::expressions::Cast {
18317                                                        this: value_expr,
18318                                                        to: dt,
18319                                                        trailing_comments: Vec::new(),
18320                                                        double_colon_syntax: false,
18321                                                        format: None,
18322                                                        default: None,
18323                                                        inferred_type: None,
18324                                                    },
18325                                                ))
18326                                            };
18327                                            Ok(cast_expr)
18328                                        }
18329                                    } else {
18330                                        // No style - simple CAST
18331                                        let final_dt = if matches!(
18332                                            target,
18333                                            DialectType::MySQL | DialectType::SingleStore
18334                                        ) {
18335                                            match &dt {
18336                                                DataType::Int { .. }
18337                                                | DataType::BigInt { .. }
18338                                                | DataType::SmallInt { .. }
18339                                                | DataType::TinyInt { .. } => DataType::Custom {
18340                                                    name: "SIGNED".to_string(),
18341                                                },
18342                                                DataType::VarChar { length, .. } => {
18343                                                    DataType::Char { length: *length }
18344                                                }
18345                                                _ => dt,
18346                                            }
18347                                        } else {
18348                                            dt
18349                                        };
18350                                        let cast_expr = if is_try {
18351                                            Expression::TryCast(Box::new(
18352                                                crate::expressions::Cast {
18353                                                    this: value_expr,
18354                                                    to: final_dt,
18355                                                    trailing_comments: Vec::new(),
18356                                                    double_colon_syntax: false,
18357                                                    format: None,
18358                                                    default: None,
18359                                                    inferred_type: None,
18360                                                },
18361                                            ))
18362                                        } else {
18363                                            Expression::Cast(Box::new(crate::expressions::Cast {
18364                                                this: value_expr,
18365                                                to: final_dt,
18366                                                trailing_comments: Vec::new(),
18367                                                double_colon_syntax: false,
18368                                                format: None,
18369                                                default: None,
18370                                                inferred_type: None,
18371                                            }))
18372                                        };
18373                                        Ok(cast_expr)
18374                                    }
18375                                } else {
18376                                    // Can't convert type expression - keep as CONVERT/TRY_CONVERT function
18377                                    Ok(Expression::Function(f))
18378                                }
18379                            }
18380                            // STRFTIME(val, fmt) from DuckDB / STRFTIME(fmt, val) from SQLite -> target-specific
18381                            "STRFTIME" if f.args.len() == 2 => {
18382                                // SQLite uses STRFTIME(fmt, val); DuckDB uses STRFTIME(val, fmt)
18383                                let (val, fmt_expr) = if matches!(source, DialectType::SQLite) {
18384                                    // SQLite: args[0] = format, args[1] = value
18385                                    (f.args[1].clone(), &f.args[0])
18386                                } else {
18387                                    // DuckDB and others: args[0] = value, args[1] = format
18388                                    (f.args[0].clone(), &f.args[1])
18389                                };
18390
18391                                // Helper to convert C-style format to Java-style
18392                                fn c_to_java_format(fmt: &str) -> String {
18393                                    fmt.replace("%Y", "yyyy")
18394                                        .replace("%m", "MM")
18395                                        .replace("%d", "dd")
18396                                        .replace("%H", "HH")
18397                                        .replace("%M", "mm")
18398                                        .replace("%S", "ss")
18399                                        .replace("%f", "SSSSSS")
18400                                        .replace("%y", "yy")
18401                                        .replace("%-m", "M")
18402                                        .replace("%-d", "d")
18403                                        .replace("%-H", "H")
18404                                        .replace("%-I", "h")
18405                                        .replace("%I", "hh")
18406                                        .replace("%p", "a")
18407                                        .replace("%j", "DDD")
18408                                        .replace("%a", "EEE")
18409                                        .replace("%b", "MMM")
18410                                        .replace("%F", "yyyy-MM-dd")
18411                                        .replace("%T", "HH:mm:ss")
18412                                }
18413
18414                                // Helper: recursively convert format strings within expressions (handles CONCAT)
18415                                fn convert_fmt_expr(
18416                                    expr: &Expression,
18417                                    converter: &dyn Fn(&str) -> String,
18418                                ) -> Expression {
18419                                    match expr {
18420                                        Expression::Literal(lit)
18421                                            if matches!(
18422                                                lit.as_ref(),
18423                                                crate::expressions::Literal::String(_)
18424                                            ) =>
18425                                        {
18426                                            let crate::expressions::Literal::String(s) =
18427                                                lit.as_ref()
18428                                            else {
18429                                                unreachable!()
18430                                            };
18431                                            Expression::string(&converter(s))
18432                                        }
18433                                        Expression::Function(func)
18434                                            if func.name.eq_ignore_ascii_case("CONCAT") =>
18435                                        {
18436                                            let new_args: Vec<Expression> = func
18437                                                .args
18438                                                .iter()
18439                                                .map(|a| convert_fmt_expr(a, converter))
18440                                                .collect();
18441                                            Expression::Function(Box::new(Function::new(
18442                                                "CONCAT".to_string(),
18443                                                new_args,
18444                                            )))
18445                                        }
18446                                        other => other.clone(),
18447                                    }
18448                                }
18449
18450                                match target {
18451                                    DialectType::DuckDB => {
18452                                        if matches!(source, DialectType::SQLite) {
18453                                            // SQLite STRFTIME(fmt, val) -> DuckDB STRFTIME(CAST(val AS TIMESTAMP), fmt)
18454                                            let cast_val = Expression::Cast(Box::new(Cast {
18455                                                this: val,
18456                                                to: crate::expressions::DataType::Timestamp {
18457                                                    precision: None,
18458                                                    timezone: false,
18459                                                },
18460                                                trailing_comments: Vec::new(),
18461                                                double_colon_syntax: false,
18462                                                format: None,
18463                                                default: None,
18464                                                inferred_type: None,
18465                                            }));
18466                                            Ok(Expression::Function(Box::new(Function::new(
18467                                                "STRFTIME".to_string(),
18468                                                vec![cast_val, fmt_expr.clone()],
18469                                            ))))
18470                                        } else {
18471                                            Ok(Expression::Function(f))
18472                                        }
18473                                    }
18474                                    DialectType::Spark
18475                                    | DialectType::Databricks
18476                                    | DialectType::Hive => {
18477                                        // STRFTIME(val, fmt) -> DATE_FORMAT(val, java_fmt)
18478                                        let converted_fmt =
18479                                            convert_fmt_expr(fmt_expr, &c_to_java_format);
18480                                        Ok(Expression::Function(Box::new(Function::new(
18481                                            "DATE_FORMAT".to_string(),
18482                                            vec![val, converted_fmt],
18483                                        ))))
18484                                    }
18485                                    DialectType::TSQL | DialectType::Fabric => {
18486                                        // STRFTIME(val, fmt) -> FORMAT(val, java_fmt)
18487                                        let converted_fmt =
18488                                            convert_fmt_expr(fmt_expr, &c_to_java_format);
18489                                        Ok(Expression::Function(Box::new(Function::new(
18490                                            "FORMAT".to_string(),
18491                                            vec![val, converted_fmt],
18492                                        ))))
18493                                    }
18494                                    DialectType::Presto
18495                                    | DialectType::Trino
18496                                    | DialectType::Athena => {
18497                                        // STRFTIME(val, fmt) -> DATE_FORMAT(val, presto_fmt) (convert DuckDB format to Presto)
18498                                        if let Expression::Literal(lit) = fmt_expr {
18499                                            if let crate::expressions::Literal::String(s) =
18500                                                lit.as_ref()
18501                                            {
18502                                                let presto_fmt = duckdb_to_presto_format(s);
18503                                                Ok(Expression::Function(Box::new(Function::new(
18504                                                    "DATE_FORMAT".to_string(),
18505                                                    vec![val, Expression::string(&presto_fmt)],
18506                                                ))))
18507                                            } else {
18508                                                Ok(Expression::Function(Box::new(Function::new(
18509                                                    "DATE_FORMAT".to_string(),
18510                                                    vec![val, fmt_expr.clone()],
18511                                                ))))
18512                                            }
18513                                        } else {
18514                                            Ok(Expression::Function(Box::new(Function::new(
18515                                                "DATE_FORMAT".to_string(),
18516                                                vec![val, fmt_expr.clone()],
18517                                            ))))
18518                                        }
18519                                    }
18520                                    DialectType::BigQuery => {
18521                                        // STRFTIME(val, fmt) -> FORMAT_DATE(bq_fmt, val) - note reversed arg order
18522                                        if let Expression::Literal(lit) = fmt_expr {
18523                                            if let crate::expressions::Literal::String(s) =
18524                                                lit.as_ref()
18525                                            {
18526                                                let bq_fmt = duckdb_to_bigquery_format(s);
18527                                                Ok(Expression::Function(Box::new(Function::new(
18528                                                    "FORMAT_DATE".to_string(),
18529                                                    vec![Expression::string(&bq_fmt), val],
18530                                                ))))
18531                                            } else {
18532                                                Ok(Expression::Function(Box::new(Function::new(
18533                                                    "FORMAT_DATE".to_string(),
18534                                                    vec![fmt_expr.clone(), val],
18535                                                ))))
18536                                            }
18537                                        } else {
18538                                            Ok(Expression::Function(Box::new(Function::new(
18539                                                "FORMAT_DATE".to_string(),
18540                                                vec![fmt_expr.clone(), val],
18541                                            ))))
18542                                        }
18543                                    }
18544                                    DialectType::PostgreSQL | DialectType::Redshift => {
18545                                        // STRFTIME(val, fmt) -> TO_CHAR(val, pg_fmt)
18546                                        if let Expression::Literal(lit) = fmt_expr {
18547                                            if let crate::expressions::Literal::String(s) =
18548                                                lit.as_ref()
18549                                            {
18550                                                let pg_fmt = s
18551                                                    .replace("%Y", "YYYY")
18552                                                    .replace("%m", "MM")
18553                                                    .replace("%d", "DD")
18554                                                    .replace("%H", "HH24")
18555                                                    .replace("%M", "MI")
18556                                                    .replace("%S", "SS")
18557                                                    .replace("%y", "YY")
18558                                                    .replace("%-m", "FMMM")
18559                                                    .replace("%-d", "FMDD")
18560                                                    .replace("%-H", "FMHH24")
18561                                                    .replace("%-I", "FMHH12")
18562                                                    .replace("%p", "AM")
18563                                                    .replace("%F", "YYYY-MM-DD")
18564                                                    .replace("%T", "HH24:MI:SS");
18565                                                Ok(Expression::Function(Box::new(Function::new(
18566                                                    "TO_CHAR".to_string(),
18567                                                    vec![val, Expression::string(&pg_fmt)],
18568                                                ))))
18569                                            } else {
18570                                                Ok(Expression::Function(Box::new(Function::new(
18571                                                    "TO_CHAR".to_string(),
18572                                                    vec![val, fmt_expr.clone()],
18573                                                ))))
18574                                            }
18575                                        } else {
18576                                            Ok(Expression::Function(Box::new(Function::new(
18577                                                "TO_CHAR".to_string(),
18578                                                vec![val, fmt_expr.clone()],
18579                                            ))))
18580                                        }
18581                                    }
18582                                    _ => Ok(Expression::Function(f)),
18583                                }
18584                            }
18585                            // STRPTIME(val, fmt) from DuckDB -> target-specific date parse function
18586                            "STRPTIME" if f.args.len() == 2 => {
18587                                let val = f.args[0].clone();
18588                                let fmt_expr = &f.args[1];
18589
18590                                fn c_to_java_format_parse(fmt: &str) -> String {
18591                                    fmt.replace("%Y", "yyyy")
18592                                        .replace("%m", "MM")
18593                                        .replace("%d", "dd")
18594                                        .replace("%H", "HH")
18595                                        .replace("%M", "mm")
18596                                        .replace("%S", "ss")
18597                                        .replace("%f", "SSSSSS")
18598                                        .replace("%y", "yy")
18599                                        .replace("%-m", "M")
18600                                        .replace("%-d", "d")
18601                                        .replace("%-H", "H")
18602                                        .replace("%-I", "h")
18603                                        .replace("%I", "hh")
18604                                        .replace("%p", "a")
18605                                        .replace("%F", "yyyy-MM-dd")
18606                                        .replace("%T", "HH:mm:ss")
18607                                }
18608
18609                                match target {
18610                                    DialectType::DuckDB => Ok(Expression::Function(f)),
18611                                    DialectType::Spark | DialectType::Databricks => {
18612                                        // STRPTIME(val, fmt) -> TO_TIMESTAMP(val, java_fmt)
18613                                        if let Expression::Literal(lit) = fmt_expr {
18614                                            if let crate::expressions::Literal::String(s) =
18615                                                lit.as_ref()
18616                                            {
18617                                                let java_fmt = c_to_java_format_parse(s);
18618                                                Ok(Expression::Function(Box::new(Function::new(
18619                                                    "TO_TIMESTAMP".to_string(),
18620                                                    vec![val, Expression::string(&java_fmt)],
18621                                                ))))
18622                                            } else {
18623                                                Ok(Expression::Function(Box::new(Function::new(
18624                                                    "TO_TIMESTAMP".to_string(),
18625                                                    vec![val, fmt_expr.clone()],
18626                                                ))))
18627                                            }
18628                                        } else {
18629                                            Ok(Expression::Function(Box::new(Function::new(
18630                                                "TO_TIMESTAMP".to_string(),
18631                                                vec![val, fmt_expr.clone()],
18632                                            ))))
18633                                        }
18634                                    }
18635                                    DialectType::Hive => {
18636                                        // STRPTIME(val, fmt) -> CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(val, java_fmt)) AS TIMESTAMP)
18637                                        if let Expression::Literal(lit) = fmt_expr {
18638                                            if let crate::expressions::Literal::String(s) =
18639                                                lit.as_ref()
18640                                            {
18641                                                let java_fmt = c_to_java_format_parse(s);
18642                                                let unix_ts =
18643                                                    Expression::Function(Box::new(Function::new(
18644                                                        "UNIX_TIMESTAMP".to_string(),
18645                                                        vec![val, Expression::string(&java_fmt)],
18646                                                    )));
18647                                                let from_unix =
18648                                                    Expression::Function(Box::new(Function::new(
18649                                                        "FROM_UNIXTIME".to_string(),
18650                                                        vec![unix_ts],
18651                                                    )));
18652                                                Ok(Expression::Cast(Box::new(
18653                                                    crate::expressions::Cast {
18654                                                        this: from_unix,
18655                                                        to: DataType::Timestamp {
18656                                                            timezone: false,
18657                                                            precision: None,
18658                                                        },
18659                                                        trailing_comments: Vec::new(),
18660                                                        double_colon_syntax: false,
18661                                                        format: None,
18662                                                        default: None,
18663                                                        inferred_type: None,
18664                                                    },
18665                                                )))
18666                                            } else {
18667                                                Ok(Expression::Function(f))
18668                                            }
18669                                        } else {
18670                                            Ok(Expression::Function(f))
18671                                        }
18672                                    }
18673                                    DialectType::Presto
18674                                    | DialectType::Trino
18675                                    | DialectType::Athena => {
18676                                        // STRPTIME(val, fmt) -> DATE_PARSE(val, presto_fmt) (convert DuckDB format to Presto)
18677                                        if let Expression::Literal(lit) = fmt_expr {
18678                                            if let crate::expressions::Literal::String(s) =
18679                                                lit.as_ref()
18680                                            {
18681                                                let presto_fmt = duckdb_to_presto_format(s);
18682                                                Ok(Expression::Function(Box::new(Function::new(
18683                                                    "DATE_PARSE".to_string(),
18684                                                    vec![val, Expression::string(&presto_fmt)],
18685                                                ))))
18686                                            } else {
18687                                                Ok(Expression::Function(Box::new(Function::new(
18688                                                    "DATE_PARSE".to_string(),
18689                                                    vec![val, fmt_expr.clone()],
18690                                                ))))
18691                                            }
18692                                        } else {
18693                                            Ok(Expression::Function(Box::new(Function::new(
18694                                                "DATE_PARSE".to_string(),
18695                                                vec![val, fmt_expr.clone()],
18696                                            ))))
18697                                        }
18698                                    }
18699                                    DialectType::BigQuery => {
18700                                        // STRPTIME(val, fmt) -> PARSE_TIMESTAMP(bq_fmt, val) - note reversed arg order
18701                                        if let Expression::Literal(lit) = fmt_expr {
18702                                            if let crate::expressions::Literal::String(s) =
18703                                                lit.as_ref()
18704                                            {
18705                                                let bq_fmt = duckdb_to_bigquery_format(s);
18706                                                Ok(Expression::Function(Box::new(Function::new(
18707                                                    "PARSE_TIMESTAMP".to_string(),
18708                                                    vec![Expression::string(&bq_fmt), val],
18709                                                ))))
18710                                            } else {
18711                                                Ok(Expression::Function(Box::new(Function::new(
18712                                                    "PARSE_TIMESTAMP".to_string(),
18713                                                    vec![fmt_expr.clone(), val],
18714                                                ))))
18715                                            }
18716                                        } else {
18717                                            Ok(Expression::Function(Box::new(Function::new(
18718                                                "PARSE_TIMESTAMP".to_string(),
18719                                                vec![fmt_expr.clone(), val],
18720                                            ))))
18721                                        }
18722                                    }
18723                                    _ => Ok(Expression::Function(f)),
18724                                }
18725                            }
18726                            // DATE_FORMAT(val, fmt) from Presto source (C-style format) -> target-specific
18727                            "DATE_FORMAT"
18728                                if f.args.len() >= 2
18729                                    && matches!(
18730                                        source,
18731                                        DialectType::Presto
18732                                            | DialectType::Trino
18733                                            | DialectType::Athena
18734                                    ) =>
18735                            {
18736                                let val = f.args[0].clone();
18737                                let fmt_expr = &f.args[1];
18738
18739                                match target {
18740                                    DialectType::Presto
18741                                    | DialectType::Trino
18742                                    | DialectType::Athena => {
18743                                        // Presto -> Presto: normalize format (e.g., %H:%i:%S -> %T)
18744                                        if let Expression::Literal(lit) = fmt_expr {
18745                                            if let crate::expressions::Literal::String(s) =
18746                                                lit.as_ref()
18747                                            {
18748                                                let normalized = normalize_presto_format(s);
18749                                                Ok(Expression::Function(Box::new(Function::new(
18750                                                    "DATE_FORMAT".to_string(),
18751                                                    vec![val, Expression::string(&normalized)],
18752                                                ))))
18753                                            } else {
18754                                                Ok(Expression::Function(f))
18755                                            }
18756                                        } else {
18757                                            Ok(Expression::Function(f))
18758                                        }
18759                                    }
18760                                    DialectType::Hive
18761                                    | DialectType::Spark
18762                                    | DialectType::Databricks => {
18763                                        // Convert Presto C-style to Java-style format
18764                                        if let Expression::Literal(lit) = fmt_expr {
18765                                            if let crate::expressions::Literal::String(s) =
18766                                                lit.as_ref()
18767                                            {
18768                                                let java_fmt = presto_to_java_format(s);
18769                                                Ok(Expression::Function(Box::new(Function::new(
18770                                                    "DATE_FORMAT".to_string(),
18771                                                    vec![val, Expression::string(&java_fmt)],
18772                                                ))))
18773                                            } else {
18774                                                Ok(Expression::Function(f))
18775                                            }
18776                                        } else {
18777                                            Ok(Expression::Function(f))
18778                                        }
18779                                    }
18780                                    DialectType::DuckDB => {
18781                                        // Convert to STRFTIME(val, duckdb_fmt)
18782                                        if let Expression::Literal(lit) = fmt_expr {
18783                                            if let crate::expressions::Literal::String(s) =
18784                                                lit.as_ref()
18785                                            {
18786                                                let duckdb_fmt = presto_to_duckdb_format(s);
18787                                                Ok(Expression::Function(Box::new(Function::new(
18788                                                    "STRFTIME".to_string(),
18789                                                    vec![val, Expression::string(&duckdb_fmt)],
18790                                                ))))
18791                                            } else {
18792                                                Ok(Expression::Function(Box::new(Function::new(
18793                                                    "STRFTIME".to_string(),
18794                                                    vec![val, fmt_expr.clone()],
18795                                                ))))
18796                                            }
18797                                        } else {
18798                                            Ok(Expression::Function(Box::new(Function::new(
18799                                                "STRFTIME".to_string(),
18800                                                vec![val, fmt_expr.clone()],
18801                                            ))))
18802                                        }
18803                                    }
18804                                    DialectType::BigQuery => {
18805                                        // Convert to FORMAT_DATE(bq_fmt, val) - reversed args
18806                                        if let Expression::Literal(lit) = fmt_expr {
18807                                            if let crate::expressions::Literal::String(s) =
18808                                                lit.as_ref()
18809                                            {
18810                                                let bq_fmt = presto_to_bigquery_format(s);
18811                                                Ok(Expression::Function(Box::new(Function::new(
18812                                                    "FORMAT_DATE".to_string(),
18813                                                    vec![Expression::string(&bq_fmt), val],
18814                                                ))))
18815                                            } else {
18816                                                Ok(Expression::Function(Box::new(Function::new(
18817                                                    "FORMAT_DATE".to_string(),
18818                                                    vec![fmt_expr.clone(), val],
18819                                                ))))
18820                                            }
18821                                        } else {
18822                                            Ok(Expression::Function(Box::new(Function::new(
18823                                                "FORMAT_DATE".to_string(),
18824                                                vec![fmt_expr.clone(), val],
18825                                            ))))
18826                                        }
18827                                    }
18828                                    _ => Ok(Expression::Function(f)),
18829                                }
18830                            }
18831                            // DATE_PARSE(val, fmt) from Presto source -> target-specific parse function
18832                            "DATE_PARSE"
18833                                if f.args.len() >= 2
18834                                    && matches!(
18835                                        source,
18836                                        DialectType::Presto
18837                                            | DialectType::Trino
18838                                            | DialectType::Athena
18839                                    ) =>
18840                            {
18841                                let val = f.args[0].clone();
18842                                let fmt_expr = &f.args[1];
18843
18844                                match target {
18845                                    DialectType::Presto
18846                                    | DialectType::Trino
18847                                    | DialectType::Athena => {
18848                                        // Presto -> Presto: normalize format
18849                                        if let Expression::Literal(lit) = fmt_expr {
18850                                            if let crate::expressions::Literal::String(s) =
18851                                                lit.as_ref()
18852                                            {
18853                                                let normalized = normalize_presto_format(s);
18854                                                Ok(Expression::Function(Box::new(Function::new(
18855                                                    "DATE_PARSE".to_string(),
18856                                                    vec![val, Expression::string(&normalized)],
18857                                                ))))
18858                                            } else {
18859                                                Ok(Expression::Function(f))
18860                                            }
18861                                        } else {
18862                                            Ok(Expression::Function(f))
18863                                        }
18864                                    }
18865                                    DialectType::Hive => {
18866                                        // Presto -> Hive: if default format, just CAST(x AS TIMESTAMP)
18867                                        if let Expression::Literal(lit) = fmt_expr {
18868                                            if let crate::expressions::Literal::String(s) =
18869                                                lit.as_ref()
18870                                            {
18871                                                if is_default_presto_timestamp_format(s)
18872                                                    || is_default_presto_date_format(s)
18873                                                {
18874                                                    Ok(Expression::Cast(Box::new(
18875                                                        crate::expressions::Cast {
18876                                                            this: val,
18877                                                            to: DataType::Timestamp {
18878                                                                timezone: false,
18879                                                                precision: None,
18880                                                            },
18881                                                            trailing_comments: Vec::new(),
18882                                                            double_colon_syntax: false,
18883                                                            format: None,
18884                                                            default: None,
18885                                                            inferred_type: None,
18886                                                        },
18887                                                    )))
18888                                                } else {
18889                                                    let java_fmt = presto_to_java_format(s);
18890                                                    Ok(Expression::Function(Box::new(
18891                                                        Function::new(
18892                                                            "TO_TIMESTAMP".to_string(),
18893                                                            vec![
18894                                                                val,
18895                                                                Expression::string(&java_fmt),
18896                                                            ],
18897                                                        ),
18898                                                    )))
18899                                                }
18900                                            } else {
18901                                                Ok(Expression::Function(f))
18902                                            }
18903                                        } else {
18904                                            Ok(Expression::Function(f))
18905                                        }
18906                                    }
18907                                    DialectType::Spark | DialectType::Databricks => {
18908                                        // Presto -> Spark: TO_TIMESTAMP(val, java_fmt)
18909                                        if let Expression::Literal(lit) = fmt_expr {
18910                                            if let crate::expressions::Literal::String(s) =
18911                                                lit.as_ref()
18912                                            {
18913                                                let java_fmt = presto_to_java_format(s);
18914                                                Ok(Expression::Function(Box::new(Function::new(
18915                                                    "TO_TIMESTAMP".to_string(),
18916                                                    vec![val, Expression::string(&java_fmt)],
18917                                                ))))
18918                                            } else {
18919                                                Ok(Expression::Function(f))
18920                                            }
18921                                        } else {
18922                                            Ok(Expression::Function(f))
18923                                        }
18924                                    }
18925                                    DialectType::DuckDB => {
18926                                        // Presto -> DuckDB: STRPTIME(val, duckdb_fmt)
18927                                        if let Expression::Literal(lit) = fmt_expr {
18928                                            if let crate::expressions::Literal::String(s) =
18929                                                lit.as_ref()
18930                                            {
18931                                                let duckdb_fmt = presto_to_duckdb_format(s);
18932                                                Ok(Expression::Function(Box::new(Function::new(
18933                                                    "STRPTIME".to_string(),
18934                                                    vec![val, Expression::string(&duckdb_fmt)],
18935                                                ))))
18936                                            } else {
18937                                                Ok(Expression::Function(Box::new(Function::new(
18938                                                    "STRPTIME".to_string(),
18939                                                    vec![val, fmt_expr.clone()],
18940                                                ))))
18941                                            }
18942                                        } else {
18943                                            Ok(Expression::Function(Box::new(Function::new(
18944                                                "STRPTIME".to_string(),
18945                                                vec![val, fmt_expr.clone()],
18946                                            ))))
18947                                        }
18948                                    }
18949                                    _ => Ok(Expression::Function(f)),
18950                                }
18951                            }
18952                            // FROM_BASE64(x) / TO_BASE64(x) from Presto -> Hive-specific renames
18953                            "FROM_BASE64"
18954                                if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
18955                            {
18956                                Ok(Expression::Function(Box::new(Function::new(
18957                                    "UNBASE64".to_string(),
18958                                    f.args,
18959                                ))))
18960                            }
18961                            "TO_BASE64"
18962                                if f.args.len() == 1 && matches!(target, DialectType::Hive) =>
18963                            {
18964                                Ok(Expression::Function(Box::new(Function::new(
18965                                    "BASE64".to_string(),
18966                                    f.args,
18967                                ))))
18968                            }
18969                            // FROM_UNIXTIME(x) -> CAST(FROM_UNIXTIME(x) AS TIMESTAMP) for Spark
18970                            "FROM_UNIXTIME"
18971                                if f.args.len() == 1
18972                                    && matches!(
18973                                        source,
18974                                        DialectType::Presto
18975                                            | DialectType::Trino
18976                                            | DialectType::Athena
18977                                    )
18978                                    && matches!(
18979                                        target,
18980                                        DialectType::Spark | DialectType::Databricks
18981                                    ) =>
18982                            {
18983                                // Wrap FROM_UNIXTIME(x) in CAST(... AS TIMESTAMP)
18984                                let from_unix = Expression::Function(Box::new(Function::new(
18985                                    "FROM_UNIXTIME".to_string(),
18986                                    f.args,
18987                                )));
18988                                Ok(Expression::Cast(Box::new(crate::expressions::Cast {
18989                                    this: from_unix,
18990                                    to: DataType::Timestamp {
18991                                        timezone: false,
18992                                        precision: None,
18993                                    },
18994                                    trailing_comments: Vec::new(),
18995                                    double_colon_syntax: false,
18996                                    format: None,
18997                                    default: None,
18998                                    inferred_type: None,
18999                                })))
19000                            }
19001                            // DATE_FORMAT(val, fmt) from Hive/Spark/MySQL -> target-specific format function
19002                            "DATE_FORMAT"
19003                                if f.args.len() >= 2
19004                                    && !matches!(
19005                                        target,
19006                                        DialectType::Hive
19007                                            | DialectType::Spark
19008                                            | DialectType::Databricks
19009                                            | DialectType::MySQL
19010                                            | DialectType::SingleStore
19011                                    ) =>
19012                            {
19013                                let val = f.args[0].clone();
19014                                let fmt_expr = &f.args[1];
19015                                let is_hive_source = matches!(
19016                                    source,
19017                                    DialectType::Hive
19018                                        | DialectType::Spark
19019                                        | DialectType::Databricks
19020                                );
19021
19022                                fn java_to_c_format(fmt: &str) -> String {
19023                                    // Replace Java patterns with C strftime patterns.
19024                                    // Uses multi-pass to handle patterns that conflict.
19025                                    // First pass: replace multi-char patterns (longer first)
19026                                    let result = fmt
19027                                        .replace("yyyy", "%Y")
19028                                        .replace("SSSSSS", "%f")
19029                                        .replace("EEEE", "%W")
19030                                        .replace("MM", "%m")
19031                                        .replace("dd", "%d")
19032                                        .replace("HH", "%H")
19033                                        .replace("mm", "%M")
19034                                        .replace("ss", "%S")
19035                                        .replace("yy", "%y");
19036                                    // Second pass: handle single-char timezone patterns
19037                                    // z -> %Z (timezone name), Z -> %z (timezone offset)
19038                                    // Must be careful not to replace 'z'/'Z' inside already-replaced %Y, %M etc.
19039                                    let mut out = String::new();
19040                                    let chars: Vec<char> = result.chars().collect();
19041                                    let mut i = 0;
19042                                    while i < chars.len() {
19043                                        if chars[i] == '%' && i + 1 < chars.len() {
19044                                            // Already a format specifier, skip both chars
19045                                            out.push(chars[i]);
19046                                            out.push(chars[i + 1]);
19047                                            i += 2;
19048                                        } else if chars[i] == 'z' {
19049                                            out.push_str("%Z");
19050                                            i += 1;
19051                                        } else if chars[i] == 'Z' {
19052                                            out.push_str("%z");
19053                                            i += 1;
19054                                        } else {
19055                                            out.push(chars[i]);
19056                                            i += 1;
19057                                        }
19058                                    }
19059                                    out
19060                                }
19061
19062                                fn java_to_presto_format(fmt: &str) -> String {
19063                                    // Presto uses %T for HH:MM:SS
19064                                    let c_fmt = java_to_c_format(fmt);
19065                                    c_fmt.replace("%H:%M:%S", "%T")
19066                                }
19067
19068                                fn java_to_bq_format(fmt: &str) -> String {
19069                                    // BigQuery uses %F for yyyy-MM-dd and %T for HH:mm:ss
19070                                    let c_fmt = java_to_c_format(fmt);
19071                                    c_fmt.replace("%Y-%m-%d", "%F").replace("%H:%M:%S", "%T")
19072                                }
19073
19074                                // For Hive source, CAST string literals to appropriate type
19075                                let cast_val = if is_hive_source {
19076                                    match &val {
19077                                        Expression::Literal(lit)
19078                                            if matches!(
19079                                                lit.as_ref(),
19080                                                crate::expressions::Literal::String(_)
19081                                            ) =>
19082                                        {
19083                                            match target {
19084                                                DialectType::DuckDB
19085                                                | DialectType::Presto
19086                                                | DialectType::Trino
19087                                                | DialectType::Athena => {
19088                                                    Self::ensure_cast_timestamp(val.clone())
19089                                                }
19090                                                DialectType::BigQuery => {
19091                                                    // BigQuery: CAST(val AS DATETIME)
19092                                                    Expression::Cast(Box::new(
19093                                                        crate::expressions::Cast {
19094                                                            this: val.clone(),
19095                                                            to: DataType::Custom {
19096                                                                name: "DATETIME".to_string(),
19097                                                            },
19098                                                            trailing_comments: vec![],
19099                                                            double_colon_syntax: false,
19100                                                            format: None,
19101                                                            default: None,
19102                                                            inferred_type: None,
19103                                                        },
19104                                                    ))
19105                                                }
19106                                                _ => val.clone(),
19107                                            }
19108                                        }
19109                                        // For CAST(x AS DATE) or DATE literal, Presto needs CAST(CAST(x AS DATE) AS TIMESTAMP)
19110                                        Expression::Cast(c)
19111                                            if matches!(c.to, DataType::Date)
19112                                                && matches!(
19113                                                    target,
19114                                                    DialectType::Presto
19115                                                        | DialectType::Trino
19116                                                        | DialectType::Athena
19117                                                ) =>
19118                                        {
19119                                            Expression::Cast(Box::new(crate::expressions::Cast {
19120                                                this: val.clone(),
19121                                                to: DataType::Timestamp {
19122                                                    timezone: false,
19123                                                    precision: None,
19124                                                },
19125                                                trailing_comments: vec![],
19126                                                double_colon_syntax: false,
19127                                                format: None,
19128                                                default: None,
19129                                                inferred_type: None,
19130                                            }))
19131                                        }
19132                                        Expression::Literal(lit)
19133                                            if matches!(
19134                                                lit.as_ref(),
19135                                                crate::expressions::Literal::Date(_)
19136                                            ) && matches!(
19137                                                target,
19138                                                DialectType::Presto
19139                                                    | DialectType::Trino
19140                                                    | DialectType::Athena
19141                                            ) =>
19142                                        {
19143                                            // DATE 'x' -> CAST(CAST('x' AS DATE) AS TIMESTAMP)
19144                                            let cast_date = Self::date_literal_to_cast(val.clone());
19145                                            Expression::Cast(Box::new(crate::expressions::Cast {
19146                                                this: cast_date,
19147                                                to: DataType::Timestamp {
19148                                                    timezone: false,
19149                                                    precision: None,
19150                                                },
19151                                                trailing_comments: vec![],
19152                                                double_colon_syntax: false,
19153                                                format: None,
19154                                                default: None,
19155                                                inferred_type: None,
19156                                            }))
19157                                        }
19158                                        _ => val.clone(),
19159                                    }
19160                                } else {
19161                                    val.clone()
19162                                };
19163
19164                                match target {
19165                                    DialectType::DuckDB => {
19166                                        if let Expression::Literal(lit) = fmt_expr {
19167                                            if let crate::expressions::Literal::String(s) =
19168                                                lit.as_ref()
19169                                            {
19170                                                let c_fmt = if is_hive_source {
19171                                                    java_to_c_format(s)
19172                                                } else {
19173                                                    s.clone()
19174                                                };
19175                                                Ok(Expression::Function(Box::new(Function::new(
19176                                                    "STRFTIME".to_string(),
19177                                                    vec![cast_val, Expression::string(&c_fmt)],
19178                                                ))))
19179                                            } else {
19180                                                Ok(Expression::Function(Box::new(Function::new(
19181                                                    "STRFTIME".to_string(),
19182                                                    vec![cast_val, fmt_expr.clone()],
19183                                                ))))
19184                                            }
19185                                        } else {
19186                                            Ok(Expression::Function(Box::new(Function::new(
19187                                                "STRFTIME".to_string(),
19188                                                vec![cast_val, fmt_expr.clone()],
19189                                            ))))
19190                                        }
19191                                    }
19192                                    DialectType::Presto
19193                                    | DialectType::Trino
19194                                    | DialectType::Athena => {
19195                                        if is_hive_source {
19196                                            if let Expression::Literal(lit) = fmt_expr {
19197                                                if let crate::expressions::Literal::String(s) =
19198                                                    lit.as_ref()
19199                                                {
19200                                                    let p_fmt = java_to_presto_format(s);
19201                                                    Ok(Expression::Function(Box::new(
19202                                                        Function::new(
19203                                                            "DATE_FORMAT".to_string(),
19204                                                            vec![
19205                                                                cast_val,
19206                                                                Expression::string(&p_fmt),
19207                                                            ],
19208                                                        ),
19209                                                    )))
19210                                                } else {
19211                                                    Ok(Expression::Function(Box::new(
19212                                                        Function::new(
19213                                                            "DATE_FORMAT".to_string(),
19214                                                            vec![cast_val, fmt_expr.clone()],
19215                                                        ),
19216                                                    )))
19217                                                }
19218                                            } else {
19219                                                Ok(Expression::Function(Box::new(Function::new(
19220                                                    "DATE_FORMAT".to_string(),
19221                                                    vec![cast_val, fmt_expr.clone()],
19222                                                ))))
19223                                            }
19224                                        } else {
19225                                            Ok(Expression::Function(Box::new(Function::new(
19226                                                "DATE_FORMAT".to_string(),
19227                                                f.args,
19228                                            ))))
19229                                        }
19230                                    }
19231                                    DialectType::BigQuery => {
19232                                        // DATE_FORMAT(val, fmt) -> FORMAT_DATE(fmt, val)
19233                                        if let Expression::Literal(lit) = fmt_expr {
19234                                            if let crate::expressions::Literal::String(s) =
19235                                                lit.as_ref()
19236                                            {
19237                                                let bq_fmt = if is_hive_source {
19238                                                    java_to_bq_format(s)
19239                                                } else {
19240                                                    java_to_c_format(s)
19241                                                };
19242                                                Ok(Expression::Function(Box::new(Function::new(
19243                                                    "FORMAT_DATE".to_string(),
19244                                                    vec![Expression::string(&bq_fmt), cast_val],
19245                                                ))))
19246                                            } else {
19247                                                Ok(Expression::Function(Box::new(Function::new(
19248                                                    "FORMAT_DATE".to_string(),
19249                                                    vec![fmt_expr.clone(), cast_val],
19250                                                ))))
19251                                            }
19252                                        } else {
19253                                            Ok(Expression::Function(Box::new(Function::new(
19254                                                "FORMAT_DATE".to_string(),
19255                                                vec![fmt_expr.clone(), cast_val],
19256                                            ))))
19257                                        }
19258                                    }
19259                                    DialectType::PostgreSQL | DialectType::Redshift => {
19260                                        if let Expression::Literal(lit) = fmt_expr {
19261                                            if let crate::expressions::Literal::String(s) =
19262                                                lit.as_ref()
19263                                            {
19264                                                let pg_fmt = s
19265                                                    .replace("yyyy", "YYYY")
19266                                                    .replace("MM", "MM")
19267                                                    .replace("dd", "DD")
19268                                                    .replace("HH", "HH24")
19269                                                    .replace("mm", "MI")
19270                                                    .replace("ss", "SS")
19271                                                    .replace("yy", "YY");
19272                                                Ok(Expression::Function(Box::new(Function::new(
19273                                                    "TO_CHAR".to_string(),
19274                                                    vec![val, Expression::string(&pg_fmt)],
19275                                                ))))
19276                                            } else {
19277                                                Ok(Expression::Function(Box::new(Function::new(
19278                                                    "TO_CHAR".to_string(),
19279                                                    vec![val, fmt_expr.clone()],
19280                                                ))))
19281                                            }
19282                                        } else {
19283                                            Ok(Expression::Function(Box::new(Function::new(
19284                                                "TO_CHAR".to_string(),
19285                                                vec![val, fmt_expr.clone()],
19286                                            ))))
19287                                        }
19288                                    }
19289                                    _ => Ok(Expression::Function(f)),
19290                                }
19291                            }
19292                            // DATEDIFF(unit, start, end) - 3-arg form
19293                            // SQLite uses DATEDIFF(date1, date2, unit_string) instead
19294                            "DATEDIFF" if f.args.len() == 3 => {
19295                                let mut args = f.args;
19296                                // SQLite source: args = (date1, date2, unit_string)
19297                                // Standard source: args = (unit, start, end)
19298                                let (_arg0, arg1, arg2, unit_str) =
19299                                    if matches!(source, DialectType::SQLite) {
19300                                        let date1 = args.remove(0);
19301                                        let date2 = args.remove(0);
19302                                        let unit_expr = args.remove(0);
19303                                        let unit_s = Self::get_unit_str_static(&unit_expr);
19304
19305                                        // For SQLite target, generate JULIANDAY arithmetic directly
19306                                        if matches!(target, DialectType::SQLite) {
19307                                            let jd_first = Expression::Function(Box::new(
19308                                                Function::new("JULIANDAY".to_string(), vec![date1]),
19309                                            ));
19310                                            let jd_second = Expression::Function(Box::new(
19311                                                Function::new("JULIANDAY".to_string(), vec![date2]),
19312                                            ));
19313                                            let diff = Expression::Sub(Box::new(
19314                                                crate::expressions::BinaryOp::new(
19315                                                    jd_first, jd_second,
19316                                                ),
19317                                            ));
19318                                            let paren_diff = Expression::Paren(Box::new(
19319                                                crate::expressions::Paren {
19320                                                    this: diff,
19321                                                    trailing_comments: Vec::new(),
19322                                                },
19323                                            ));
19324                                            let adjusted = match unit_s.as_str() {
19325                                                "HOUR" => Expression::Mul(Box::new(
19326                                                    crate::expressions::BinaryOp::new(
19327                                                        paren_diff,
19328                                                        Expression::Literal(Box::new(
19329                                                            Literal::Number("24.0".to_string()),
19330                                                        )),
19331                                                    ),
19332                                                )),
19333                                                "MINUTE" => Expression::Mul(Box::new(
19334                                                    crate::expressions::BinaryOp::new(
19335                                                        paren_diff,
19336                                                        Expression::Literal(Box::new(
19337                                                            Literal::Number("1440.0".to_string()),
19338                                                        )),
19339                                                    ),
19340                                                )),
19341                                                "SECOND" => Expression::Mul(Box::new(
19342                                                    crate::expressions::BinaryOp::new(
19343                                                        paren_diff,
19344                                                        Expression::Literal(Box::new(
19345                                                            Literal::Number("86400.0".to_string()),
19346                                                        )),
19347                                                    ),
19348                                                )),
19349                                                "MONTH" => Expression::Div(Box::new(
19350                                                    crate::expressions::BinaryOp::new(
19351                                                        paren_diff,
19352                                                        Expression::Literal(Box::new(
19353                                                            Literal::Number("30.0".to_string()),
19354                                                        )),
19355                                                    ),
19356                                                )),
19357                                                "YEAR" => Expression::Div(Box::new(
19358                                                    crate::expressions::BinaryOp::new(
19359                                                        paren_diff,
19360                                                        Expression::Literal(Box::new(
19361                                                            Literal::Number("365.0".to_string()),
19362                                                        )),
19363                                                    ),
19364                                                )),
19365                                                _ => paren_diff,
19366                                            };
19367                                            return Ok(Expression::Cast(Box::new(Cast {
19368                                                this: adjusted,
19369                                                to: DataType::Int {
19370                                                    length: None,
19371                                                    integer_spelling: true,
19372                                                },
19373                                                trailing_comments: vec![],
19374                                                double_colon_syntax: false,
19375                                                format: None,
19376                                                default: None,
19377                                                inferred_type: None,
19378                                            })));
19379                                        }
19380
19381                                        // For other targets, remap to standard (unit, start, end) form
19382                                        let unit_ident =
19383                                            Expression::Identifier(Identifier::new(&unit_s));
19384                                        (unit_ident, date1, date2, unit_s)
19385                                    } else {
19386                                        let arg0 = args.remove(0);
19387                                        let arg1 = args.remove(0);
19388                                        let arg2 = args.remove(0);
19389                                        let unit_s = Self::get_unit_str_static(&arg0);
19390                                        (arg0, arg1, arg2, unit_s)
19391                                    };
19392
19393                                // For Hive/Spark source, string literal dates need to be cast
19394                                // Note: Databricks is excluded - it handles string args like standard SQL
19395                                let is_hive_spark =
19396                                    matches!(source, DialectType::Hive | DialectType::Spark);
19397
19398                                match target {
19399                                    DialectType::Snowflake => {
19400                                        let unit =
19401                                            Expression::Identifier(Identifier::new(&unit_str));
19402                                        // Use ensure_to_date_preserved to add TO_DATE with a marker
19403                                        // that prevents the Snowflake TO_DATE handler from converting it to CAST
19404                                        let d1 = if is_hive_spark {
19405                                            Self::ensure_to_date_preserved(arg1)
19406                                        } else {
19407                                            arg1
19408                                        };
19409                                        let d2 = if is_hive_spark {
19410                                            Self::ensure_to_date_preserved(arg2)
19411                                        } else {
19412                                            arg2
19413                                        };
19414                                        Ok(Expression::Function(Box::new(Function::new(
19415                                            "DATEDIFF".to_string(),
19416                                            vec![unit, d1, d2],
19417                                        ))))
19418                                    }
19419                                    DialectType::Redshift => {
19420                                        let unit =
19421                                            Expression::Identifier(Identifier::new(&unit_str));
19422                                        let d1 = if is_hive_spark {
19423                                            Self::ensure_cast_date(arg1)
19424                                        } else {
19425                                            arg1
19426                                        };
19427                                        let d2 = if is_hive_spark {
19428                                            Self::ensure_cast_date(arg2)
19429                                        } else {
19430                                            arg2
19431                                        };
19432                                        Ok(Expression::Function(Box::new(Function::new(
19433                                            "DATEDIFF".to_string(),
19434                                            vec![unit, d1, d2],
19435                                        ))))
19436                                    }
19437                                    DialectType::TSQL => {
19438                                        let unit =
19439                                            Expression::Identifier(Identifier::new(&unit_str));
19440                                        Ok(Expression::Function(Box::new(Function::new(
19441                                            "DATEDIFF".to_string(),
19442                                            vec![unit, arg1, arg2],
19443                                        ))))
19444                                    }
19445                                    DialectType::DuckDB => {
19446                                        let is_redshift_tsql = matches!(
19447                                            source,
19448                                            DialectType::Redshift | DialectType::TSQL
19449                                        );
19450                                        if is_hive_spark {
19451                                            // For Hive/Spark source, CAST string args to DATE and emit DATE_DIFF directly
19452                                            let d1 = Self::ensure_cast_date(arg1);
19453                                            let d2 = Self::ensure_cast_date(arg2);
19454                                            Ok(Expression::Function(Box::new(Function::new(
19455                                                "DATE_DIFF".to_string(),
19456                                                vec![Expression::string(&unit_str), d1, d2],
19457                                            ))))
19458                                        } else if matches!(source, DialectType::Snowflake) {
19459                                            // For Snowflake source: special handling per unit
19460                                            match unit_str.as_str() {
19461                                                "NANOSECOND" => {
19462                                                    // DATEDIFF(NANOSECOND, start, end) -> EPOCH_NS(CAST(end AS TIMESTAMP_NS)) - EPOCH_NS(CAST(start AS TIMESTAMP_NS))
19463                                                    fn cast_to_timestamp_ns(
19464                                                        expr: Expression,
19465                                                    ) -> Expression
19466                                                    {
19467                                                        Expression::Cast(Box::new(Cast {
19468                                                            this: expr,
19469                                                            to: DataType::Custom {
19470                                                                name: "TIMESTAMP_NS".to_string(),
19471                                                            },
19472                                                            trailing_comments: vec![],
19473                                                            double_colon_syntax: false,
19474                                                            format: None,
19475                                                            default: None,
19476                                                            inferred_type: None,
19477                                                        }))
19478                                                    }
19479                                                    let epoch_end = Expression::Function(Box::new(
19480                                                        Function::new(
19481                                                            "EPOCH_NS".to_string(),
19482                                                            vec![cast_to_timestamp_ns(arg2)],
19483                                                        ),
19484                                                    ));
19485                                                    let epoch_start = Expression::Function(
19486                                                        Box::new(Function::new(
19487                                                            "EPOCH_NS".to_string(),
19488                                                            vec![cast_to_timestamp_ns(arg1)],
19489                                                        )),
19490                                                    );
19491                                                    Ok(Expression::Sub(Box::new(BinaryOp::new(
19492                                                        epoch_end,
19493                                                        epoch_start,
19494                                                    ))))
19495                                                }
19496                                                "WEEK" => {
19497                                                    // DATE_DIFF('WEEK', DATE_TRUNC('WEEK', CAST(x AS DATE)), DATE_TRUNC('WEEK', CAST(y AS DATE)))
19498                                                    let d1 = Self::force_cast_date(arg1);
19499                                                    let d2 = Self::force_cast_date(arg2);
19500                                                    let dt1 = Expression::Function(Box::new(
19501                                                        Function::new(
19502                                                            "DATE_TRUNC".to_string(),
19503                                                            vec![Expression::string("WEEK"), d1],
19504                                                        ),
19505                                                    ));
19506                                                    let dt2 = Expression::Function(Box::new(
19507                                                        Function::new(
19508                                                            "DATE_TRUNC".to_string(),
19509                                                            vec![Expression::string("WEEK"), d2],
19510                                                        ),
19511                                                    ));
19512                                                    Ok(Expression::Function(Box::new(
19513                                                        Function::new(
19514                                                            "DATE_DIFF".to_string(),
19515                                                            vec![
19516                                                                Expression::string(&unit_str),
19517                                                                dt1,
19518                                                                dt2,
19519                                                            ],
19520                                                        ),
19521                                                    )))
19522                                                }
19523                                                _ => {
19524                                                    // YEAR, MONTH, QUARTER, DAY, etc.: CAST to DATE
19525                                                    let d1 = Self::force_cast_date(arg1);
19526                                                    let d2 = Self::force_cast_date(arg2);
19527                                                    Ok(Expression::Function(Box::new(
19528                                                        Function::new(
19529                                                            "DATE_DIFF".to_string(),
19530                                                            vec![
19531                                                                Expression::string(&unit_str),
19532                                                                d1,
19533                                                                d2,
19534                                                            ],
19535                                                        ),
19536                                                    )))
19537                                                }
19538                                            }
19539                                        } else if is_redshift_tsql {
19540                                            // For Redshift/TSQL source, CAST args to TIMESTAMP (always)
19541                                            let d1 = Self::force_cast_timestamp(arg1);
19542                                            let d2 = Self::force_cast_timestamp(arg2);
19543                                            Ok(Expression::Function(Box::new(Function::new(
19544                                                "DATE_DIFF".to_string(),
19545                                                vec![Expression::string(&unit_str), d1, d2],
19546                                            ))))
19547                                        } else {
19548                                            // Keep as DATEDIFF so DuckDB's transform_datediff handles
19549                                            // DATE_TRUNC for WEEK, CAST for string literals, etc.
19550                                            let unit =
19551                                                Expression::Identifier(Identifier::new(&unit_str));
19552                                            Ok(Expression::Function(Box::new(Function::new(
19553                                                "DATEDIFF".to_string(),
19554                                                vec![unit, arg1, arg2],
19555                                            ))))
19556                                        }
19557                                    }
19558                                    DialectType::BigQuery => {
19559                                        let is_redshift_tsql = matches!(
19560                                            source,
19561                                            DialectType::Redshift
19562                                                | DialectType::TSQL
19563                                                | DialectType::Snowflake
19564                                        );
19565                                        let cast_d1 = if is_hive_spark {
19566                                            Self::ensure_cast_date(arg1)
19567                                        } else if is_redshift_tsql {
19568                                            Self::force_cast_datetime(arg1)
19569                                        } else {
19570                                            Self::ensure_cast_datetime(arg1)
19571                                        };
19572                                        let cast_d2 = if is_hive_spark {
19573                                            Self::ensure_cast_date(arg2)
19574                                        } else if is_redshift_tsql {
19575                                            Self::force_cast_datetime(arg2)
19576                                        } else {
19577                                            Self::ensure_cast_datetime(arg2)
19578                                        };
19579                                        let unit =
19580                                            Expression::Identifier(Identifier::new(&unit_str));
19581                                        Ok(Expression::Function(Box::new(Function::new(
19582                                            "DATE_DIFF".to_string(),
19583                                            vec![cast_d2, cast_d1, unit],
19584                                        ))))
19585                                    }
19586                                    DialectType::Presto
19587                                    | DialectType::Trino
19588                                    | DialectType::Athena => {
19589                                        // For Hive/Spark source, string literals need double-cast: CAST(CAST(x AS TIMESTAMP) AS DATE)
19590                                        // For Redshift/TSQL source, args need CAST to TIMESTAMP (always)
19591                                        let is_redshift_tsql = matches!(
19592                                            source,
19593                                            DialectType::Redshift
19594                                                | DialectType::TSQL
19595                                                | DialectType::Snowflake
19596                                        );
19597                                        let d1 = if is_hive_spark {
19598                                            Self::double_cast_timestamp_date(arg1)
19599                                        } else if is_redshift_tsql {
19600                                            Self::force_cast_timestamp(arg1)
19601                                        } else {
19602                                            arg1
19603                                        };
19604                                        let d2 = if is_hive_spark {
19605                                            Self::double_cast_timestamp_date(arg2)
19606                                        } else if is_redshift_tsql {
19607                                            Self::force_cast_timestamp(arg2)
19608                                        } else {
19609                                            arg2
19610                                        };
19611                                        Ok(Expression::Function(Box::new(Function::new(
19612                                            "DATE_DIFF".to_string(),
19613                                            vec![Expression::string(&unit_str), d1, d2],
19614                                        ))))
19615                                    }
19616                                    DialectType::Hive => match unit_str.as_str() {
19617                                        "MONTH" => Ok(Expression::Cast(Box::new(Cast {
19618                                            this: Expression::Function(Box::new(Function::new(
19619                                                "MONTHS_BETWEEN".to_string(),
19620                                                vec![arg2, arg1],
19621                                            ))),
19622                                            to: DataType::Int {
19623                                                length: None,
19624                                                integer_spelling: false,
19625                                            },
19626                                            trailing_comments: vec![],
19627                                            double_colon_syntax: false,
19628                                            format: None,
19629                                            default: None,
19630                                            inferred_type: None,
19631                                        }))),
19632                                        "WEEK" => Ok(Expression::Cast(Box::new(Cast {
19633                                            this: Expression::Div(Box::new(
19634                                                crate::expressions::BinaryOp::new(
19635                                                    Expression::Function(Box::new(Function::new(
19636                                                        "DATEDIFF".to_string(),
19637                                                        vec![arg2, arg1],
19638                                                    ))),
19639                                                    Expression::number(7),
19640                                                ),
19641                                            )),
19642                                            to: DataType::Int {
19643                                                length: None,
19644                                                integer_spelling: false,
19645                                            },
19646                                            trailing_comments: vec![],
19647                                            double_colon_syntax: false,
19648                                            format: None,
19649                                            default: None,
19650                                            inferred_type: None,
19651                                        }))),
19652                                        _ => Ok(Expression::Function(Box::new(Function::new(
19653                                            "DATEDIFF".to_string(),
19654                                            vec![arg2, arg1],
19655                                        )))),
19656                                    },
19657                                    DialectType::Spark | DialectType::Databricks => {
19658                                        let unit =
19659                                            Expression::Identifier(Identifier::new(&unit_str));
19660                                        Ok(Expression::Function(Box::new(Function::new(
19661                                            "DATEDIFF".to_string(),
19662                                            vec![unit, arg1, arg2],
19663                                        ))))
19664                                    }
19665                                    _ => {
19666                                        // For Hive/Spark source targeting PostgreSQL etc., cast string literals to DATE
19667                                        let d1 = if is_hive_spark {
19668                                            Self::ensure_cast_date(arg1)
19669                                        } else {
19670                                            arg1
19671                                        };
19672                                        let d2 = if is_hive_spark {
19673                                            Self::ensure_cast_date(arg2)
19674                                        } else {
19675                                            arg2
19676                                        };
19677                                        let unit =
19678                                            Expression::Identifier(Identifier::new(&unit_str));
19679                                        Ok(Expression::Function(Box::new(Function::new(
19680                                            "DATEDIFF".to_string(),
19681                                            vec![unit, d1, d2],
19682                                        ))))
19683                                    }
19684                                }
19685                            }
19686                            // DATEDIFF(end, start) - 2-arg form from Hive/MySQL
19687                            "DATEDIFF" if f.args.len() == 2 => {
19688                                let mut args = f.args;
19689                                let arg0 = args.remove(0);
19690                                let arg1 = args.remove(0);
19691
19692                                // Helper: unwrap TO_DATE(x) -> x (extracts inner arg)
19693                                // Also recognizes TryCast/Cast to DATE that may have been produced by
19694                                // cross-dialect TO_DATE -> TRY_CAST conversion
19695                                let unwrap_to_date = |e: Expression| -> (Expression, bool) {
19696                                    if let Expression::Function(ref f) = e {
19697                                        if f.name.eq_ignore_ascii_case("TO_DATE")
19698                                            && f.args.len() == 1
19699                                        {
19700                                            return (f.args[0].clone(), true);
19701                                        }
19702                                    }
19703                                    // Also recognize TryCast(x, Date) as an already-converted TO_DATE
19704                                    if let Expression::TryCast(ref c) = e {
19705                                        if matches!(c.to, DataType::Date) {
19706                                            return (e, true); // Already properly cast, return as-is
19707                                        }
19708                                    }
19709                                    (e, false)
19710                                };
19711
19712                                match target {
19713                                    DialectType::DuckDB => {
19714                                        // For Hive source, always CAST to DATE
19715                                        // If arg is TO_DATE(x) or TRY_CAST(x AS DATE), use it directly
19716                                        let cast_d0 = if matches!(
19717                                            source,
19718                                            DialectType::Hive
19719                                                | DialectType::Spark
19720                                                | DialectType::Databricks
19721                                        ) {
19722                                            let (inner, was_to_date) = unwrap_to_date(arg1);
19723                                            if was_to_date {
19724                                                // Already a date expression, use directly
19725                                                if matches!(&inner, Expression::TryCast(_)) {
19726                                                    inner // Already TRY_CAST(x AS DATE)
19727                                                } else {
19728                                                    Self::try_cast_date(inner)
19729                                                }
19730                                            } else {
19731                                                Self::force_cast_date(inner)
19732                                            }
19733                                        } else {
19734                                            Self::ensure_cast_date(arg1)
19735                                        };
19736                                        let cast_d1 = if matches!(
19737                                            source,
19738                                            DialectType::Hive
19739                                                | DialectType::Spark
19740                                                | DialectType::Databricks
19741                                        ) {
19742                                            let (inner, was_to_date) = unwrap_to_date(arg0);
19743                                            if was_to_date {
19744                                                if matches!(&inner, Expression::TryCast(_)) {
19745                                                    inner
19746                                                } else {
19747                                                    Self::try_cast_date(inner)
19748                                                }
19749                                            } else {
19750                                                Self::force_cast_date(inner)
19751                                            }
19752                                        } else {
19753                                            Self::ensure_cast_date(arg0)
19754                                        };
19755                                        Ok(Expression::Function(Box::new(Function::new(
19756                                            "DATE_DIFF".to_string(),
19757                                            vec![Expression::string("DAY"), cast_d0, cast_d1],
19758                                        ))))
19759                                    }
19760                                    DialectType::Presto
19761                                    | DialectType::Trino
19762                                    | DialectType::Athena => {
19763                                        // For Hive/Spark source, apply double_cast_timestamp_date
19764                                        // For other sources (MySQL etc.), just swap args without casting
19765                                        if matches!(
19766                                            source,
19767                                            DialectType::Hive
19768                                                | DialectType::Spark
19769                                                | DialectType::Databricks
19770                                        ) {
19771                                            let cast_fn = |e: Expression| -> Expression {
19772                                                let (inner, was_to_date) = unwrap_to_date(e);
19773                                                if was_to_date {
19774                                                    let first_cast =
19775                                                        Self::double_cast_timestamp_date(inner);
19776                                                    Self::double_cast_timestamp_date(first_cast)
19777                                                } else {
19778                                                    Self::double_cast_timestamp_date(inner)
19779                                                }
19780                                            };
19781                                            Ok(Expression::Function(Box::new(Function::new(
19782                                                "DATE_DIFF".to_string(),
19783                                                vec![
19784                                                    Expression::string("DAY"),
19785                                                    cast_fn(arg1),
19786                                                    cast_fn(arg0),
19787                                                ],
19788                                            ))))
19789                                        } else {
19790                                            Ok(Expression::Function(Box::new(Function::new(
19791                                                "DATE_DIFF".to_string(),
19792                                                vec![Expression::string("DAY"), arg1, arg0],
19793                                            ))))
19794                                        }
19795                                    }
19796                                    DialectType::Redshift => {
19797                                        let unit = Expression::Identifier(Identifier::new("DAY"));
19798                                        Ok(Expression::Function(Box::new(Function::new(
19799                                            "DATEDIFF".to_string(),
19800                                            vec![unit, arg1, arg0],
19801                                        ))))
19802                                    }
19803                                    _ => Ok(Expression::Function(Box::new(Function::new(
19804                                        "DATEDIFF".to_string(),
19805                                        vec![arg0, arg1],
19806                                    )))),
19807                                }
19808                            }
19809                            // DATE_DIFF(unit, start, end) - 3-arg with string unit (ClickHouse/DuckDB style)
19810                            "DATE_DIFF" if f.args.len() == 3 => {
19811                                let mut args = f.args;
19812                                let arg0 = args.remove(0);
19813                                let arg1 = args.remove(0);
19814                                let arg2 = args.remove(0);
19815                                let unit_str = Self::get_unit_str_static(&arg0);
19816
19817                                match target {
19818                                    DialectType::DuckDB => {
19819                                        // DuckDB: DATE_DIFF('UNIT', start, end)
19820                                        Ok(Expression::Function(Box::new(Function::new(
19821                                            "DATE_DIFF".to_string(),
19822                                            vec![Expression::string(&unit_str), arg1, arg2],
19823                                        ))))
19824                                    }
19825                                    DialectType::Presto
19826                                    | DialectType::Trino
19827                                    | DialectType::Athena => {
19828                                        Ok(Expression::Function(Box::new(Function::new(
19829                                            "DATE_DIFF".to_string(),
19830                                            vec![Expression::string(&unit_str), arg1, arg2],
19831                                        ))))
19832                                    }
19833                                    DialectType::ClickHouse => {
19834                                        // ClickHouse: DATE_DIFF(UNIT, start, end) - identifier unit
19835                                        let unit =
19836                                            Expression::Identifier(Identifier::new(&unit_str));
19837                                        Ok(Expression::Function(Box::new(Function::new(
19838                                            "DATE_DIFF".to_string(),
19839                                            vec![unit, arg1, arg2],
19840                                        ))))
19841                                    }
19842                                    DialectType::Snowflake | DialectType::Redshift => {
19843                                        let unit =
19844                                            Expression::Identifier(Identifier::new(&unit_str));
19845                                        Ok(Expression::Function(Box::new(Function::new(
19846                                            "DATEDIFF".to_string(),
19847                                            vec![unit, arg1, arg2],
19848                                        ))))
19849                                    }
19850                                    _ => {
19851                                        let unit =
19852                                            Expression::Identifier(Identifier::new(&unit_str));
19853                                        Ok(Expression::Function(Box::new(Function::new(
19854                                            "DATEDIFF".to_string(),
19855                                            vec![unit, arg1, arg2],
19856                                        ))))
19857                                    }
19858                                }
19859                            }
19860                            // DATEADD(unit, val, date) - 3-arg form
19861                            "DATEADD" if f.args.len() == 3 => {
19862                                let mut args = f.args;
19863                                let arg0 = args.remove(0);
19864                                let arg1 = args.remove(0);
19865                                let arg2 = args.remove(0);
19866                                let unit_str = Self::get_unit_str_static(&arg0);
19867
19868                                // Normalize TSQL unit abbreviations to standard names
19869                                let unit_str = match unit_str.as_str() {
19870                                    "YY" | "YYYY" => "YEAR".to_string(),
19871                                    "QQ" | "Q" => "QUARTER".to_string(),
19872                                    "MM" | "M" => "MONTH".to_string(),
19873                                    "WK" | "WW" => "WEEK".to_string(),
19874                                    "DD" | "D" | "DY" => "DAY".to_string(),
19875                                    "HH" => "HOUR".to_string(),
19876                                    "MI" | "N" => "MINUTE".to_string(),
19877                                    "SS" | "S" => "SECOND".to_string(),
19878                                    "MS" => "MILLISECOND".to_string(),
19879                                    "MCS" | "US" => "MICROSECOND".to_string(),
19880                                    _ => unit_str,
19881                                };
19882                                match target {
19883                                    DialectType::Snowflake => {
19884                                        let unit =
19885                                            Expression::Identifier(Identifier::new(&unit_str));
19886                                        // Cast string literal to TIMESTAMP, but not for Snowflake source
19887                                        // (Snowflake natively accepts string literals in DATEADD)
19888                                        let arg2 = if matches!(
19889                                            &arg2,
19890                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
19891                                        ) && !matches!(source, DialectType::Snowflake)
19892                                        {
19893                                            Expression::Cast(Box::new(Cast {
19894                                                this: arg2,
19895                                                to: DataType::Timestamp {
19896                                                    precision: None,
19897                                                    timezone: false,
19898                                                },
19899                                                trailing_comments: Vec::new(),
19900                                                double_colon_syntax: false,
19901                                                format: None,
19902                                                default: None,
19903                                                inferred_type: None,
19904                                            }))
19905                                        } else {
19906                                            arg2
19907                                        };
19908                                        Ok(Expression::Function(Box::new(Function::new(
19909                                            "DATEADD".to_string(),
19910                                            vec![unit, arg1, arg2],
19911                                        ))))
19912                                    }
19913                                    DialectType::TSQL => {
19914                                        let unit =
19915                                            Expression::Identifier(Identifier::new(&unit_str));
19916                                        // Cast string literal to DATETIME2, but not when source is Spark/Databricks family
19917                                        let arg2 = if matches!(
19918                                            &arg2,
19919                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
19920                                        ) && !matches!(
19921                                            source,
19922                                            DialectType::Spark
19923                                                | DialectType::Databricks
19924                                                | DialectType::Hive
19925                                        ) {
19926                                            Expression::Cast(Box::new(Cast {
19927                                                this: arg2,
19928                                                to: DataType::Custom {
19929                                                    name: "DATETIME2".to_string(),
19930                                                },
19931                                                trailing_comments: Vec::new(),
19932                                                double_colon_syntax: false,
19933                                                format: None,
19934                                                default: None,
19935                                                inferred_type: None,
19936                                            }))
19937                                        } else {
19938                                            arg2
19939                                        };
19940                                        Ok(Expression::Function(Box::new(Function::new(
19941                                            "DATEADD".to_string(),
19942                                            vec![unit, arg1, arg2],
19943                                        ))))
19944                                    }
19945                                    DialectType::Redshift => {
19946                                        let unit =
19947                                            Expression::Identifier(Identifier::new(&unit_str));
19948                                        Ok(Expression::Function(Box::new(Function::new(
19949                                            "DATEADD".to_string(),
19950                                            vec![unit, arg1, arg2],
19951                                        ))))
19952                                    }
19953                                    DialectType::Databricks => {
19954                                        let unit =
19955                                            Expression::Identifier(Identifier::new(&unit_str));
19956                                        // Sources with native DATEADD (TSQL, Databricks, Snowflake) -> DATEADD
19957                                        // Other sources (Redshift TsOrDsAdd, etc.) -> DATE_ADD
19958                                        let func_name = if matches!(
19959                                            source,
19960                                            DialectType::TSQL
19961                                                | DialectType::Fabric
19962                                                | DialectType::Databricks
19963                                                | DialectType::Snowflake
19964                                        ) {
19965                                            "DATEADD"
19966                                        } else {
19967                                            "DATE_ADD"
19968                                        };
19969                                        Ok(Expression::Function(Box::new(Function::new(
19970                                            func_name.to_string(),
19971                                            vec![unit, arg1, arg2],
19972                                        ))))
19973                                    }
19974                                    DialectType::DuckDB => {
19975                                        // Special handling for NANOSECOND from Snowflake
19976                                        if unit_str == "NANOSECOND"
19977                                            && matches!(source, DialectType::Snowflake)
19978                                        {
19979                                            // DATEADD(NANOSECOND, offset, ts) -> MAKE_TIMESTAMP_NS(EPOCH_NS(CAST(ts AS TIMESTAMP_NS)) + offset)
19980                                            let cast_ts = Expression::Cast(Box::new(Cast {
19981                                                this: arg2,
19982                                                to: DataType::Custom {
19983                                                    name: "TIMESTAMP_NS".to_string(),
19984                                                },
19985                                                trailing_comments: vec![],
19986                                                double_colon_syntax: false,
19987                                                format: None,
19988                                                default: None,
19989                                                inferred_type: None,
19990                                            }));
19991                                            let epoch_ns =
19992                                                Expression::Function(Box::new(Function::new(
19993                                                    "EPOCH_NS".to_string(),
19994                                                    vec![cast_ts],
19995                                                )));
19996                                            let sum = Expression::Add(Box::new(BinaryOp::new(
19997                                                epoch_ns, arg1,
19998                                            )));
19999                                            Ok(Expression::Function(Box::new(Function::new(
20000                                                "MAKE_TIMESTAMP_NS".to_string(),
20001                                                vec![sum],
20002                                            ))))
20003                                        } else {
20004                                            // DuckDB: convert to date + INTERVAL syntax with CAST
20005                                            let iu = Self::parse_interval_unit_static(&unit_str);
20006                                            let interval = Expression::Interval(Box::new(crate::expressions::Interval {
20007                                                this: Some(arg1),
20008                                                unit: Some(crate::expressions::IntervalUnitSpec::Simple { unit: iu, use_plural: false }),
20009                                            }));
20010                                            // Cast string literal to TIMESTAMP
20011                                            let arg2 = if matches!(
20012                                                &arg2,
20013                                                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20014                                            ) {
20015                                                Expression::Cast(Box::new(Cast {
20016                                                    this: arg2,
20017                                                    to: DataType::Timestamp {
20018                                                        precision: None,
20019                                                        timezone: false,
20020                                                    },
20021                                                    trailing_comments: Vec::new(),
20022                                                    double_colon_syntax: false,
20023                                                    format: None,
20024                                                    default: None,
20025                                                    inferred_type: None,
20026                                                }))
20027                                            } else {
20028                                                arg2
20029                                            };
20030                                            Ok(Expression::Add(Box::new(
20031                                                crate::expressions::BinaryOp::new(arg2, interval),
20032                                            )))
20033                                        }
20034                                    }
20035                                    DialectType::Spark => {
20036                                        // For TSQL source: convert to ADD_MONTHS/DATE_ADD(date, val)
20037                                        // For other sources: keep 3-arg DATE_ADD(UNIT, val, date) form
20038                                        if matches!(source, DialectType::TSQL | DialectType::Fabric)
20039                                        {
20040                                            fn multiply_expr_spark(
20041                                                expr: Expression,
20042                                                factor: i64,
20043                                            ) -> Expression
20044                                            {
20045                                                if let Expression::Literal(lit) = &expr {
20046                                                    if let crate::expressions::Literal::Number(n) =
20047                                                        lit.as_ref()
20048                                                    {
20049                                                        if let Ok(val) = n.parse::<i64>() {
20050                                                            return Expression::Literal(Box::new(
20051                                                                crate::expressions::Literal::Number(
20052                                                                    (val * factor).to_string(),
20053                                                                ),
20054                                                            ));
20055                                                        }
20056                                                    }
20057                                                }
20058                                                Expression::Mul(Box::new(
20059                                                    crate::expressions::BinaryOp::new(
20060                                                        expr,
20061                                                        Expression::Literal(Box::new(
20062                                                            crate::expressions::Literal::Number(
20063                                                                factor.to_string(),
20064                                                            ),
20065                                                        )),
20066                                                    ),
20067                                                ))
20068                                            }
20069                                            let normalized_unit = match unit_str.as_str() {
20070                                                "YEAR" | "YY" | "YYYY" => "YEAR",
20071                                                "QUARTER" | "QQ" | "Q" => "QUARTER",
20072                                                "MONTH" | "MM" | "M" => "MONTH",
20073                                                "WEEK" | "WK" | "WW" => "WEEK",
20074                                                "DAY" | "DD" | "D" | "DY" => "DAY",
20075                                                _ => &unit_str,
20076                                            };
20077                                            match normalized_unit {
20078                                                "YEAR" => {
20079                                                    let months = multiply_expr_spark(arg1, 12);
20080                                                    Ok(Expression::Function(Box::new(
20081                                                        Function::new(
20082                                                            "ADD_MONTHS".to_string(),
20083                                                            vec![arg2, months],
20084                                                        ),
20085                                                    )))
20086                                                }
20087                                                "QUARTER" => {
20088                                                    let months = multiply_expr_spark(arg1, 3);
20089                                                    Ok(Expression::Function(Box::new(
20090                                                        Function::new(
20091                                                            "ADD_MONTHS".to_string(),
20092                                                            vec![arg2, months],
20093                                                        ),
20094                                                    )))
20095                                                }
20096                                                "MONTH" => Ok(Expression::Function(Box::new(
20097                                                    Function::new(
20098                                                        "ADD_MONTHS".to_string(),
20099                                                        vec![arg2, arg1],
20100                                                    ),
20101                                                ))),
20102                                                "WEEK" => {
20103                                                    let days = multiply_expr_spark(arg1, 7);
20104                                                    Ok(Expression::Function(Box::new(
20105                                                        Function::new(
20106                                                            "DATE_ADD".to_string(),
20107                                                            vec![arg2, days],
20108                                                        ),
20109                                                    )))
20110                                                }
20111                                                "DAY" => Ok(Expression::Function(Box::new(
20112                                                    Function::new(
20113                                                        "DATE_ADD".to_string(),
20114                                                        vec![arg2, arg1],
20115                                                    ),
20116                                                ))),
20117                                                _ => {
20118                                                    let unit = Expression::Identifier(
20119                                                        Identifier::new(&unit_str),
20120                                                    );
20121                                                    Ok(Expression::Function(Box::new(
20122                                                        Function::new(
20123                                                            "DATE_ADD".to_string(),
20124                                                            vec![unit, arg1, arg2],
20125                                                        ),
20126                                                    )))
20127                                                }
20128                                            }
20129                                        } else {
20130                                            // Non-TSQL source: keep 3-arg DATE_ADD(UNIT, val, date)
20131                                            let unit =
20132                                                Expression::Identifier(Identifier::new(&unit_str));
20133                                            Ok(Expression::Function(Box::new(Function::new(
20134                                                "DATE_ADD".to_string(),
20135                                                vec![unit, arg1, arg2],
20136                                            ))))
20137                                        }
20138                                    }
20139                                    DialectType::Hive => match unit_str.as_str() {
20140                                        "MONTH" => {
20141                                            Ok(Expression::Function(Box::new(Function::new(
20142                                                "ADD_MONTHS".to_string(),
20143                                                vec![arg2, arg1],
20144                                            ))))
20145                                        }
20146                                        _ => Ok(Expression::Function(Box::new(Function::new(
20147                                            "DATE_ADD".to_string(),
20148                                            vec![arg2, arg1],
20149                                        )))),
20150                                    },
20151                                    DialectType::Presto
20152                                    | DialectType::Trino
20153                                    | DialectType::Athena => {
20154                                        // Cast string literal date to TIMESTAMP
20155                                        let arg2 = if matches!(
20156                                            &arg2,
20157                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20158                                        ) {
20159                                            Expression::Cast(Box::new(Cast {
20160                                                this: arg2,
20161                                                to: DataType::Timestamp {
20162                                                    precision: None,
20163                                                    timezone: false,
20164                                                },
20165                                                trailing_comments: Vec::new(),
20166                                                double_colon_syntax: false,
20167                                                format: None,
20168                                                default: None,
20169                                                inferred_type: None,
20170                                            }))
20171                                        } else {
20172                                            arg2
20173                                        };
20174                                        Ok(Expression::Function(Box::new(Function::new(
20175                                            "DATE_ADD".to_string(),
20176                                            vec![Expression::string(&unit_str), arg1, arg2],
20177                                        ))))
20178                                    }
20179                                    DialectType::MySQL => {
20180                                        let iu = Self::parse_interval_unit_static(&unit_str);
20181                                        Ok(Expression::DateAdd(Box::new(
20182                                            crate::expressions::DateAddFunc {
20183                                                this: arg2,
20184                                                interval: arg1,
20185                                                unit: iu,
20186                                            },
20187                                        )))
20188                                    }
20189                                    DialectType::PostgreSQL => {
20190                                        // Cast string literal date to TIMESTAMP
20191                                        let arg2 = if matches!(
20192                                            &arg2,
20193                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20194                                        ) {
20195                                            Expression::Cast(Box::new(Cast {
20196                                                this: arg2,
20197                                                to: DataType::Timestamp {
20198                                                    precision: None,
20199                                                    timezone: false,
20200                                                },
20201                                                trailing_comments: Vec::new(),
20202                                                double_colon_syntax: false,
20203                                                format: None,
20204                                                default: None,
20205                                                inferred_type: None,
20206                                            }))
20207                                        } else {
20208                                            arg2
20209                                        };
20210                                        let interval = Expression::Interval(Box::new(
20211                                            crate::expressions::Interval {
20212                                                this: Some(Expression::string(&format!(
20213                                                    "{} {}",
20214                                                    Self::expr_to_string_static(&arg1),
20215                                                    unit_str
20216                                                ))),
20217                                                unit: None,
20218                                            },
20219                                        ));
20220                                        Ok(Expression::Add(Box::new(
20221                                            crate::expressions::BinaryOp::new(arg2, interval),
20222                                        )))
20223                                    }
20224                                    DialectType::BigQuery => {
20225                                        let iu = Self::parse_interval_unit_static(&unit_str);
20226                                        let interval = Expression::Interval(Box::new(
20227                                            crate::expressions::Interval {
20228                                                this: Some(arg1),
20229                                                unit: Some(
20230                                                    crate::expressions::IntervalUnitSpec::Simple {
20231                                                        unit: iu,
20232                                                        use_plural: false,
20233                                                    },
20234                                                ),
20235                                            },
20236                                        ));
20237                                        // Non-TSQL sources: CAST string literal to DATETIME
20238                                        let arg2 = if !matches!(
20239                                            source,
20240                                            DialectType::TSQL | DialectType::Fabric
20241                                        ) && matches!(
20242                                            &arg2,
20243                                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
20244                                        ) {
20245                                            Expression::Cast(Box::new(Cast {
20246                                                this: arg2,
20247                                                to: DataType::Custom {
20248                                                    name: "DATETIME".to_string(),
20249                                                },
20250                                                trailing_comments: Vec::new(),
20251                                                double_colon_syntax: false,
20252                                                format: None,
20253                                                default: None,
20254                                                inferred_type: None,
20255                                            }))
20256                                        } else {
20257                                            arg2
20258                                        };
20259                                        Ok(Expression::Function(Box::new(Function::new(
20260                                            "DATE_ADD".to_string(),
20261                                            vec![arg2, interval],
20262                                        ))))
20263                                    }
20264                                    _ => {
20265                                        let unit =
20266                                            Expression::Identifier(Identifier::new(&unit_str));
20267                                        Ok(Expression::Function(Box::new(Function::new(
20268                                            "DATEADD".to_string(),
20269                                            vec![unit, arg1, arg2],
20270                                        ))))
20271                                    }
20272                                }
20273                            }
20274                            // DATE_ADD - 3-arg: either (unit, val, date) from Presto/ClickHouse
20275                            // or (date, val, 'UNIT') from Generic canonical form
20276                            "DATE_ADD" if f.args.len() == 3 => {
20277                                let mut args = f.args;
20278                                let arg0 = args.remove(0);
20279                                let arg1 = args.remove(0);
20280                                let arg2 = args.remove(0);
20281                                // Detect Generic canonical form: DATE_ADD(date, amount, 'UNIT')
20282                                // where arg2 is a string literal matching a unit name
20283                                let arg2_unit = match &arg2 {
20284                                    Expression::Literal(lit)
20285                                        if matches!(lit.as_ref(), Literal::String(_)) =>
20286                                    {
20287                                        let Literal::String(s) = lit.as_ref() else {
20288                                            unreachable!()
20289                                        };
20290                                        let u = s.to_ascii_uppercase();
20291                                        if matches!(
20292                                            u.as_str(),
20293                                            "DAY"
20294                                                | "MONTH"
20295                                                | "YEAR"
20296                                                | "HOUR"
20297                                                | "MINUTE"
20298                                                | "SECOND"
20299                                                | "WEEK"
20300                                                | "QUARTER"
20301                                                | "MILLISECOND"
20302                                                | "MICROSECOND"
20303                                        ) {
20304                                            Some(u)
20305                                        } else {
20306                                            None
20307                                        }
20308                                    }
20309                                    _ => None,
20310                                };
20311                                // Reorder: if arg2 is the unit, swap to (unit, val, date) form
20312                                let (unit_str, val, date) = if let Some(u) = arg2_unit {
20313                                    (u, arg1, arg0)
20314                                } else {
20315                                    (Self::get_unit_str_static(&arg0), arg1, arg2)
20316                                };
20317                                // Alias for backward compat with the rest of the match
20318                                let arg1 = val;
20319                                let arg2 = date;
20320
20321                                match target {
20322                                    DialectType::Presto
20323                                    | DialectType::Trino
20324                                    | DialectType::Athena => {
20325                                        Ok(Expression::Function(Box::new(Function::new(
20326                                            "DATE_ADD".to_string(),
20327                                            vec![Expression::string(&unit_str), arg1, arg2],
20328                                        ))))
20329                                    }
20330                                    DialectType::DuckDB => {
20331                                        let iu = Self::parse_interval_unit_static(&unit_str);
20332                                        let interval = Expression::Interval(Box::new(
20333                                            crate::expressions::Interval {
20334                                                this: Some(arg1),
20335                                                unit: Some(
20336                                                    crate::expressions::IntervalUnitSpec::Simple {
20337                                                        unit: iu,
20338                                                        use_plural: false,
20339                                                    },
20340                                                ),
20341                                            },
20342                                        ));
20343                                        Ok(Expression::Add(Box::new(
20344                                            crate::expressions::BinaryOp::new(arg2, interval),
20345                                        )))
20346                                    }
20347                                    DialectType::PostgreSQL
20348                                    | DialectType::Materialize
20349                                    | DialectType::RisingWave => {
20350                                        // PostgreSQL: x + INTERVAL '1 DAY'
20351                                        let amount_str = Self::expr_to_string_static(&arg1);
20352                                        let interval = Expression::Interval(Box::new(
20353                                            crate::expressions::Interval {
20354                                                this: Some(Expression::string(&format!(
20355                                                    "{} {}",
20356                                                    amount_str, unit_str
20357                                                ))),
20358                                                unit: None,
20359                                            },
20360                                        ));
20361                                        Ok(Expression::Add(Box::new(
20362                                            crate::expressions::BinaryOp::new(arg2, interval),
20363                                        )))
20364                                    }
20365                                    DialectType::Snowflake
20366                                    | DialectType::TSQL
20367                                    | DialectType::Redshift => {
20368                                        let unit =
20369                                            Expression::Identifier(Identifier::new(&unit_str));
20370                                        Ok(Expression::Function(Box::new(Function::new(
20371                                            "DATEADD".to_string(),
20372                                            vec![unit, arg1, arg2],
20373                                        ))))
20374                                    }
20375                                    DialectType::BigQuery
20376                                    | DialectType::MySQL
20377                                    | DialectType::Doris
20378                                    | DialectType::StarRocks
20379                                    | DialectType::Drill => {
20380                                        // DATE_ADD(date, INTERVAL amount UNIT)
20381                                        let iu = Self::parse_interval_unit_static(&unit_str);
20382                                        let interval = Expression::Interval(Box::new(
20383                                            crate::expressions::Interval {
20384                                                this: Some(arg1),
20385                                                unit: Some(
20386                                                    crate::expressions::IntervalUnitSpec::Simple {
20387                                                        unit: iu,
20388                                                        use_plural: false,
20389                                                    },
20390                                                ),
20391                                            },
20392                                        ));
20393                                        Ok(Expression::Function(Box::new(Function::new(
20394                                            "DATE_ADD".to_string(),
20395                                            vec![arg2, interval],
20396                                        ))))
20397                                    }
20398                                    DialectType::SQLite => {
20399                                        // SQLite: DATE(x, '1 DAY')
20400                                        // Build the string '1 DAY' from amount and unit
20401                                        let amount_str = match &arg1 {
20402                                            Expression::Literal(lit)
20403                                                if matches!(lit.as_ref(), Literal::Number(_)) =>
20404                                            {
20405                                                let Literal::Number(n) = lit.as_ref() else {
20406                                                    unreachable!()
20407                                                };
20408                                                n.clone()
20409                                            }
20410                                            _ => "1".to_string(),
20411                                        };
20412                                        Ok(Expression::Function(Box::new(Function::new(
20413                                            "DATE".to_string(),
20414                                            vec![
20415                                                arg2,
20416                                                Expression::string(format!(
20417                                                    "{} {}",
20418                                                    amount_str, unit_str
20419                                                )),
20420                                            ],
20421                                        ))))
20422                                    }
20423                                    DialectType::Dremio => {
20424                                        // Dremio: DATE_ADD(date, amount) - drops unit
20425                                        Ok(Expression::Function(Box::new(Function::new(
20426                                            "DATE_ADD".to_string(),
20427                                            vec![arg2, arg1],
20428                                        ))))
20429                                    }
20430                                    DialectType::Spark => {
20431                                        // Spark: DATE_ADD(date, val) for DAY, or DATEADD(UNIT, val, date)
20432                                        if unit_str == "DAY" {
20433                                            Ok(Expression::Function(Box::new(Function::new(
20434                                                "DATE_ADD".to_string(),
20435                                                vec![arg2, arg1],
20436                                            ))))
20437                                        } else {
20438                                            let unit =
20439                                                Expression::Identifier(Identifier::new(&unit_str));
20440                                            Ok(Expression::Function(Box::new(Function::new(
20441                                                "DATE_ADD".to_string(),
20442                                                vec![unit, arg1, arg2],
20443                                            ))))
20444                                        }
20445                                    }
20446                                    DialectType::Databricks => {
20447                                        let unit =
20448                                            Expression::Identifier(Identifier::new(&unit_str));
20449                                        Ok(Expression::Function(Box::new(Function::new(
20450                                            "DATE_ADD".to_string(),
20451                                            vec![unit, arg1, arg2],
20452                                        ))))
20453                                    }
20454                                    DialectType::Hive => {
20455                                        // Hive: DATE_ADD(date, val) for DAY
20456                                        Ok(Expression::Function(Box::new(Function::new(
20457                                            "DATE_ADD".to_string(),
20458                                            vec![arg2, arg1],
20459                                        ))))
20460                                    }
20461                                    _ => {
20462                                        let unit =
20463                                            Expression::Identifier(Identifier::new(&unit_str));
20464                                        Ok(Expression::Function(Box::new(Function::new(
20465                                            "DATE_ADD".to_string(),
20466                                            vec![unit, arg1, arg2],
20467                                        ))))
20468                                    }
20469                                }
20470                            }
20471                            // DATE_ADD(date, days) - 2-arg Hive/Spark/Generic form (add days)
20472                            "DATE_ADD"
20473                                if f.args.len() == 2
20474                                    && matches!(
20475                                        source,
20476                                        DialectType::Hive
20477                                            | DialectType::Spark
20478                                            | DialectType::Databricks
20479                                            | DialectType::Generic
20480                                    ) =>
20481                            {
20482                                let mut args = f.args;
20483                                let date = args.remove(0);
20484                                let days = args.remove(0);
20485                                match target {
20486                                    DialectType::Hive | DialectType::Spark => {
20487                                        // Keep as DATE_ADD(date, days) for Hive/Spark
20488                                        Ok(Expression::Function(Box::new(Function::new(
20489                                            "DATE_ADD".to_string(),
20490                                            vec![date, days],
20491                                        ))))
20492                                    }
20493                                    DialectType::Databricks => Ok(Expression::Function(Box::new(
20494                                        Function::new("DATE_ADD".to_string(), vec![date, days]),
20495                                    ))),
20496                                    DialectType::DuckDB => {
20497                                        // DuckDB: CAST(date AS DATE) + INTERVAL days DAY
20498                                        let cast_date = Self::ensure_cast_date(date);
20499                                        // Wrap complex expressions (like Mul from DATE_SUB negation) in Paren
20500                                        let interval_val = if matches!(
20501                                            days,
20502                                            Expression::Mul(_)
20503                                                | Expression::Sub(_)
20504                                                | Expression::Add(_)
20505                                        ) {
20506                                            Expression::Paren(Box::new(crate::expressions::Paren {
20507                                                this: days,
20508                                                trailing_comments: vec![],
20509                                            }))
20510                                        } else {
20511                                            days
20512                                        };
20513                                        let interval = Expression::Interval(Box::new(
20514                                            crate::expressions::Interval {
20515                                                this: Some(interval_val),
20516                                                unit: Some(
20517                                                    crate::expressions::IntervalUnitSpec::Simple {
20518                                                        unit: crate::expressions::IntervalUnit::Day,
20519                                                        use_plural: false,
20520                                                    },
20521                                                ),
20522                                            },
20523                                        ));
20524                                        Ok(Expression::Add(Box::new(
20525                                            crate::expressions::BinaryOp::new(cast_date, interval),
20526                                        )))
20527                                    }
20528                                    DialectType::Snowflake => {
20529                                        // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
20530                                        let cast_date = if matches!(
20531                                            source,
20532                                            DialectType::Hive
20533                                                | DialectType::Spark
20534                                                | DialectType::Databricks
20535                                        ) {
20536                                            if matches!(
20537                                                date,
20538                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20539                                            ) {
20540                                                Self::double_cast_timestamp_date(date)
20541                                            } else {
20542                                                date
20543                                            }
20544                                        } else {
20545                                            date
20546                                        };
20547                                        Ok(Expression::Function(Box::new(Function::new(
20548                                            "DATEADD".to_string(),
20549                                            vec![
20550                                                Expression::Identifier(Identifier::new("DAY")),
20551                                                days,
20552                                                cast_date,
20553                                            ],
20554                                        ))))
20555                                    }
20556                                    DialectType::Redshift => {
20557                                        Ok(Expression::Function(Box::new(Function::new(
20558                                            "DATEADD".to_string(),
20559                                            vec![
20560                                                Expression::Identifier(Identifier::new("DAY")),
20561                                                days,
20562                                                date,
20563                                            ],
20564                                        ))))
20565                                    }
20566                                    DialectType::TSQL | DialectType::Fabric => {
20567                                        // For Hive source with string literal date, use CAST(CAST(date AS DATETIME2) AS DATE)
20568                                        // But Databricks DATE_ADD doesn't need this wrapping for TSQL
20569                                        let cast_date = if matches!(
20570                                            source,
20571                                            DialectType::Hive
20572                                                | DialectType::Spark
20573                                                | DialectType::Databricks
20574                                        ) {
20575                                            if matches!(
20576                                                date,
20577                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20578                                            ) {
20579                                                Self::double_cast_datetime2_date(date)
20580                                            } else {
20581                                                date
20582                                            }
20583                                        } else {
20584                                            date
20585                                        };
20586                                        Ok(Expression::Function(Box::new(Function::new(
20587                                            "DATEADD".to_string(),
20588                                            vec![
20589                                                Expression::Identifier(Identifier::new("DAY")),
20590                                                days,
20591                                                cast_date,
20592                                            ],
20593                                        ))))
20594                                    }
20595                                    DialectType::Presto
20596                                    | DialectType::Trino
20597                                    | DialectType::Athena => {
20598                                        // For Hive source with string literal date, use CAST(CAST(date AS TIMESTAMP) AS DATE)
20599                                        let cast_date = if matches!(
20600                                            source,
20601                                            DialectType::Hive
20602                                                | DialectType::Spark
20603                                                | DialectType::Databricks
20604                                        ) {
20605                                            if matches!(
20606                                                date,
20607                                                Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_))
20608                                            ) {
20609                                                Self::double_cast_timestamp_date(date)
20610                                            } else {
20611                                                date
20612                                            }
20613                                        } else {
20614                                            date
20615                                        };
20616                                        Ok(Expression::Function(Box::new(Function::new(
20617                                            "DATE_ADD".to_string(),
20618                                            vec![Expression::string("DAY"), days, cast_date],
20619                                        ))))
20620                                    }
20621                                    DialectType::BigQuery => {
20622                                        // For Hive/Spark source, wrap date in CAST(CAST(date AS DATETIME) AS DATE)
20623                                        let cast_date = if matches!(
20624                                            source,
20625                                            DialectType::Hive
20626                                                | DialectType::Spark
20627                                                | DialectType::Databricks
20628                                        ) {
20629                                            Self::double_cast_datetime_date(date)
20630                                        } else {
20631                                            date
20632                                        };
20633                                        // Wrap complex expressions in Paren for interval
20634                                        let interval_val = if matches!(
20635                                            days,
20636                                            Expression::Mul(_)
20637                                                | Expression::Sub(_)
20638                                                | Expression::Add(_)
20639                                        ) {
20640                                            Expression::Paren(Box::new(crate::expressions::Paren {
20641                                                this: days,
20642                                                trailing_comments: vec![],
20643                                            }))
20644                                        } else {
20645                                            days
20646                                        };
20647                                        let interval = Expression::Interval(Box::new(
20648                                            crate::expressions::Interval {
20649                                                this: Some(interval_val),
20650                                                unit: Some(
20651                                                    crate::expressions::IntervalUnitSpec::Simple {
20652                                                        unit: crate::expressions::IntervalUnit::Day,
20653                                                        use_plural: false,
20654                                                    },
20655                                                ),
20656                                            },
20657                                        ));
20658                                        Ok(Expression::Function(Box::new(Function::new(
20659                                            "DATE_ADD".to_string(),
20660                                            vec![cast_date, interval],
20661                                        ))))
20662                                    }
20663                                    DialectType::MySQL => {
20664                                        let iu = crate::expressions::IntervalUnit::Day;
20665                                        Ok(Expression::DateAdd(Box::new(
20666                                            crate::expressions::DateAddFunc {
20667                                                this: date,
20668                                                interval: days,
20669                                                unit: iu,
20670                                            },
20671                                        )))
20672                                    }
20673                                    DialectType::PostgreSQL => {
20674                                        let interval = Expression::Interval(Box::new(
20675                                            crate::expressions::Interval {
20676                                                this: Some(Expression::string(&format!(
20677                                                    "{} DAY",
20678                                                    Self::expr_to_string_static(&days)
20679                                                ))),
20680                                                unit: None,
20681                                            },
20682                                        ));
20683                                        Ok(Expression::Add(Box::new(
20684                                            crate::expressions::BinaryOp::new(date, interval),
20685                                        )))
20686                                    }
20687                                    DialectType::Doris
20688                                    | DialectType::StarRocks
20689                                    | DialectType::Drill => {
20690                                        // DATE_ADD(date, INTERVAL days DAY)
20691                                        let interval = Expression::Interval(Box::new(
20692                                            crate::expressions::Interval {
20693                                                this: Some(days),
20694                                                unit: Some(
20695                                                    crate::expressions::IntervalUnitSpec::Simple {
20696                                                        unit: crate::expressions::IntervalUnit::Day,
20697                                                        use_plural: false,
20698                                                    },
20699                                                ),
20700                                            },
20701                                        ));
20702                                        Ok(Expression::Function(Box::new(Function::new(
20703                                            "DATE_ADD".to_string(),
20704                                            vec![date, interval],
20705                                        ))))
20706                                    }
20707                                    _ => Ok(Expression::Function(Box::new(Function::new(
20708                                        "DATE_ADD".to_string(),
20709                                        vec![date, days],
20710                                    )))),
20711                                }
20712                            }
20713                            // DATE_ADD(date, INTERVAL val UNIT) - MySQL 2-arg form with INTERVAL as 2nd arg
20714                            "DATE_ADD"
20715                                if f.args.len() == 2
20716                                    && matches!(
20717                                        source,
20718                                        DialectType::MySQL | DialectType::SingleStore
20719                                    )
20720                                    && matches!(&f.args[1], Expression::Interval(_)) =>
20721                            {
20722                                let mut args = f.args;
20723                                let date = args.remove(0);
20724                                let interval_expr = args.remove(0);
20725                                let (val, unit) = Self::extract_interval_parts(&interval_expr)
20726                                    .unwrap_or_else(|| {
20727                                        (
20728                                            interval_expr.clone(),
20729                                            crate::expressions::IntervalUnit::Day,
20730                                        )
20731                                    });
20732                                let unit_str = Self::interval_unit_to_string(&unit);
20733                                let is_literal = matches!(&val,
20734                                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_) | Literal::String(_))
20735                                );
20736
20737                                match target {
20738                                    DialectType::MySQL | DialectType::SingleStore => {
20739                                        // Keep as DATE_ADD(date, INTERVAL val UNIT)
20740                                        Ok(Expression::Function(Box::new(Function::new(
20741                                            "DATE_ADD".to_string(),
20742                                            vec![date, interval_expr],
20743                                        ))))
20744                                    }
20745                                    DialectType::PostgreSQL => {
20746                                        if is_literal {
20747                                            // Literal: date + INTERVAL 'val UNIT'
20748                                            let interval = Expression::Interval(Box::new(
20749                                                crate::expressions::Interval {
20750                                                    this: Some(Expression::Literal(Box::new(
20751                                                        Literal::String(format!(
20752                                                            "{} {}",
20753                                                            Self::expr_to_string(&val),
20754                                                            unit_str
20755                                                        )),
20756                                                    ))),
20757                                                    unit: None,
20758                                                },
20759                                            ));
20760                                            Ok(Expression::Add(Box::new(
20761                                                crate::expressions::BinaryOp::new(date, interval),
20762                                            )))
20763                                        } else {
20764                                            // Non-literal (column ref): date + INTERVAL '1 UNIT' * val
20765                                            let interval_one = Expression::Interval(Box::new(
20766                                                crate::expressions::Interval {
20767                                                    this: Some(Expression::Literal(Box::new(
20768                                                        Literal::String(format!("1 {}", unit_str)),
20769                                                    ))),
20770                                                    unit: None,
20771                                                },
20772                                            ));
20773                                            let mul = Expression::Mul(Box::new(
20774                                                crate::expressions::BinaryOp::new(
20775                                                    interval_one,
20776                                                    val,
20777                                                ),
20778                                            ));
20779                                            Ok(Expression::Add(Box::new(
20780                                                crate::expressions::BinaryOp::new(date, mul),
20781                                            )))
20782                                        }
20783                                    }
20784                                    _ => {
20785                                        // Default: keep as DATE_ADD(date, interval)
20786                                        Ok(Expression::Function(Box::new(Function::new(
20787                                            "DATE_ADD".to_string(),
20788                                            vec![date, interval_expr],
20789                                        ))))
20790                                    }
20791                                }
20792                            }
20793                            // DATE_SUB(date, days) - 2-arg Hive/Spark form (subtract days)
20794                            "DATE_SUB"
20795                                if f.args.len() == 2
20796                                    && matches!(
20797                                        source,
20798                                        DialectType::Hive
20799                                            | DialectType::Spark
20800                                            | DialectType::Databricks
20801                                    ) =>
20802                            {
20803                                let mut args = f.args;
20804                                let date = args.remove(0);
20805                                let days = args.remove(0);
20806                                // Helper to create days * -1
20807                                let make_neg_days = |d: Expression| -> Expression {
20808                                    Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
20809                                        d,
20810                                        Expression::Literal(Box::new(Literal::Number(
20811                                            "-1".to_string(),
20812                                        ))),
20813                                    )))
20814                                };
20815                                let is_string_literal = matches!(date, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)));
20816                                match target {
20817                                    DialectType::Hive
20818                                    | DialectType::Spark
20819                                    | DialectType::Databricks => {
20820                                        // Keep as DATE_SUB(date, days) for Hive/Spark
20821                                        Ok(Expression::Function(Box::new(Function::new(
20822                                            "DATE_SUB".to_string(),
20823                                            vec![date, days],
20824                                        ))))
20825                                    }
20826                                    DialectType::DuckDB => {
20827                                        let cast_date = Self::ensure_cast_date(date);
20828                                        let neg = make_neg_days(days);
20829                                        let interval = Expression::Interval(Box::new(
20830                                            crate::expressions::Interval {
20831                                                this: Some(Expression::Paren(Box::new(
20832                                                    crate::expressions::Paren {
20833                                                        this: neg,
20834                                                        trailing_comments: vec![],
20835                                                    },
20836                                                ))),
20837                                                unit: Some(
20838                                                    crate::expressions::IntervalUnitSpec::Simple {
20839                                                        unit: crate::expressions::IntervalUnit::Day,
20840                                                        use_plural: false,
20841                                                    },
20842                                                ),
20843                                            },
20844                                        ));
20845                                        Ok(Expression::Add(Box::new(
20846                                            crate::expressions::BinaryOp::new(cast_date, interval),
20847                                        )))
20848                                    }
20849                                    DialectType::Snowflake => {
20850                                        let cast_date = if is_string_literal {
20851                                            Self::double_cast_timestamp_date(date)
20852                                        } else {
20853                                            date
20854                                        };
20855                                        let neg = make_neg_days(days);
20856                                        Ok(Expression::Function(Box::new(Function::new(
20857                                            "DATEADD".to_string(),
20858                                            vec![
20859                                                Expression::Identifier(Identifier::new("DAY")),
20860                                                neg,
20861                                                cast_date,
20862                                            ],
20863                                        ))))
20864                                    }
20865                                    DialectType::Redshift => {
20866                                        let neg = make_neg_days(days);
20867                                        Ok(Expression::Function(Box::new(Function::new(
20868                                            "DATEADD".to_string(),
20869                                            vec![
20870                                                Expression::Identifier(Identifier::new("DAY")),
20871                                                neg,
20872                                                date,
20873                                            ],
20874                                        ))))
20875                                    }
20876                                    DialectType::TSQL | DialectType::Fabric => {
20877                                        let cast_date = if is_string_literal {
20878                                            Self::double_cast_datetime2_date(date)
20879                                        } else {
20880                                            date
20881                                        };
20882                                        let neg = make_neg_days(days);
20883                                        Ok(Expression::Function(Box::new(Function::new(
20884                                            "DATEADD".to_string(),
20885                                            vec![
20886                                                Expression::Identifier(Identifier::new("DAY")),
20887                                                neg,
20888                                                cast_date,
20889                                            ],
20890                                        ))))
20891                                    }
20892                                    DialectType::Presto
20893                                    | DialectType::Trino
20894                                    | DialectType::Athena => {
20895                                        let cast_date = if is_string_literal {
20896                                            Self::double_cast_timestamp_date(date)
20897                                        } else {
20898                                            date
20899                                        };
20900                                        let neg = make_neg_days(days);
20901                                        Ok(Expression::Function(Box::new(Function::new(
20902                                            "DATE_ADD".to_string(),
20903                                            vec![Expression::string("DAY"), neg, cast_date],
20904                                        ))))
20905                                    }
20906                                    DialectType::BigQuery => {
20907                                        let cast_date = if is_string_literal {
20908                                            Self::double_cast_datetime_date(date)
20909                                        } else {
20910                                            date
20911                                        };
20912                                        let neg = make_neg_days(days);
20913                                        let interval = Expression::Interval(Box::new(
20914                                            crate::expressions::Interval {
20915                                                this: Some(Expression::Paren(Box::new(
20916                                                    crate::expressions::Paren {
20917                                                        this: neg,
20918                                                        trailing_comments: vec![],
20919                                                    },
20920                                                ))),
20921                                                unit: Some(
20922                                                    crate::expressions::IntervalUnitSpec::Simple {
20923                                                        unit: crate::expressions::IntervalUnit::Day,
20924                                                        use_plural: false,
20925                                                    },
20926                                                ),
20927                                            },
20928                                        ));
20929                                        Ok(Expression::Function(Box::new(Function::new(
20930                                            "DATE_ADD".to_string(),
20931                                            vec![cast_date, interval],
20932                                        ))))
20933                                    }
20934                                    _ => Ok(Expression::Function(Box::new(Function::new(
20935                                        "DATE_SUB".to_string(),
20936                                        vec![date, days],
20937                                    )))),
20938                                }
20939                            }
20940                            // ADD_MONTHS(date, val) -> target-specific
20941                            "ADD_MONTHS" if f.args.len() == 2 => {
20942                                let mut args = f.args;
20943                                let date = args.remove(0);
20944                                let val = args.remove(0);
20945                                match target {
20946                                    DialectType::TSQL => {
20947                                        let cast_date = Self::ensure_cast_datetime2(date);
20948                                        Ok(Expression::Function(Box::new(Function::new(
20949                                            "DATEADD".to_string(),
20950                                            vec![
20951                                                Expression::Identifier(Identifier::new("MONTH")),
20952                                                val,
20953                                                cast_date,
20954                                            ],
20955                                        ))))
20956                                    }
20957                                    DialectType::DuckDB => {
20958                                        let interval = Expression::Interval(Box::new(
20959                                            crate::expressions::Interval {
20960                                                this: Some(val),
20961                                                unit: Some(
20962                                                    crate::expressions::IntervalUnitSpec::Simple {
20963                                                        unit:
20964                                                            crate::expressions::IntervalUnit::Month,
20965                                                        use_plural: false,
20966                                                    },
20967                                                ),
20968                                            },
20969                                        ));
20970                                        Ok(Expression::Add(Box::new(
20971                                            crate::expressions::BinaryOp::new(date, interval),
20972                                        )))
20973                                    }
20974                                    DialectType::Snowflake => {
20975                                        // Keep ADD_MONTHS when source is Snowflake
20976                                        if matches!(source, DialectType::Snowflake) {
20977                                            Ok(Expression::Function(Box::new(Function::new(
20978                                                "ADD_MONTHS".to_string(),
20979                                                vec![date, val],
20980                                            ))))
20981                                        } else {
20982                                            Ok(Expression::Function(Box::new(Function::new(
20983                                                "DATEADD".to_string(),
20984                                                vec![
20985                                                    Expression::Identifier(Identifier::new(
20986                                                        "MONTH",
20987                                                    )),
20988                                                    val,
20989                                                    date,
20990                                                ],
20991                                            ))))
20992                                        }
20993                                    }
20994                                    DialectType::Redshift => {
20995                                        Ok(Expression::Function(Box::new(Function::new(
20996                                            "DATEADD".to_string(),
20997                                            vec![
20998                                                Expression::Identifier(Identifier::new("MONTH")),
20999                                                val,
21000                                                date,
21001                                            ],
21002                                        ))))
21003                                    }
21004                                    DialectType::Presto
21005                                    | DialectType::Trino
21006                                    | DialectType::Athena => {
21007                                        Ok(Expression::Function(Box::new(Function::new(
21008                                            "DATE_ADD".to_string(),
21009                                            vec![Expression::string("MONTH"), val, date],
21010                                        ))))
21011                                    }
21012                                    DialectType::BigQuery => {
21013                                        let interval = Expression::Interval(Box::new(
21014                                            crate::expressions::Interval {
21015                                                this: Some(val),
21016                                                unit: Some(
21017                                                    crate::expressions::IntervalUnitSpec::Simple {
21018                                                        unit:
21019                                                            crate::expressions::IntervalUnit::Month,
21020                                                        use_plural: false,
21021                                                    },
21022                                                ),
21023                                            },
21024                                        ));
21025                                        Ok(Expression::Function(Box::new(Function::new(
21026                                            "DATE_ADD".to_string(),
21027                                            vec![date, interval],
21028                                        ))))
21029                                    }
21030                                    _ => Ok(Expression::Function(Box::new(Function::new(
21031                                        "ADD_MONTHS".to_string(),
21032                                        vec![date, val],
21033                                    )))),
21034                                }
21035                            }
21036                            // DATETRUNC(unit, date) - TSQL form -> DATE_TRUNC for other targets
21037                            "DATETRUNC" if f.args.len() == 2 => {
21038                                let mut args = f.args;
21039                                let arg0 = args.remove(0);
21040                                let arg1 = args.remove(0);
21041                                let unit_str = Self::get_unit_str_static(&arg0);
21042                                match target {
21043                                    DialectType::TSQL | DialectType::Fabric => {
21044                                        // Keep as DATETRUNC for TSQL - the target handler will uppercase the unit
21045                                        Ok(Expression::Function(Box::new(Function::new(
21046                                            "DATETRUNC".to_string(),
21047                                            vec![
21048                                                Expression::Identifier(Identifier::new(&unit_str)),
21049                                                arg1,
21050                                            ],
21051                                        ))))
21052                                    }
21053                                    DialectType::DuckDB => {
21054                                        // DuckDB: DATE_TRUNC('UNIT', expr) with CAST for string literals
21055                                        let date = Self::ensure_cast_timestamp(arg1);
21056                                        Ok(Expression::Function(Box::new(Function::new(
21057                                            "DATE_TRUNC".to_string(),
21058                                            vec![Expression::string(&unit_str), date],
21059                                        ))))
21060                                    }
21061                                    DialectType::ClickHouse => {
21062                                        // ClickHouse: dateTrunc('UNIT', expr)
21063                                        Ok(Expression::Function(Box::new(Function::new(
21064                                            "dateTrunc".to_string(),
21065                                            vec![Expression::string(&unit_str), arg1],
21066                                        ))))
21067                                    }
21068                                    _ => {
21069                                        // Standard: DATE_TRUNC('UNIT', expr)
21070                                        let unit = Expression::string(&unit_str);
21071                                        Ok(Expression::Function(Box::new(Function::new(
21072                                            "DATE_TRUNC".to_string(),
21073                                            vec![unit, arg1],
21074                                        ))))
21075                                    }
21076                                }
21077                            }
21078                            // GETDATE() -> CURRENT_TIMESTAMP for non-TSQL targets
21079                            "GETDATE" if f.args.is_empty() => match target {
21080                                DialectType::TSQL => Ok(Expression::Function(f)),
21081                                DialectType::Redshift => Ok(Expression::Function(Box::new(
21082                                    Function::new("GETDATE".to_string(), vec![]),
21083                                ))),
21084                                _ => Ok(Expression::CurrentTimestamp(
21085                                    crate::expressions::CurrentTimestamp {
21086                                        precision: None,
21087                                        sysdate: false,
21088                                    },
21089                                )),
21090                            },
21091                            // TO_HEX(x) / HEX(x) -> target-specific hex function
21092                            "TO_HEX" | "HEX" if f.args.len() == 1 => {
21093                                let name = match target {
21094                                    DialectType::Presto | DialectType::Trino => "TO_HEX",
21095                                    DialectType::Spark
21096                                    | DialectType::Databricks
21097                                    | DialectType::Hive => "HEX",
21098                                    DialectType::DuckDB
21099                                    | DialectType::PostgreSQL
21100                                    | DialectType::Redshift => "TO_HEX",
21101                                    _ => &f.name,
21102                                };
21103                                Ok(Expression::Function(Box::new(Function::new(
21104                                    name.to_string(),
21105                                    f.args,
21106                                ))))
21107                            }
21108                            // FROM_HEX(x) / UNHEX(x) -> target-specific hex decode function
21109                            "FROM_HEX" | "UNHEX" if f.args.len() == 1 => {
21110                                match target {
21111                                    DialectType::BigQuery => {
21112                                        // BigQuery: UNHEX(x) -> FROM_HEX(x)
21113                                        // Special case: UNHEX(MD5(x)) -> FROM_HEX(TO_HEX(MD5(x)))
21114                                        // because BigQuery MD5 returns BYTES, not hex string
21115                                        let arg = &f.args[0];
21116                                        let wrapped_arg = match arg {
21117                                            Expression::Function(inner_f)
21118                                                if inner_f.name.eq_ignore_ascii_case("MD5")
21119                                                    || inner_f
21120                                                        .name
21121                                                        .eq_ignore_ascii_case("SHA1")
21122                                                    || inner_f
21123                                                        .name
21124                                                        .eq_ignore_ascii_case("SHA256")
21125                                                    || inner_f
21126                                                        .name
21127                                                        .eq_ignore_ascii_case("SHA512") =>
21128                                            {
21129                                                // Wrap hash function in TO_HEX for BigQuery
21130                                                Expression::Function(Box::new(Function::new(
21131                                                    "TO_HEX".to_string(),
21132                                                    vec![arg.clone()],
21133                                                )))
21134                                            }
21135                                            _ => f.args.into_iter().next().unwrap(),
21136                                        };
21137                                        Ok(Expression::Function(Box::new(Function::new(
21138                                            "FROM_HEX".to_string(),
21139                                            vec![wrapped_arg],
21140                                        ))))
21141                                    }
21142                                    _ => {
21143                                        let name = match target {
21144                                            DialectType::Presto | DialectType::Trino => "FROM_HEX",
21145                                            DialectType::Spark
21146                                            | DialectType::Databricks
21147                                            | DialectType::Hive => "UNHEX",
21148                                            _ => &f.name,
21149                                        };
21150                                        Ok(Expression::Function(Box::new(Function::new(
21151                                            name.to_string(),
21152                                            f.args,
21153                                        ))))
21154                                    }
21155                                }
21156                            }
21157                            // TO_UTF8(x) -> ENCODE(x, 'utf-8') for Spark
21158                            "TO_UTF8" if f.args.len() == 1 => match target {
21159                                DialectType::Spark | DialectType::Databricks => {
21160                                    let mut args = f.args;
21161                                    args.push(Expression::string("utf-8"));
21162                                    Ok(Expression::Function(Box::new(Function::new(
21163                                        "ENCODE".to_string(),
21164                                        args,
21165                                    ))))
21166                                }
21167                                _ => Ok(Expression::Function(f)),
21168                            },
21169                            // FROM_UTF8(x) -> DECODE(x, 'utf-8') for Spark
21170                            "FROM_UTF8" if f.args.len() == 1 => match target {
21171                                DialectType::Spark | DialectType::Databricks => {
21172                                    let mut args = f.args;
21173                                    args.push(Expression::string("utf-8"));
21174                                    Ok(Expression::Function(Box::new(Function::new(
21175                                        "DECODE".to_string(),
21176                                        args,
21177                                    ))))
21178                                }
21179                                _ => Ok(Expression::Function(f)),
21180                            },
21181                            // STARTS_WITH(x, y) / STARTSWITH(x, y) -> target-specific
21182                            "STARTS_WITH" | "STARTSWITH" if f.args.len() == 2 => {
21183                                let name = match target {
21184                                    DialectType::Spark | DialectType::Databricks => "STARTSWITH",
21185                                    DialectType::Presto | DialectType::Trino => "STARTS_WITH",
21186                                    DialectType::PostgreSQL | DialectType::Redshift => {
21187                                        "STARTS_WITH"
21188                                    }
21189                                    _ => &f.name,
21190                                };
21191                                Ok(Expression::Function(Box::new(Function::new(
21192                                    name.to_string(),
21193                                    f.args,
21194                                ))))
21195                            }
21196                            // APPROX_COUNT_DISTINCT(x) <-> APPROX_DISTINCT(x)
21197                            "APPROX_COUNT_DISTINCT" if f.args.len() >= 1 => {
21198                                let name = match target {
21199                                    DialectType::Presto
21200                                    | DialectType::Trino
21201                                    | DialectType::Athena => "APPROX_DISTINCT",
21202                                    _ => "APPROX_COUNT_DISTINCT",
21203                                };
21204                                Ok(Expression::Function(Box::new(Function::new(
21205                                    name.to_string(),
21206                                    f.args,
21207                                ))))
21208                            }
21209                            // JSON_EXTRACT -> GET_JSON_OBJECT for Spark/Hive
21210                            "JSON_EXTRACT"
21211                                if f.args.len() == 2
21212                                    && !matches!(source, DialectType::BigQuery)
21213                                    && matches!(
21214                                        target,
21215                                        DialectType::Spark
21216                                            | DialectType::Databricks
21217                                            | DialectType::Hive
21218                                    ) =>
21219                            {
21220                                Ok(Expression::Function(Box::new(Function::new(
21221                                    "GET_JSON_OBJECT".to_string(),
21222                                    f.args,
21223                                ))))
21224                            }
21225                            // JSON_EXTRACT(x, path) -> x -> path for SQLite (arrow syntax)
21226                            "JSON_EXTRACT"
21227                                if f.args.len() == 2 && matches!(target, DialectType::SQLite) =>
21228                            {
21229                                let mut args = f.args;
21230                                let path = args.remove(1);
21231                                let this = args.remove(0);
21232                                Ok(Expression::JsonExtract(Box::new(
21233                                    crate::expressions::JsonExtractFunc {
21234                                        this,
21235                                        path,
21236                                        returning: None,
21237                                        arrow_syntax: true,
21238                                        hash_arrow_syntax: false,
21239                                        wrapper_option: None,
21240                                        quotes_option: None,
21241                                        on_scalar_string: false,
21242                                        on_error: None,
21243                                    },
21244                                )))
21245                            }
21246                            // JSON_FORMAT(x) -> TO_JSON(x) for Spark, TO_JSON_STRING for BigQuery, CAST(TO_JSON(x) AS TEXT) for DuckDB
21247                            "JSON_FORMAT" if f.args.len() == 1 => {
21248                                match target {
21249                                    DialectType::Spark | DialectType::Databricks => {
21250                                        // Presto JSON_FORMAT(JSON '...') needs Spark's string-unquoting flow:
21251                                        // REGEXP_EXTRACT(TO_JSON(FROM_JSON('[...]', SCHEMA_OF_JSON('[...]'))), '^.(.*).$', 1)
21252                                        if matches!(
21253                                            source,
21254                                            DialectType::Presto
21255                                                | DialectType::Trino
21256                                                | DialectType::Athena
21257                                        ) {
21258                                            if let Some(Expression::ParseJson(pj)) = f.args.first()
21259                                            {
21260                                                if let Expression::Literal(lit) = &pj.this {
21261                                                    if let Literal::String(s) = lit.as_ref() {
21262                                                        let wrapped =
21263                                                            Expression::Literal(Box::new(
21264                                                                Literal::String(format!("[{}]", s)),
21265                                                            ));
21266                                                        let schema_of_json = Expression::Function(
21267                                                            Box::new(Function::new(
21268                                                                "SCHEMA_OF_JSON".to_string(),
21269                                                                vec![wrapped.clone()],
21270                                                            )),
21271                                                        );
21272                                                        let from_json = Expression::Function(
21273                                                            Box::new(Function::new(
21274                                                                "FROM_JSON".to_string(),
21275                                                                vec![wrapped, schema_of_json],
21276                                                            )),
21277                                                        );
21278                                                        let to_json = Expression::Function(
21279                                                            Box::new(Function::new(
21280                                                                "TO_JSON".to_string(),
21281                                                                vec![from_json],
21282                                                            )),
21283                                                        );
21284                                                        return Ok(Expression::Function(Box::new(
21285                                                            Function::new(
21286                                                                "REGEXP_EXTRACT".to_string(),
21287                                                                vec![
21288                                                                    to_json,
21289                                                                    Expression::Literal(Box::new(
21290                                                                        Literal::String(
21291                                                                            "^.(.*).$".to_string(),
21292                                                                        ),
21293                                                                    )),
21294                                                                    Expression::Literal(Box::new(
21295                                                                        Literal::Number(
21296                                                                            "1".to_string(),
21297                                                                        ),
21298                                                                    )),
21299                                                                ],
21300                                                            ),
21301                                                        )));
21302                                                    }
21303                                                }
21304                                            }
21305                                        }
21306
21307                                        // Strip inner CAST(... AS JSON) or TO_JSON() if present
21308                                        // The CastToJsonForSpark may have already converted CAST(x AS JSON) to TO_JSON(x)
21309                                        let mut args = f.args;
21310                                        if let Some(Expression::Cast(ref c)) = args.first() {
21311                                            if matches!(&c.to, DataType::Json | DataType::JsonB) {
21312                                                args = vec![c.this.clone()];
21313                                            }
21314                                        } else if let Some(Expression::Function(ref inner_f)) =
21315                                            args.first()
21316                                        {
21317                                            if inner_f.name.eq_ignore_ascii_case("TO_JSON")
21318                                                && inner_f.args.len() == 1
21319                                            {
21320                                                // Already TO_JSON(x) from CastToJsonForSpark, just use the inner arg
21321                                                args = inner_f.args.clone();
21322                                            }
21323                                        }
21324                                        Ok(Expression::Function(Box::new(Function::new(
21325                                            "TO_JSON".to_string(),
21326                                            args,
21327                                        ))))
21328                                    }
21329                                    DialectType::BigQuery => Ok(Expression::Function(Box::new(
21330                                        Function::new("TO_JSON_STRING".to_string(), f.args),
21331                                    ))),
21332                                    DialectType::DuckDB => {
21333                                        // CAST(TO_JSON(x) AS TEXT)
21334                                        let to_json = Expression::Function(Box::new(
21335                                            Function::new("TO_JSON".to_string(), f.args),
21336                                        ));
21337                                        Ok(Expression::Cast(Box::new(Cast {
21338                                            this: to_json,
21339                                            to: DataType::Text,
21340                                            trailing_comments: Vec::new(),
21341                                            double_colon_syntax: false,
21342                                            format: None,
21343                                            default: None,
21344                                            inferred_type: None,
21345                                        })))
21346                                    }
21347                                    _ => Ok(Expression::Function(f)),
21348                                }
21349                            }
21350                            // SYSDATE -> CURRENT_TIMESTAMP for non-Oracle/Redshift/Snowflake targets
21351                            "SYSDATE" if f.args.is_empty() => {
21352                                match target {
21353                                    DialectType::Oracle | DialectType::Redshift => {
21354                                        Ok(Expression::Function(f))
21355                                    }
21356                                    DialectType::Snowflake => {
21357                                        // Snowflake uses SYSDATE() with parens
21358                                        let mut f = *f;
21359                                        f.no_parens = false;
21360                                        Ok(Expression::Function(Box::new(f)))
21361                                    }
21362                                    DialectType::DuckDB => {
21363                                        // DuckDB: SYSDATE() -> CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
21364                                        Ok(Expression::AtTimeZone(Box::new(
21365                                            crate::expressions::AtTimeZone {
21366                                                this: Expression::CurrentTimestamp(
21367                                                    crate::expressions::CurrentTimestamp {
21368                                                        precision: None,
21369                                                        sysdate: false,
21370                                                    },
21371                                                ),
21372                                                zone: Expression::Literal(Box::new(
21373                                                    Literal::String("UTC".to_string()),
21374                                                )),
21375                                            },
21376                                        )))
21377                                    }
21378                                    _ => Ok(Expression::CurrentTimestamp(
21379                                        crate::expressions::CurrentTimestamp {
21380                                            precision: None,
21381                                            sysdate: true,
21382                                        },
21383                                    )),
21384                                }
21385                            }
21386                            // LOGICAL_OR(x) -> BOOL_OR(x)
21387                            "LOGICAL_OR" if f.args.len() == 1 => {
21388                                let name = match target {
21389                                    DialectType::Spark | DialectType::Databricks => "BOOL_OR",
21390                                    _ => &f.name,
21391                                };
21392                                Ok(Expression::Function(Box::new(Function::new(
21393                                    name.to_string(),
21394                                    f.args,
21395                                ))))
21396                            }
21397                            // LOGICAL_AND(x) -> BOOL_AND(x)
21398                            "LOGICAL_AND" if f.args.len() == 1 => {
21399                                let name = match target {
21400                                    DialectType::Spark | DialectType::Databricks => "BOOL_AND",
21401                                    _ => &f.name,
21402                                };
21403                                Ok(Expression::Function(Box::new(Function::new(
21404                                    name.to_string(),
21405                                    f.args,
21406                                ))))
21407                            }
21408                            // MONTHS_ADD(d, n) -> ADD_MONTHS(d, n) for Oracle
21409                            "MONTHS_ADD" if f.args.len() == 2 => match target {
21410                                DialectType::Oracle => Ok(Expression::Function(Box::new(
21411                                    Function::new("ADD_MONTHS".to_string(), f.args),
21412                                ))),
21413                                _ => Ok(Expression::Function(f)),
21414                            },
21415                            // ARRAY_JOIN(arr, sep[, null_replacement]) -> target-specific
21416                            "ARRAY_JOIN" if f.args.len() >= 2 => {
21417                                match target {
21418                                    DialectType::Spark | DialectType::Databricks => {
21419                                        // Keep as ARRAY_JOIN for Spark (it supports null_replacement)
21420                                        Ok(Expression::Function(f))
21421                                    }
21422                                    DialectType::Hive => {
21423                                        // ARRAY_JOIN(arr, sep[, null_rep]) -> CONCAT_WS(sep, arr) (drop null_replacement)
21424                                        let mut args = f.args;
21425                                        let arr = args.remove(0);
21426                                        let sep = args.remove(0);
21427                                        // Drop any remaining args (null_replacement)
21428                                        Ok(Expression::Function(Box::new(Function::new(
21429                                            "CONCAT_WS".to_string(),
21430                                            vec![sep, arr],
21431                                        ))))
21432                                    }
21433                                    DialectType::Presto | DialectType::Trino => {
21434                                        Ok(Expression::Function(f))
21435                                    }
21436                                    _ => Ok(Expression::Function(f)),
21437                                }
21438                            }
21439                            // LOCATE(substr, str, pos) 3-arg -> target-specific
21440                            // For Presto/DuckDB: STRPOS doesn't support 3-arg, need complex expansion
21441                            "LOCATE"
21442                                if f.args.len() == 3
21443                                    && matches!(
21444                                        target,
21445                                        DialectType::Presto
21446                                            | DialectType::Trino
21447                                            | DialectType::Athena
21448                                            | DialectType::DuckDB
21449                                    ) =>
21450                            {
21451                                let mut args = f.args;
21452                                let substr = args.remove(0);
21453                                let string = args.remove(0);
21454                                let pos = args.remove(0);
21455                                // STRPOS(SUBSTRING(string, pos), substr)
21456                                let substring_call = Expression::Function(Box::new(Function::new(
21457                                    "SUBSTRING".to_string(),
21458                                    vec![string.clone(), pos.clone()],
21459                                )));
21460                                let strpos_call = Expression::Function(Box::new(Function::new(
21461                                    "STRPOS".to_string(),
21462                                    vec![substring_call, substr.clone()],
21463                                )));
21464                                // STRPOS(...) + pos - 1
21465                                let pos_adjusted =
21466                                    Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
21467                                        Expression::Add(Box::new(
21468                                            crate::expressions::BinaryOp::new(
21469                                                strpos_call.clone(),
21470                                                pos.clone(),
21471                                            ),
21472                                        )),
21473                                        Expression::number(1),
21474                                    )));
21475                                // STRPOS(...) = 0
21476                                let is_zero =
21477                                    Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
21478                                        strpos_call.clone(),
21479                                        Expression::number(0),
21480                                    )));
21481
21482                                match target {
21483                                    DialectType::Presto
21484                                    | DialectType::Trino
21485                                    | DialectType::Athena => {
21486                                        // IF(STRPOS(...) = 0, 0, STRPOS(...) + pos - 1)
21487                                        Ok(Expression::Function(Box::new(Function::new(
21488                                            "IF".to_string(),
21489                                            vec![is_zero, Expression::number(0), pos_adjusted],
21490                                        ))))
21491                                    }
21492                                    DialectType::DuckDB => {
21493                                        // CASE WHEN STRPOS(...) = 0 THEN 0 ELSE STRPOS(...) + pos - 1 END
21494                                        Ok(Expression::Case(Box::new(crate::expressions::Case {
21495                                            operand: None,
21496                                            whens: vec![(is_zero, Expression::number(0))],
21497                                            else_: Some(pos_adjusted),
21498                                            comments: Vec::new(),
21499                                            inferred_type: None,
21500                                        })))
21501                                    }
21502                                    _ => Ok(Expression::Function(Box::new(Function::new(
21503                                        "LOCATE".to_string(),
21504                                        vec![substr, string, pos],
21505                                    )))),
21506                                }
21507                            }
21508                            // STRPOS(haystack, needle, occurrence) 3-arg -> INSTR(haystack, needle, 1, occurrence)
21509                            "STRPOS"
21510                                if f.args.len() == 3
21511                                    && matches!(
21512                                        target,
21513                                        DialectType::BigQuery
21514                                            | DialectType::Oracle
21515                                            | DialectType::Teradata
21516                                    ) =>
21517                            {
21518                                let mut args = f.args;
21519                                let haystack = args.remove(0);
21520                                let needle = args.remove(0);
21521                                let occurrence = args.remove(0);
21522                                Ok(Expression::Function(Box::new(Function::new(
21523                                    "INSTR".to_string(),
21524                                    vec![haystack, needle, Expression::number(1), occurrence],
21525                                ))))
21526                            }
21527                            // SCHEMA_NAME(id) -> target-specific
21528                            "SCHEMA_NAME" if f.args.len() <= 1 => match target {
21529                                DialectType::MySQL | DialectType::SingleStore => {
21530                                    Ok(Expression::Function(Box::new(Function::new(
21531                                        "SCHEMA".to_string(),
21532                                        vec![],
21533                                    ))))
21534                                }
21535                                DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
21536                                    crate::expressions::CurrentSchema { this: None },
21537                                ))),
21538                                DialectType::SQLite => Ok(Expression::string("main")),
21539                                _ => Ok(Expression::Function(f)),
21540                            },
21541                            // STRTOL(str, base) -> FROM_BASE(str, base) for Trino/Presto
21542                            "STRTOL" if f.args.len() == 2 => match target {
21543                                DialectType::Presto | DialectType::Trino => {
21544                                    Ok(Expression::Function(Box::new(Function::new(
21545                                        "FROM_BASE".to_string(),
21546                                        f.args,
21547                                    ))))
21548                                }
21549                                _ => Ok(Expression::Function(f)),
21550                            },
21551                            // EDITDIST3(a, b) -> LEVENSHTEIN(a, b) for Spark
21552                            "EDITDIST3" if f.args.len() == 2 => match target {
21553                                DialectType::Spark | DialectType::Databricks => {
21554                                    Ok(Expression::Function(Box::new(Function::new(
21555                                        "LEVENSHTEIN".to_string(),
21556                                        f.args,
21557                                    ))))
21558                                }
21559                                _ => Ok(Expression::Function(f)),
21560                            },
21561                            // FORMAT(num, decimals) from MySQL -> DuckDB FORMAT('{:,.Xf}', num)
21562                            "FORMAT"
21563                                if f.args.len() == 2
21564                                    && matches!(
21565                                        source,
21566                                        DialectType::MySQL | DialectType::SingleStore
21567                                    )
21568                                    && matches!(target, DialectType::DuckDB) =>
21569                            {
21570                                let mut args = f.args;
21571                                let num_expr = args.remove(0);
21572                                let decimals_expr = args.remove(0);
21573                                // Extract decimal count
21574                                let dec_count = match &decimals_expr {
21575                                    Expression::Literal(lit)
21576                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
21577                                    {
21578                                        let Literal::Number(n) = lit.as_ref() else {
21579                                            unreachable!()
21580                                        };
21581                                        n.clone()
21582                                    }
21583                                    _ => "0".to_string(),
21584                                };
21585                                let fmt_str = format!("{{:,.{}f}}", dec_count);
21586                                Ok(Expression::Function(Box::new(Function::new(
21587                                    "FORMAT".to_string(),
21588                                    vec![Expression::string(&fmt_str), num_expr],
21589                                ))))
21590                            }
21591                            // FORMAT(x, fmt) from TSQL -> DATE_FORMAT for Spark, or expand short codes
21592                            "FORMAT"
21593                                if f.args.len() == 2
21594                                    && matches!(
21595                                        source,
21596                                        DialectType::TSQL | DialectType::Fabric
21597                                    ) =>
21598                            {
21599                                let val_expr = f.args[0].clone();
21600                                let fmt_expr = f.args[1].clone();
21601                                // Expand unambiguous .NET single-char date format shortcodes to full patterns.
21602                                // Only expand shortcodes that are NOT also valid numeric format specifiers.
21603                                // Ambiguous: d, D, f, F, g, G (used for both dates and numbers)
21604                                // Unambiguous date: m/M (Month day), t/T (Time), y/Y (Year month)
21605                                let (expanded_fmt, is_shortcode) = match &fmt_expr {
21606                                    Expression::Literal(lit)
21607                                        if matches!(
21608                                            lit.as_ref(),
21609                                            crate::expressions::Literal::String(_)
21610                                        ) =>
21611                                    {
21612                                        let crate::expressions::Literal::String(s) = lit.as_ref()
21613                                        else {
21614                                            unreachable!()
21615                                        };
21616                                        match s.as_str() {
21617                                            "m" | "M" => (Expression::string("MMMM d"), true),
21618                                            "t" => (Expression::string("h:mm tt"), true),
21619                                            "T" => (Expression::string("h:mm:ss tt"), true),
21620                                            "y" | "Y" => (Expression::string("MMMM yyyy"), true),
21621                                            _ => (fmt_expr.clone(), false),
21622                                        }
21623                                    }
21624                                    _ => (fmt_expr.clone(), false),
21625                                };
21626                                // Check if the format looks like a date format
21627                                let is_date_format = is_shortcode
21628                                    || match &expanded_fmt {
21629                                        Expression::Literal(lit)
21630                                            if matches!(
21631                                                lit.as_ref(),
21632                                                crate::expressions::Literal::String(_)
21633                                            ) =>
21634                                        {
21635                                            let crate::expressions::Literal::String(s) =
21636                                                lit.as_ref()
21637                                            else {
21638                                                unreachable!()
21639                                            };
21640                                            // Date formats typically contain yyyy, MM, dd, MMMM, HH, etc.
21641                                            s.contains("yyyy")
21642                                                || s.contains("YYYY")
21643                                                || s.contains("MM")
21644                                                || s.contains("dd")
21645                                                || s.contains("MMMM")
21646                                                || s.contains("HH")
21647                                                || s.contains("hh")
21648                                                || s.contains("ss")
21649                                        }
21650                                        _ => false,
21651                                    };
21652                                match target {
21653                                    DialectType::Spark | DialectType::Databricks => {
21654                                        let func_name = if is_date_format {
21655                                            "DATE_FORMAT"
21656                                        } else {
21657                                            "FORMAT_NUMBER"
21658                                        };
21659                                        Ok(Expression::Function(Box::new(Function::new(
21660                                            func_name.to_string(),
21661                                            vec![val_expr, expanded_fmt],
21662                                        ))))
21663                                    }
21664                                    _ => {
21665                                        // For TSQL and other targets, expand shortcodes but keep FORMAT
21666                                        if is_shortcode {
21667                                            Ok(Expression::Function(Box::new(Function::new(
21668                                                "FORMAT".to_string(),
21669                                                vec![val_expr, expanded_fmt],
21670                                            ))))
21671                                        } else {
21672                                            Ok(Expression::Function(f))
21673                                        }
21674                                    }
21675                                }
21676                            }
21677                            // FORMAT('%s', x) from Trino/Presto -> target-specific
21678                            "FORMAT"
21679                                if f.args.len() >= 2
21680                                    && matches!(
21681                                        source,
21682                                        DialectType::Trino
21683                                            | DialectType::Presto
21684                                            | DialectType::Athena
21685                                    ) =>
21686                            {
21687                                let fmt_expr = f.args[0].clone();
21688                                let value_args: Vec<Expression> = f.args[1..].to_vec();
21689                                match target {
21690                                    // DuckDB: replace %s with {} in format string
21691                                    DialectType::DuckDB => {
21692                                        let new_fmt = match &fmt_expr {
21693                                            Expression::Literal(lit)
21694                                                if matches!(lit.as_ref(), Literal::String(_)) =>
21695                                            {
21696                                                let Literal::String(s) = lit.as_ref() else {
21697                                                    unreachable!()
21698                                                };
21699                                                Expression::Literal(Box::new(Literal::String(
21700                                                    s.replace("%s", "{}"),
21701                                                )))
21702                                            }
21703                                            _ => fmt_expr,
21704                                        };
21705                                        let mut args = vec![new_fmt];
21706                                        args.extend(value_args);
21707                                        Ok(Expression::Function(Box::new(Function::new(
21708                                            "FORMAT".to_string(),
21709                                            args,
21710                                        ))))
21711                                    }
21712                                    // Snowflake: FORMAT('%s', x) -> TO_CHAR(x) when just %s
21713                                    DialectType::Snowflake => match &fmt_expr {
21714                                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "%s" && value_args.len() == 1) =>
21715                                        {
21716                                            let Literal::String(_) = lit.as_ref() else {
21717                                                unreachable!()
21718                                            };
21719                                            Ok(Expression::Function(Box::new(Function::new(
21720                                                "TO_CHAR".to_string(),
21721                                                value_args,
21722                                            ))))
21723                                        }
21724                                        _ => Ok(Expression::Function(f)),
21725                                    },
21726                                    // Default: keep FORMAT as-is
21727                                    _ => Ok(Expression::Function(f)),
21728                                }
21729                            }
21730                            // LIST_CONTAINS / LIST_HAS / ARRAY_CONTAINS -> target-specific
21731                            "LIST_CONTAINS" | "LIST_HAS" | "ARRAY_CONTAINS"
21732                                if f.args.len() == 2 =>
21733                            {
21734                                // When coming from Snowflake source: ARRAY_CONTAINS(value, array)
21735                                // args[0]=value, args[1]=array. For DuckDB target, swap and add NULL-aware CASE.
21736                                if matches!(target, DialectType::DuckDB)
21737                                    && matches!(source, DialectType::Snowflake)
21738                                    && f.name.eq_ignore_ascii_case("ARRAY_CONTAINS")
21739                                {
21740                                    let value = f.args[0].clone();
21741                                    let array = f.args[1].clone();
21742
21743                                    // value IS NULL
21744                                    let value_is_null =
21745                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
21746                                            this: value.clone(),
21747                                            not: false,
21748                                            postfix_form: false,
21749                                        }));
21750
21751                                    // ARRAY_LENGTH(array)
21752                                    let array_length =
21753                                        Expression::Function(Box::new(Function::new(
21754                                            "ARRAY_LENGTH".to_string(),
21755                                            vec![array.clone()],
21756                                        )));
21757                                    // LIST_COUNT(array)
21758                                    let list_count = Expression::Function(Box::new(Function::new(
21759                                        "LIST_COUNT".to_string(),
21760                                        vec![array.clone()],
21761                                    )));
21762                                    // ARRAY_LENGTH(array) <> LIST_COUNT(array)
21763                                    let neq =
21764                                        Expression::Neq(Box::new(crate::expressions::BinaryOp {
21765                                            left: array_length,
21766                                            right: list_count,
21767                                            left_comments: vec![],
21768                                            operator_comments: vec![],
21769                                            trailing_comments: vec![],
21770                                            inferred_type: None,
21771                                        }));
21772                                    // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
21773                                    let nullif =
21774                                        Expression::Nullif(Box::new(crate::expressions::Nullif {
21775                                            this: Box::new(neq),
21776                                            expression: Box::new(Expression::Boolean(
21777                                                crate::expressions::BooleanLiteral { value: false },
21778                                            )),
21779                                        }));
21780
21781                                    // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
21782                                    let array_contains =
21783                                        Expression::Function(Box::new(Function::new(
21784                                            "ARRAY_CONTAINS".to_string(),
21785                                            vec![array, value],
21786                                        )));
21787
21788                                    // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
21789                                    return Ok(Expression::Case(Box::new(Case {
21790                                        operand: None,
21791                                        whens: vec![(value_is_null, nullif)],
21792                                        else_: Some(array_contains),
21793                                        comments: Vec::new(),
21794                                        inferred_type: None,
21795                                    })));
21796                                }
21797                                match target {
21798                                    DialectType::PostgreSQL | DialectType::Redshift => {
21799                                        // CASE WHEN needle IS NULL THEN NULL ELSE COALESCE(needle = ANY(arr), FALSE) END
21800                                        let arr = f.args[0].clone();
21801                                        let needle = f.args[1].clone();
21802                                        // Convert [] to ARRAY[] for PostgreSQL
21803                                        let pg_arr = match arr {
21804                                            Expression::Array(a) => Expression::ArrayFunc(
21805                                                Box::new(crate::expressions::ArrayConstructor {
21806                                                    expressions: a.expressions,
21807                                                    bracket_notation: false,
21808                                                    use_list_keyword: false,
21809                                                }),
21810                                            ),
21811                                            _ => arr,
21812                                        };
21813                                        // needle = ANY(arr) using the Any quantified expression
21814                                        let any_expr = Expression::Any(Box::new(
21815                                            crate::expressions::QuantifiedExpr {
21816                                                this: needle.clone(),
21817                                                subquery: pg_arr,
21818                                                op: Some(crate::expressions::QuantifiedOp::Eq),
21819                                            },
21820                                        ));
21821                                        let coalesce = Expression::Coalesce(Box::new(
21822                                            crate::expressions::VarArgFunc {
21823                                                expressions: vec![
21824                                                    any_expr,
21825                                                    Expression::Boolean(
21826                                                        crate::expressions::BooleanLiteral {
21827                                                            value: false,
21828                                                        },
21829                                                    ),
21830                                                ],
21831                                                original_name: None,
21832                                                inferred_type: None,
21833                                            },
21834                                        ));
21835                                        let is_null_check = Expression::IsNull(Box::new(
21836                                            crate::expressions::IsNull {
21837                                                this: needle,
21838                                                not: false,
21839                                                postfix_form: false,
21840                                            },
21841                                        ));
21842                                        Ok(Expression::Case(Box::new(Case {
21843                                            operand: None,
21844                                            whens: vec![(
21845                                                is_null_check,
21846                                                Expression::Null(crate::expressions::Null),
21847                                            )],
21848                                            else_: Some(coalesce),
21849                                            comments: Vec::new(),
21850                                            inferred_type: None,
21851                                        })))
21852                                    }
21853                                    _ => Ok(Expression::Function(Box::new(Function::new(
21854                                        "ARRAY_CONTAINS".to_string(),
21855                                        f.args,
21856                                    )))),
21857                                }
21858                            }
21859                            // LIST_HAS_ANY / ARRAY_HAS_ANY -> target-specific overlap operator
21860                            "LIST_HAS_ANY" | "ARRAY_HAS_ANY" if f.args.len() == 2 => {
21861                                match target {
21862                                    DialectType::PostgreSQL | DialectType::Redshift => {
21863                                        // arr1 && arr2 with ARRAY[] syntax
21864                                        let mut args = f.args;
21865                                        let arr1 = args.remove(0);
21866                                        let arr2 = args.remove(0);
21867                                        let pg_arr1 = match arr1 {
21868                                            Expression::Array(a) => Expression::ArrayFunc(
21869                                                Box::new(crate::expressions::ArrayConstructor {
21870                                                    expressions: a.expressions,
21871                                                    bracket_notation: false,
21872                                                    use_list_keyword: false,
21873                                                }),
21874                                            ),
21875                                            _ => arr1,
21876                                        };
21877                                        let pg_arr2 = match arr2 {
21878                                            Expression::Array(a) => Expression::ArrayFunc(
21879                                                Box::new(crate::expressions::ArrayConstructor {
21880                                                    expressions: a.expressions,
21881                                                    bracket_notation: false,
21882                                                    use_list_keyword: false,
21883                                                }),
21884                                            ),
21885                                            _ => arr2,
21886                                        };
21887                                        Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
21888                                            pg_arr1, pg_arr2,
21889                                        ))))
21890                                    }
21891                                    DialectType::DuckDB => {
21892                                        // DuckDB: arr1 && arr2 (native support)
21893                                        let mut args = f.args;
21894                                        let arr1 = args.remove(0);
21895                                        let arr2 = args.remove(0);
21896                                        Ok(Expression::ArrayOverlaps(Box::new(BinaryOp::new(
21897                                            arr1, arr2,
21898                                        ))))
21899                                    }
21900                                    _ => Ok(Expression::Function(Box::new(Function::new(
21901                                        "LIST_HAS_ANY".to_string(),
21902                                        f.args,
21903                                    )))),
21904                                }
21905                            }
21906                            // APPROX_QUANTILE(x, q) -> target-specific
21907                            "APPROX_QUANTILE" if f.args.len() == 2 => match target {
21908                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
21909                                    Function::new("APPROX_PERCENTILE".to_string(), f.args),
21910                                ))),
21911                                DialectType::DuckDB => Ok(Expression::Function(f)),
21912                                _ => Ok(Expression::Function(f)),
21913                            },
21914                            // MAKE_DATE(y, m, d) -> DATE(y, m, d) for BigQuery
21915                            "MAKE_DATE" if f.args.len() == 3 => match target {
21916                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
21917                                    Function::new("DATE".to_string(), f.args),
21918                                ))),
21919                                _ => Ok(Expression::Function(f)),
21920                            },
21921                            // RANGE(start, end[, step]) -> target-specific
21922                            "RANGE"
21923                                if f.args.len() >= 2 && !matches!(target, DialectType::DuckDB) =>
21924                            {
21925                                let start = f.args[0].clone();
21926                                let end = f.args[1].clone();
21927                                let step = f.args.get(2).cloned();
21928                                match target {
21929                                    // Snowflake ARRAY_GENERATE_RANGE uses exclusive end (same as DuckDB RANGE),
21930                                    // so just rename without adjusting the end argument.
21931                                    DialectType::Snowflake => {
21932                                        let mut args = vec![start, end];
21933                                        if let Some(s) = step {
21934                                            args.push(s);
21935                                        }
21936                                        Ok(Expression::Function(Box::new(Function::new(
21937                                            "ARRAY_GENERATE_RANGE".to_string(),
21938                                            args,
21939                                        ))))
21940                                    }
21941                                    DialectType::Spark | DialectType::Databricks => {
21942                                        // RANGE(start, end) -> SEQUENCE(start, end-1)
21943                                        // RANGE(start, end, step) -> SEQUENCE(start, end-step, step) when step constant
21944                                        // RANGE(start, start) -> ARRAY() (empty)
21945                                        // RANGE(start, end, 0) -> ARRAY() (empty)
21946                                        // When end is variable: IF((end - 1) <= start, ARRAY(), SEQUENCE(start, (end - 1)))
21947
21948                                        // Check for constant args
21949                                        fn extract_i64(e: &Expression) -> Option<i64> {
21950                                            match e {
21951                                                Expression::Literal(lit)
21952                                                    if matches!(
21953                                                        lit.as_ref(),
21954                                                        Literal::Number(_)
21955                                                    ) =>
21956                                                {
21957                                                    let Literal::Number(n) = lit.as_ref() else {
21958                                                        unreachable!()
21959                                                    };
21960                                                    n.parse::<i64>().ok()
21961                                                }
21962                                                Expression::Neg(u) => {
21963                                                    if let Expression::Literal(lit) = &u.this {
21964                                                        if let Literal::Number(n) = lit.as_ref() {
21965                                                            n.parse::<i64>().ok().map(|v| -v)
21966                                                        } else {
21967                                                            None
21968                                                        }
21969                                                    } else {
21970                                                        None
21971                                                    }
21972                                                }
21973                                                _ => None,
21974                                            }
21975                                        }
21976                                        let start_val = extract_i64(&start);
21977                                        let end_val = extract_i64(&end);
21978                                        let step_val = step.as_ref().and_then(|s| extract_i64(s));
21979
21980                                        // Check for RANGE(x, x) or RANGE(x, y, 0) -> empty array
21981                                        if step_val == Some(0) {
21982                                            return Ok(Expression::Function(Box::new(
21983                                                Function::new("ARRAY".to_string(), vec![]),
21984                                            )));
21985                                        }
21986                                        if let (Some(s), Some(e_val)) = (start_val, end_val) {
21987                                            if s == e_val {
21988                                                return Ok(Expression::Function(Box::new(
21989                                                    Function::new("ARRAY".to_string(), vec![]),
21990                                                )));
21991                                            }
21992                                        }
21993
21994                                        if let (Some(_s_val), Some(e_val)) = (start_val, end_val) {
21995                                            // All constants - compute new end = end - step (if step provided) or end - 1
21996                                            match step_val {
21997                                                Some(st) if st < 0 => {
21998                                                    // Negative step: SEQUENCE(start, end - step, step)
21999                                                    let new_end = e_val - st; // end - step (= end + |step|)
22000                                                    let mut args =
22001                                                        vec![start, Expression::number(new_end)];
22002                                                    if let Some(s) = step {
22003                                                        args.push(s);
22004                                                    }
22005                                                    Ok(Expression::Function(Box::new(
22006                                                        Function::new("SEQUENCE".to_string(), args),
22007                                                    )))
22008                                                }
22009                                                Some(st) => {
22010                                                    let new_end = e_val - st;
22011                                                    let mut args =
22012                                                        vec![start, Expression::number(new_end)];
22013                                                    if let Some(s) = step {
22014                                                        args.push(s);
22015                                                    }
22016                                                    Ok(Expression::Function(Box::new(
22017                                                        Function::new("SEQUENCE".to_string(), args),
22018                                                    )))
22019                                                }
22020                                                None => {
22021                                                    // No step: SEQUENCE(start, end - 1)
22022                                                    let new_end = e_val - 1;
22023                                                    Ok(Expression::Function(Box::new(
22024                                                        Function::new(
22025                                                            "SEQUENCE".to_string(),
22026                                                            vec![
22027                                                                start,
22028                                                                Expression::number(new_end),
22029                                                            ],
22030                                                        ),
22031                                                    )))
22032                                                }
22033                                            }
22034                                        } else {
22035                                            // Variable end: IF((end - 1) < start, ARRAY(), SEQUENCE(start, (end - 1)))
22036                                            let end_m1 = Expression::Sub(Box::new(BinaryOp::new(
22037                                                end.clone(),
22038                                                Expression::number(1),
22039                                            )));
22040                                            let cond = Expression::Lt(Box::new(BinaryOp::new(
22041                                                Expression::Paren(Box::new(Paren {
22042                                                    this: end_m1.clone(),
22043                                                    trailing_comments: Vec::new(),
22044                                                })),
22045                                                start.clone(),
22046                                            )));
22047                                            let empty = Expression::Function(Box::new(
22048                                                Function::new("ARRAY".to_string(), vec![]),
22049                                            ));
22050                                            let mut seq_args = vec![
22051                                                start,
22052                                                Expression::Paren(Box::new(Paren {
22053                                                    this: end_m1,
22054                                                    trailing_comments: Vec::new(),
22055                                                })),
22056                                            ];
22057                                            if let Some(s) = step {
22058                                                seq_args.push(s);
22059                                            }
22060                                            let seq = Expression::Function(Box::new(
22061                                                Function::new("SEQUENCE".to_string(), seq_args),
22062                                            ));
22063                                            Ok(Expression::IfFunc(Box::new(
22064                                                crate::expressions::IfFunc {
22065                                                    condition: cond,
22066                                                    true_value: empty,
22067                                                    false_value: Some(seq),
22068                                                    original_name: None,
22069                                                    inferred_type: None,
22070                                                },
22071                                            )))
22072                                        }
22073                                    }
22074                                    DialectType::SQLite => {
22075                                        // RANGE(start, end) -> GENERATE_SERIES(start, end)
22076                                        // The subquery wrapping is handled at the Alias level
22077                                        let mut args = vec![start, end];
22078                                        if let Some(s) = step {
22079                                            args.push(s);
22080                                        }
22081                                        Ok(Expression::Function(Box::new(Function::new(
22082                                            "GENERATE_SERIES".to_string(),
22083                                            args,
22084                                        ))))
22085                                    }
22086                                    _ => Ok(Expression::Function(f)),
22087                                }
22088                            }
22089                            // ARRAY_REVERSE_SORT -> target-specific
22090                            // (handled above as well, but also need DuckDB self-normalization)
22091                            // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
22092                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => match target {
22093                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
22094                                    Function::new("OBJECT_CONSTRUCT".to_string(), f.args),
22095                                ))),
22096                                DialectType::Spark | DialectType::Databricks => {
22097                                    Ok(Expression::Function(Box::new(Function::new(
22098                                        "MAP_FROM_ARRAYS".to_string(),
22099                                        f.args,
22100                                    ))))
22101                                }
22102                                _ => Ok(Expression::Function(Box::new(Function::new(
22103                                    "MAP".to_string(),
22104                                    f.args,
22105                                )))),
22106                            },
22107                            // VARIANCE(x) -> varSamp(x) for ClickHouse
22108                            "VARIANCE" if f.args.len() == 1 => match target {
22109                                DialectType::ClickHouse => Ok(Expression::Function(Box::new(
22110                                    Function::new("varSamp".to_string(), f.args),
22111                                ))),
22112                                _ => Ok(Expression::Function(f)),
22113                            },
22114                            // STDDEV(x) -> stddevSamp(x) for ClickHouse
22115                            "STDDEV" if f.args.len() == 1 => match target {
22116                                DialectType::ClickHouse => Ok(Expression::Function(Box::new(
22117                                    Function::new("stddevSamp".to_string(), f.args),
22118                                ))),
22119                                _ => Ok(Expression::Function(f)),
22120                            },
22121                            // ISINF(x) -> IS_INF(x) for BigQuery
22122                            "ISINF" if f.args.len() == 1 => match target {
22123                                DialectType::BigQuery => Ok(Expression::Function(Box::new(
22124                                    Function::new("IS_INF".to_string(), f.args),
22125                                ))),
22126                                _ => Ok(Expression::Function(f)),
22127                            },
22128                            // CONTAINS(arr, x) -> ARRAY_CONTAINS(arr, x) for Spark/Hive
22129                            "CONTAINS" if f.args.len() == 2 => match target {
22130                                DialectType::Spark
22131                                | DialectType::Databricks
22132                                | DialectType::Hive => Ok(Expression::Function(Box::new(
22133                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
22134                                ))),
22135                                _ => Ok(Expression::Function(f)),
22136                            },
22137                            // ARRAY_CONTAINS(arr, x) -> CONTAINS(arr, x) for Presto
22138                            "ARRAY_CONTAINS" if f.args.len() == 2 => match target {
22139                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
22140                                    Ok(Expression::Function(Box::new(Function::new(
22141                                        "CONTAINS".to_string(),
22142                                        f.args,
22143                                    ))))
22144                                }
22145                                DialectType::DuckDB => Ok(Expression::Function(Box::new(
22146                                    Function::new("ARRAY_CONTAINS".to_string(), f.args),
22147                                ))),
22148                                _ => Ok(Expression::Function(f)),
22149                            },
22150                            // TO_UNIXTIME(x) -> UNIX_TIMESTAMP(x) for Hive/Spark
22151                            "TO_UNIXTIME" if f.args.len() == 1 => match target {
22152                                DialectType::Hive
22153                                | DialectType::Spark
22154                                | DialectType::Databricks => Ok(Expression::Function(Box::new(
22155                                    Function::new("UNIX_TIMESTAMP".to_string(), f.args),
22156                                ))),
22157                                _ => Ok(Expression::Function(f)),
22158                            },
22159                            // FROM_UNIXTIME(x) -> target-specific
22160                            "FROM_UNIXTIME" if f.args.len() == 1 => {
22161                                match target {
22162                                    DialectType::Hive
22163                                    | DialectType::Spark
22164                                    | DialectType::Databricks
22165                                    | DialectType::Presto
22166                                    | DialectType::Trino => Ok(Expression::Function(f)),
22167                                    DialectType::DuckDB => {
22168                                        // DuckDB: TO_TIMESTAMP(x)
22169                                        let arg = f.args.into_iter().next().unwrap();
22170                                        Ok(Expression::Function(Box::new(Function::new(
22171                                            "TO_TIMESTAMP".to_string(),
22172                                            vec![arg],
22173                                        ))))
22174                                    }
22175                                    DialectType::PostgreSQL => {
22176                                        // PG: TO_TIMESTAMP(col)
22177                                        let arg = f.args.into_iter().next().unwrap();
22178                                        Ok(Expression::Function(Box::new(Function::new(
22179                                            "TO_TIMESTAMP".to_string(),
22180                                            vec![arg],
22181                                        ))))
22182                                    }
22183                                    DialectType::Redshift => {
22184                                        // Redshift: (TIMESTAMP 'epoch' + col * INTERVAL '1 SECOND')
22185                                        let arg = f.args.into_iter().next().unwrap();
22186                                        let epoch_ts = Expression::Literal(Box::new(
22187                                            Literal::Timestamp("epoch".to_string()),
22188                                        ));
22189                                        let interval = Expression::Interval(Box::new(
22190                                            crate::expressions::Interval {
22191                                                this: Some(Expression::string("1 SECOND")),
22192                                                unit: None,
22193                                            },
22194                                        ));
22195                                        let mul =
22196                                            Expression::Mul(Box::new(BinaryOp::new(arg, interval)));
22197                                        let add =
22198                                            Expression::Add(Box::new(BinaryOp::new(epoch_ts, mul)));
22199                                        Ok(Expression::Paren(Box::new(crate::expressions::Paren {
22200                                            this: add,
22201                                            trailing_comments: Vec::new(),
22202                                        })))
22203                                    }
22204                                    _ => Ok(Expression::Function(f)),
22205                                }
22206                            }
22207                            // FROM_UNIXTIME(x, fmt) with 2 args from Hive/Spark -> target-specific
22208                            "FROM_UNIXTIME"
22209                                if f.args.len() == 2
22210                                    && matches!(
22211                                        source,
22212                                        DialectType::Hive
22213                                            | DialectType::Spark
22214                                            | DialectType::Databricks
22215                                    ) =>
22216                            {
22217                                let mut args = f.args;
22218                                let unix_ts = args.remove(0);
22219                                let fmt_expr = args.remove(0);
22220                                match target {
22221                                    DialectType::DuckDB => {
22222                                        // DuckDB: STRFTIME(TO_TIMESTAMP(x), c_fmt)
22223                                        let to_ts = Expression::Function(Box::new(Function::new(
22224                                            "TO_TIMESTAMP".to_string(),
22225                                            vec![unix_ts],
22226                                        )));
22227                                        if let Expression::Literal(lit) = &fmt_expr {
22228                                            if let crate::expressions::Literal::String(s) =
22229                                                lit.as_ref()
22230                                            {
22231                                                let c_fmt = Self::hive_format_to_c_format(s);
22232                                                Ok(Expression::Function(Box::new(Function::new(
22233                                                    "STRFTIME".to_string(),
22234                                                    vec![to_ts, Expression::string(&c_fmt)],
22235                                                ))))
22236                                            } else {
22237                                                Ok(Expression::Function(Box::new(Function::new(
22238                                                    "STRFTIME".to_string(),
22239                                                    vec![to_ts, fmt_expr],
22240                                                ))))
22241                                            }
22242                                        } else {
22243                                            Ok(Expression::Function(Box::new(Function::new(
22244                                                "STRFTIME".to_string(),
22245                                                vec![to_ts, fmt_expr],
22246                                            ))))
22247                                        }
22248                                    }
22249                                    DialectType::Presto
22250                                    | DialectType::Trino
22251                                    | DialectType::Athena => {
22252                                        // Presto: DATE_FORMAT(FROM_UNIXTIME(x), presto_fmt)
22253                                        let from_unix =
22254                                            Expression::Function(Box::new(Function::new(
22255                                                "FROM_UNIXTIME".to_string(),
22256                                                vec![unix_ts],
22257                                            )));
22258                                        if let Expression::Literal(lit) = &fmt_expr {
22259                                            if let crate::expressions::Literal::String(s) =
22260                                                lit.as_ref()
22261                                            {
22262                                                let p_fmt = Self::hive_format_to_presto_format(s);
22263                                                Ok(Expression::Function(Box::new(Function::new(
22264                                                    "DATE_FORMAT".to_string(),
22265                                                    vec![from_unix, Expression::string(&p_fmt)],
22266                                                ))))
22267                                            } else {
22268                                                Ok(Expression::Function(Box::new(Function::new(
22269                                                    "DATE_FORMAT".to_string(),
22270                                                    vec![from_unix, fmt_expr],
22271                                                ))))
22272                                            }
22273                                        } else {
22274                                            Ok(Expression::Function(Box::new(Function::new(
22275                                                "DATE_FORMAT".to_string(),
22276                                                vec![from_unix, fmt_expr],
22277                                            ))))
22278                                        }
22279                                    }
22280                                    _ => {
22281                                        // Keep as FROM_UNIXTIME(x, fmt) for other targets
22282                                        Ok(Expression::Function(Box::new(Function::new(
22283                                            "FROM_UNIXTIME".to_string(),
22284                                            vec![unix_ts, fmt_expr],
22285                                        ))))
22286                                    }
22287                                }
22288                            }
22289                            // DATEPART(unit, expr) -> EXTRACT(unit FROM expr) for Spark
22290                            "DATEPART" | "DATE_PART" if f.args.len() == 2 => {
22291                                let unit_str = Self::get_unit_str_static(&f.args[0]);
22292                                // Get the raw unit text preserving original case
22293                                let raw_unit = match &f.args[0] {
22294                                    Expression::Identifier(id) => id.name.clone(),
22295                                    Expression::Var(v) => v.this.clone(),
22296                                    Expression::Literal(lit)
22297                                        if matches!(
22298                                            lit.as_ref(),
22299                                            crate::expressions::Literal::String(_)
22300                                        ) =>
22301                                    {
22302                                        let crate::expressions::Literal::String(s) = lit.as_ref()
22303                                        else {
22304                                            unreachable!()
22305                                        };
22306                                        s.clone()
22307                                    }
22308                                    Expression::Cast(cast)
22309                                        if cast.format.is_none()
22310                                            && cast.default.is_none()
22311                                            && Self::unit_cast_target_is_string(&cast.to)
22312                                            && matches!(
22313                                                &cast.this,
22314                                                Expression::Literal(lit)
22315                                                    if matches!(
22316                                                        lit.as_ref(),
22317                                                        crate::expressions::Literal::String(_)
22318                                                    )
22319                                            ) =>
22320                                    {
22321                                        let Expression::Literal(lit) = &cast.this else {
22322                                            unreachable!()
22323                                        };
22324                                        let crate::expressions::Literal::String(s) = lit.as_ref()
22325                                        else {
22326                                            unreachable!()
22327                                        };
22328                                        s.clone()
22329                                    }
22330                                    Expression::Column(col) => col.name.name.clone(),
22331                                    _ => unit_str.clone(),
22332                                };
22333                                match target {
22334                                    DialectType::TSQL | DialectType::Fabric => {
22335                                        // Preserve original case of unit for TSQL
22336                                        let unit_name = match unit_str.as_str() {
22337                                            "YY" | "YYYY" => "YEAR".to_string(),
22338                                            "QQ" | "Q" => "QUARTER".to_string(),
22339                                            "MM" | "M" => "MONTH".to_string(),
22340                                            "WK" | "WW" => "WEEK".to_string(),
22341                                            "DD" | "D" | "DY" => "DAY".to_string(),
22342                                            "HH" => "HOUR".to_string(),
22343                                            "MI" | "N" => "MINUTE".to_string(),
22344                                            "SS" | "S" => "SECOND".to_string(),
22345                                            _ => raw_unit.clone(), // preserve original case
22346                                        };
22347                                        let mut args = f.args;
22348                                        args[0] =
22349                                            Expression::Identifier(Identifier::new(&unit_name));
22350                                        Ok(Expression::Function(Box::new(Function::new(
22351                                            "DATEPART".to_string(),
22352                                            args,
22353                                        ))))
22354                                    }
22355                                    DialectType::Spark | DialectType::Databricks => {
22356                                        // DATEPART(unit, expr) -> EXTRACT(unit FROM expr)
22357                                        // Preserve original case for non-abbreviation units
22358                                        let unit = match unit_str.as_str() {
22359                                            "YY" | "YYYY" => "YEAR".to_string(),
22360                                            "QQ" | "Q" => "QUARTER".to_string(),
22361                                            "MM" | "M" => "MONTH".to_string(),
22362                                            "WK" | "WW" => "WEEK".to_string(),
22363                                            "DD" | "D" | "DY" => "DAY".to_string(),
22364                                            "HH" => "HOUR".to_string(),
22365                                            "MI" | "N" => "MINUTE".to_string(),
22366                                            "SS" | "S" => "SECOND".to_string(),
22367                                            _ => raw_unit, // preserve original case
22368                                        };
22369                                        Ok(Expression::Extract(Box::new(
22370                                            crate::expressions::ExtractFunc {
22371                                                this: f.args[1].clone(),
22372                                                field: crate::expressions::DateTimeField::Custom(
22373                                                    unit,
22374                                                ),
22375                                            },
22376                                        )))
22377                                    }
22378                                    _ => Ok(Expression::Function(Box::new(Function::new(
22379                                        "DATE_PART".to_string(),
22380                                        f.args,
22381                                    )))),
22382                                }
22383                            }
22384                            // DATENAME(mm, date) -> FORMAT(CAST(date AS DATETIME2), 'MMMM') for TSQL
22385                            // DATENAME(dw, date) -> FORMAT(CAST(date AS DATETIME2), 'dddd') for TSQL
22386                            // DATENAME(mm, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'MMMM') for Spark
22387                            // DATENAME(dw, date) -> DATE_FORMAT(CAST(date AS TIMESTAMP), 'EEEE') for Spark
22388                            "DATENAME" if f.args.len() == 2 => {
22389                                let unit_str = Self::get_unit_str_static(&f.args[0]);
22390                                let date_expr = f.args[1].clone();
22391                                match unit_str.as_str() {
22392                                    "MM" | "M" | "MONTH" => match target {
22393                                        DialectType::TSQL => {
22394                                            let cast_date = Expression::Cast(Box::new(
22395                                                crate::expressions::Cast {
22396                                                    this: date_expr,
22397                                                    to: DataType::Custom {
22398                                                        name: "DATETIME2".to_string(),
22399                                                    },
22400                                                    trailing_comments: Vec::new(),
22401                                                    double_colon_syntax: false,
22402                                                    format: None,
22403                                                    default: None,
22404                                                    inferred_type: None,
22405                                                },
22406                                            ));
22407                                            Ok(Expression::Function(Box::new(Function::new(
22408                                                "FORMAT".to_string(),
22409                                                vec![cast_date, Expression::string("MMMM")],
22410                                            ))))
22411                                        }
22412                                        DialectType::Spark | DialectType::Databricks => {
22413                                            let cast_date = Expression::Cast(Box::new(
22414                                                crate::expressions::Cast {
22415                                                    this: date_expr,
22416                                                    to: DataType::Timestamp {
22417                                                        timezone: false,
22418                                                        precision: None,
22419                                                    },
22420                                                    trailing_comments: Vec::new(),
22421                                                    double_colon_syntax: false,
22422                                                    format: None,
22423                                                    default: None,
22424                                                    inferred_type: None,
22425                                                },
22426                                            ));
22427                                            Ok(Expression::Function(Box::new(Function::new(
22428                                                "DATE_FORMAT".to_string(),
22429                                                vec![cast_date, Expression::string("MMMM")],
22430                                            ))))
22431                                        }
22432                                        _ => Ok(Expression::Function(f)),
22433                                    },
22434                                    "DW" | "WEEKDAY" => match target {
22435                                        DialectType::TSQL => {
22436                                            let cast_date = Expression::Cast(Box::new(
22437                                                crate::expressions::Cast {
22438                                                    this: date_expr,
22439                                                    to: DataType::Custom {
22440                                                        name: "DATETIME2".to_string(),
22441                                                    },
22442                                                    trailing_comments: Vec::new(),
22443                                                    double_colon_syntax: false,
22444                                                    format: None,
22445                                                    default: None,
22446                                                    inferred_type: None,
22447                                                },
22448                                            ));
22449                                            Ok(Expression::Function(Box::new(Function::new(
22450                                                "FORMAT".to_string(),
22451                                                vec![cast_date, Expression::string("dddd")],
22452                                            ))))
22453                                        }
22454                                        DialectType::Spark | DialectType::Databricks => {
22455                                            let cast_date = Expression::Cast(Box::new(
22456                                                crate::expressions::Cast {
22457                                                    this: date_expr,
22458                                                    to: DataType::Timestamp {
22459                                                        timezone: false,
22460                                                        precision: None,
22461                                                    },
22462                                                    trailing_comments: Vec::new(),
22463                                                    double_colon_syntax: false,
22464                                                    format: None,
22465                                                    default: None,
22466                                                    inferred_type: None,
22467                                                },
22468                                            ));
22469                                            Ok(Expression::Function(Box::new(Function::new(
22470                                                "DATE_FORMAT".to_string(),
22471                                                vec![cast_date, Expression::string("EEEE")],
22472                                            ))))
22473                                        }
22474                                        _ => Ok(Expression::Function(f)),
22475                                    },
22476                                    _ => Ok(Expression::Function(f)),
22477                                }
22478                            }
22479                            // STRING_AGG(x, sep) without WITHIN GROUP -> target-specific
22480                            "STRING_AGG" if f.args.len() >= 2 => {
22481                                let x = f.args[0].clone();
22482                                let sep = f.args[1].clone();
22483                                match target {
22484                                    DialectType::MySQL
22485                                    | DialectType::SingleStore
22486                                    | DialectType::Doris
22487                                    | DialectType::StarRocks => Ok(Expression::GroupConcat(
22488                                        Box::new(crate::expressions::GroupConcatFunc {
22489                                            this: x,
22490                                            separator: Some(sep),
22491                                            order_by: None,
22492                                            distinct: false,
22493                                            filter: None,
22494                                            limit: None,
22495                                            inferred_type: None,
22496                                        }),
22497                                    )),
22498                                    DialectType::SQLite => Ok(Expression::GroupConcat(Box::new(
22499                                        crate::expressions::GroupConcatFunc {
22500                                            this: x,
22501                                            separator: Some(sep),
22502                                            order_by: None,
22503                                            distinct: false,
22504                                            filter: None,
22505                                            limit: None,
22506                                            inferred_type: None,
22507                                        },
22508                                    ))),
22509                                    DialectType::PostgreSQL | DialectType::Redshift => {
22510                                        Ok(Expression::StringAgg(Box::new(
22511                                            crate::expressions::StringAggFunc {
22512                                                this: x,
22513                                                separator: Some(sep),
22514                                                order_by: None,
22515                                                distinct: false,
22516                                                filter: None,
22517                                                limit: None,
22518                                                inferred_type: None,
22519                                            },
22520                                        )))
22521                                    }
22522                                    _ => Ok(Expression::Function(f)),
22523                                }
22524                            }
22525                            "TRY_DIVIDE" if f.args.len() == 2 => {
22526                                let mut args = f.args;
22527                                let x = args.remove(0);
22528                                let y = args.remove(0);
22529                                match target {
22530                                    DialectType::Spark | DialectType::Databricks => {
22531                                        Ok(Expression::Function(Box::new(Function::new(
22532                                            "TRY_DIVIDE".to_string(),
22533                                            vec![x, y],
22534                                        ))))
22535                                    }
22536                                    DialectType::Snowflake => {
22537                                        let y_ref = match &y {
22538                                            Expression::Column(_)
22539                                            | Expression::Literal(_)
22540                                            | Expression::Identifier(_) => y.clone(),
22541                                            _ => Expression::Paren(Box::new(Paren {
22542                                                this: y.clone(),
22543                                                trailing_comments: vec![],
22544                                            })),
22545                                        };
22546                                        let x_ref = match &x {
22547                                            Expression::Column(_)
22548                                            | Expression::Literal(_)
22549                                            | Expression::Identifier(_) => x.clone(),
22550                                            _ => Expression::Paren(Box::new(Paren {
22551                                                this: x.clone(),
22552                                                trailing_comments: vec![],
22553                                            })),
22554                                        };
22555                                        let condition = Expression::Neq(Box::new(
22556                                            crate::expressions::BinaryOp::new(
22557                                                y_ref.clone(),
22558                                                Expression::number(0),
22559                                            ),
22560                                        ));
22561                                        let div_expr = Expression::Div(Box::new(
22562                                            crate::expressions::BinaryOp::new(x_ref, y_ref),
22563                                        ));
22564                                        Ok(Expression::IfFunc(Box::new(
22565                                            crate::expressions::IfFunc {
22566                                                condition,
22567                                                true_value: div_expr,
22568                                                false_value: Some(Expression::Null(Null)),
22569                                                original_name: Some("IFF".to_string()),
22570                                                inferred_type: None,
22571                                            },
22572                                        )))
22573                                    }
22574                                    DialectType::DuckDB => {
22575                                        let y_ref = match &y {
22576                                            Expression::Column(_)
22577                                            | Expression::Literal(_)
22578                                            | Expression::Identifier(_) => y.clone(),
22579                                            _ => Expression::Paren(Box::new(Paren {
22580                                                this: y.clone(),
22581                                                trailing_comments: vec![],
22582                                            })),
22583                                        };
22584                                        let x_ref = match &x {
22585                                            Expression::Column(_)
22586                                            | Expression::Literal(_)
22587                                            | Expression::Identifier(_) => x.clone(),
22588                                            _ => Expression::Paren(Box::new(Paren {
22589                                                this: x.clone(),
22590                                                trailing_comments: vec![],
22591                                            })),
22592                                        };
22593                                        let condition = Expression::Neq(Box::new(
22594                                            crate::expressions::BinaryOp::new(
22595                                                y_ref.clone(),
22596                                                Expression::number(0),
22597                                            ),
22598                                        ));
22599                                        let div_expr = Expression::Div(Box::new(
22600                                            crate::expressions::BinaryOp::new(x_ref, y_ref),
22601                                        ));
22602                                        Ok(Expression::Case(Box::new(Case {
22603                                            operand: None,
22604                                            whens: vec![(condition, div_expr)],
22605                                            else_: Some(Expression::Null(Null)),
22606                                            comments: Vec::new(),
22607                                            inferred_type: None,
22608                                        })))
22609                                    }
22610                                    _ => Ok(Expression::Function(Box::new(Function::new(
22611                                        "TRY_DIVIDE".to_string(),
22612                                        vec![x, y],
22613                                    )))),
22614                                }
22615                            }
22616                            // JSON_ARRAYAGG -> JSON_AGG for PostgreSQL
22617                            "JSON_ARRAYAGG" => match target {
22618                                DialectType::PostgreSQL => {
22619                                    Ok(Expression::Function(Box::new(Function {
22620                                        name: "JSON_AGG".to_string(),
22621                                        ..(*f)
22622                                    })))
22623                                }
22624                                _ => Ok(Expression::Function(f)),
22625                            },
22626                            // SCHEMA_NAME(id) -> CURRENT_SCHEMA for PostgreSQL, 'main' for SQLite
22627                            "SCHEMA_NAME" => match target {
22628                                DialectType::PostgreSQL => Ok(Expression::CurrentSchema(Box::new(
22629                                    crate::expressions::CurrentSchema { this: None },
22630                                ))),
22631                                DialectType::SQLite => Ok(Expression::string("main")),
22632                                _ => Ok(Expression::Function(f)),
22633                            },
22634                            // TO_TIMESTAMP(x, fmt) 2-arg from Spark/Hive: convert Java format to target format
22635                            "TO_TIMESTAMP"
22636                                if f.args.len() == 2
22637                                    && matches!(
22638                                        source,
22639                                        DialectType::Spark
22640                                            | DialectType::Databricks
22641                                            | DialectType::Hive
22642                                    )
22643                                    && matches!(target, DialectType::DuckDB) =>
22644                            {
22645                                let mut args = f.args;
22646                                let val = args.remove(0);
22647                                let fmt_expr = args.remove(0);
22648                                if let Expression::Literal(ref lit) = fmt_expr {
22649                                    if let Literal::String(ref s) = lit.as_ref() {
22650                                        // Convert Java/Spark format to C strptime format
22651                                        fn java_to_c_fmt(fmt: &str) -> String {
22652                                            let result = fmt
22653                                                .replace("yyyy", "%Y")
22654                                                .replace("SSSSSS", "%f")
22655                                                .replace("EEEE", "%W")
22656                                                .replace("MM", "%m")
22657                                                .replace("dd", "%d")
22658                                                .replace("HH", "%H")
22659                                                .replace("mm", "%M")
22660                                                .replace("ss", "%S")
22661                                                .replace("yy", "%y");
22662                                            let mut out = String::new();
22663                                            let chars: Vec<char> = result.chars().collect();
22664                                            let mut i = 0;
22665                                            while i < chars.len() {
22666                                                if chars[i] == '%' && i + 1 < chars.len() {
22667                                                    out.push(chars[i]);
22668                                                    out.push(chars[i + 1]);
22669                                                    i += 2;
22670                                                } else if chars[i] == 'z' {
22671                                                    out.push_str("%Z");
22672                                                    i += 1;
22673                                                } else if chars[i] == 'Z' {
22674                                                    out.push_str("%z");
22675                                                    i += 1;
22676                                                } else {
22677                                                    out.push(chars[i]);
22678                                                    i += 1;
22679                                                }
22680                                            }
22681                                            out
22682                                        }
22683                                        let c_fmt = java_to_c_fmt(s);
22684                                        Ok(Expression::Function(Box::new(Function::new(
22685                                            "STRPTIME".to_string(),
22686                                            vec![val, Expression::string(&c_fmt)],
22687                                        ))))
22688                                    } else {
22689                                        Ok(Expression::Function(Box::new(Function::new(
22690                                            "STRPTIME".to_string(),
22691                                            vec![val, fmt_expr],
22692                                        ))))
22693                                    }
22694                                } else {
22695                                    Ok(Expression::Function(Box::new(Function::new(
22696                                        "STRPTIME".to_string(),
22697                                        vec![val, fmt_expr],
22698                                    ))))
22699                                }
22700                            }
22701                            // TO_DATE(x) 1-arg from Doris: date conversion
22702                            "TO_DATE"
22703                                if f.args.len() == 1
22704                                    && matches!(
22705                                        source,
22706                                        DialectType::Doris | DialectType::StarRocks
22707                                    ) =>
22708                            {
22709                                let arg = f.args.into_iter().next().unwrap();
22710                                match target {
22711                                    DialectType::Oracle
22712                                    | DialectType::DuckDB
22713                                    | DialectType::TSQL => {
22714                                        // CAST(x AS DATE)
22715                                        Ok(Expression::Cast(Box::new(Cast {
22716                                            this: arg,
22717                                            to: DataType::Date,
22718                                            double_colon_syntax: false,
22719                                            trailing_comments: vec![],
22720                                            format: None,
22721                                            default: None,
22722                                            inferred_type: None,
22723                                        })))
22724                                    }
22725                                    DialectType::MySQL | DialectType::SingleStore => {
22726                                        // DATE(x)
22727                                        Ok(Expression::Function(Box::new(Function::new(
22728                                            "DATE".to_string(),
22729                                            vec![arg],
22730                                        ))))
22731                                    }
22732                                    _ => {
22733                                        // Default: keep as TO_DATE(x) (Spark, PostgreSQL, etc.)
22734                                        Ok(Expression::Function(Box::new(Function::new(
22735                                            "TO_DATE".to_string(),
22736                                            vec![arg],
22737                                        ))))
22738                                    }
22739                                }
22740                            }
22741                            // TO_DATE(x) 1-arg from Spark/Hive: safe date conversion
22742                            "TO_DATE"
22743                                if f.args.len() == 1
22744                                    && matches!(
22745                                        source,
22746                                        DialectType::Spark
22747                                            | DialectType::Databricks
22748                                            | DialectType::Hive
22749                                    ) =>
22750                            {
22751                                let arg = f.args.into_iter().next().unwrap();
22752                                match target {
22753                                    DialectType::DuckDB => {
22754                                        // Spark TO_DATE is safe -> TRY_CAST(x AS DATE)
22755                                        Ok(Expression::TryCast(Box::new(Cast {
22756                                            this: arg,
22757                                            to: DataType::Date,
22758                                            double_colon_syntax: false,
22759                                            trailing_comments: vec![],
22760                                            format: None,
22761                                            default: None,
22762                                            inferred_type: None,
22763                                        })))
22764                                    }
22765                                    DialectType::Presto
22766                                    | DialectType::Trino
22767                                    | DialectType::Athena => {
22768                                        // CAST(CAST(x AS TIMESTAMP) AS DATE)
22769                                        Ok(Self::double_cast_timestamp_date(arg))
22770                                    }
22771                                    DialectType::Snowflake => {
22772                                        // Spark's TO_DATE is safe -> TRY_TO_DATE(x, 'yyyy-mm-DD')
22773                                        // The default Spark format 'yyyy-MM-dd' maps to Snowflake 'yyyy-mm-DD'
22774                                        Ok(Expression::Function(Box::new(Function::new(
22775                                            "TRY_TO_DATE".to_string(),
22776                                            vec![arg, Expression::string("yyyy-mm-DD")],
22777                                        ))))
22778                                    }
22779                                    _ => {
22780                                        // Default: keep as TO_DATE(x)
22781                                        Ok(Expression::Function(Box::new(Function::new(
22782                                            "TO_DATE".to_string(),
22783                                            vec![arg],
22784                                        ))))
22785                                    }
22786                                }
22787                            }
22788                            // TO_DATE(x, fmt) 2-arg from Spark/Hive: format-based date conversion
22789                            "TO_DATE"
22790                                if f.args.len() == 2
22791                                    && matches!(
22792                                        source,
22793                                        DialectType::Spark
22794                                            | DialectType::Databricks
22795                                            | DialectType::Hive
22796                                    ) =>
22797                            {
22798                                let mut args = f.args;
22799                                let val = args.remove(0);
22800                                let fmt_expr = args.remove(0);
22801                                let is_default_format = matches!(&fmt_expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s == "yyyy-MM-dd"));
22802
22803                                if is_default_format {
22804                                    // Default format: same as 1-arg form
22805                                    match target {
22806                                        DialectType::DuckDB => {
22807                                            Ok(Expression::TryCast(Box::new(Cast {
22808                                                this: val,
22809                                                to: DataType::Date,
22810                                                double_colon_syntax: false,
22811                                                trailing_comments: vec![],
22812                                                format: None,
22813                                                default: None,
22814                                                inferred_type: None,
22815                                            })))
22816                                        }
22817                                        DialectType::Presto
22818                                        | DialectType::Trino
22819                                        | DialectType::Athena => {
22820                                            Ok(Self::double_cast_timestamp_date(val))
22821                                        }
22822                                        DialectType::Snowflake => {
22823                                            // TRY_TO_DATE(x, format) with Snowflake format mapping
22824                                            let sf_fmt = "yyyy-MM-dd"
22825                                                .replace("yyyy", "yyyy")
22826                                                .replace("MM", "mm")
22827                                                .replace("dd", "DD");
22828                                            Ok(Expression::Function(Box::new(Function::new(
22829                                                "TRY_TO_DATE".to_string(),
22830                                                vec![val, Expression::string(&sf_fmt)],
22831                                            ))))
22832                                        }
22833                                        _ => Ok(Expression::Function(Box::new(Function::new(
22834                                            "TO_DATE".to_string(),
22835                                            vec![val],
22836                                        )))),
22837                                    }
22838                                } else {
22839                                    // Non-default format: use format-based parsing
22840                                    if let Expression::Literal(ref lit) = fmt_expr {
22841                                        if let Literal::String(ref s) = lit.as_ref() {
22842                                            match target {
22843                                                DialectType::DuckDB => {
22844                                                    // CAST(CAST(TRY_STRPTIME(x, c_fmt) AS TIMESTAMP) AS DATE)
22845                                                    fn java_to_c_fmt_todate(fmt: &str) -> String {
22846                                                        let result = fmt
22847                                                            .replace("yyyy", "%Y")
22848                                                            .replace("SSSSSS", "%f")
22849                                                            .replace("EEEE", "%W")
22850                                                            .replace("MM", "%m")
22851                                                            .replace("dd", "%d")
22852                                                            .replace("HH", "%H")
22853                                                            .replace("mm", "%M")
22854                                                            .replace("ss", "%S")
22855                                                            .replace("yy", "%y");
22856                                                        let mut out = String::new();
22857                                                        let chars: Vec<char> =
22858                                                            result.chars().collect();
22859                                                        let mut i = 0;
22860                                                        while i < chars.len() {
22861                                                            if chars[i] == '%'
22862                                                                && i + 1 < chars.len()
22863                                                            {
22864                                                                out.push(chars[i]);
22865                                                                out.push(chars[i + 1]);
22866                                                                i += 2;
22867                                                            } else if chars[i] == 'z' {
22868                                                                out.push_str("%Z");
22869                                                                i += 1;
22870                                                            } else if chars[i] == 'Z' {
22871                                                                out.push_str("%z");
22872                                                                i += 1;
22873                                                            } else {
22874                                                                out.push(chars[i]);
22875                                                                i += 1;
22876                                                            }
22877                                                        }
22878                                                        out
22879                                                    }
22880                                                    let c_fmt = java_to_c_fmt_todate(s);
22881                                                    // CAST(CAST(TRY_STRPTIME(x, fmt) AS TIMESTAMP) AS DATE)
22882                                                    let try_strptime = Expression::Function(
22883                                                        Box::new(Function::new(
22884                                                            "TRY_STRPTIME".to_string(),
22885                                                            vec![val, Expression::string(&c_fmt)],
22886                                                        )),
22887                                                    );
22888                                                    let cast_ts =
22889                                                        Expression::Cast(Box::new(Cast {
22890                                                            this: try_strptime,
22891                                                            to: DataType::Timestamp {
22892                                                                precision: None,
22893                                                                timezone: false,
22894                                                            },
22895                                                            double_colon_syntax: false,
22896                                                            trailing_comments: vec![],
22897                                                            format: None,
22898                                                            default: None,
22899                                                            inferred_type: None,
22900                                                        }));
22901                                                    Ok(Expression::Cast(Box::new(Cast {
22902                                                        this: cast_ts,
22903                                                        to: DataType::Date,
22904                                                        double_colon_syntax: false,
22905                                                        trailing_comments: vec![],
22906                                                        format: None,
22907                                                        default: None,
22908                                                        inferred_type: None,
22909                                                    })))
22910                                                }
22911                                                DialectType::Presto
22912                                                | DialectType::Trino
22913                                                | DialectType::Athena => {
22914                                                    // CAST(DATE_PARSE(x, presto_fmt) AS DATE)
22915                                                    let p_fmt = s
22916                                                        .replace("yyyy", "%Y")
22917                                                        .replace("SSSSSS", "%f")
22918                                                        .replace("MM", "%m")
22919                                                        .replace("dd", "%d")
22920                                                        .replace("HH", "%H")
22921                                                        .replace("mm", "%M")
22922                                                        .replace("ss", "%S")
22923                                                        .replace("yy", "%y");
22924                                                    let date_parse = Expression::Function(
22925                                                        Box::new(Function::new(
22926                                                            "DATE_PARSE".to_string(),
22927                                                            vec![val, Expression::string(&p_fmt)],
22928                                                        )),
22929                                                    );
22930                                                    Ok(Expression::Cast(Box::new(Cast {
22931                                                        this: date_parse,
22932                                                        to: DataType::Date,
22933                                                        double_colon_syntax: false,
22934                                                        trailing_comments: vec![],
22935                                                        format: None,
22936                                                        default: None,
22937                                                        inferred_type: None,
22938                                                    })))
22939                                                }
22940                                                DialectType::Snowflake => {
22941                                                    // TRY_TO_DATE(x, snowflake_fmt)
22942                                                    Ok(Expression::Function(Box::new(
22943                                                        Function::new(
22944                                                            "TRY_TO_DATE".to_string(),
22945                                                            vec![val, Expression::string(s)],
22946                                                        ),
22947                                                    )))
22948                                                }
22949                                                _ => Ok(Expression::Function(Box::new(
22950                                                    Function::new(
22951                                                        "TO_DATE".to_string(),
22952                                                        vec![val, fmt_expr],
22953                                                    ),
22954                                                ))),
22955                                            }
22956                                        } else {
22957                                            Ok(Expression::Function(Box::new(Function::new(
22958                                                "TO_DATE".to_string(),
22959                                                vec![val, fmt_expr],
22960                                            ))))
22961                                        }
22962                                    } else {
22963                                        Ok(Expression::Function(Box::new(Function::new(
22964                                            "TO_DATE".to_string(),
22965                                            vec![val, fmt_expr],
22966                                        ))))
22967                                    }
22968                                }
22969                            }
22970                            // TO_TIMESTAMP(x) 1-arg: epoch conversion
22971                            "TO_TIMESTAMP"
22972                                if f.args.len() == 1
22973                                    && matches!(source, DialectType::DuckDB)
22974                                    && matches!(
22975                                        target,
22976                                        DialectType::BigQuery
22977                                            | DialectType::Presto
22978                                            | DialectType::Trino
22979                                            | DialectType::Hive
22980                                            | DialectType::Spark
22981                                            | DialectType::Databricks
22982                                            | DialectType::Athena
22983                                    ) =>
22984                            {
22985                                let arg = f.args.into_iter().next().unwrap();
22986                                let func_name = match target {
22987                                    DialectType::BigQuery => "TIMESTAMP_SECONDS",
22988                                    DialectType::Presto
22989                                    | DialectType::Trino
22990                                    | DialectType::Athena
22991                                    | DialectType::Hive
22992                                    | DialectType::Spark
22993                                    | DialectType::Databricks => "FROM_UNIXTIME",
22994                                    _ => "TO_TIMESTAMP",
22995                                };
22996                                Ok(Expression::Function(Box::new(Function::new(
22997                                    func_name.to_string(),
22998                                    vec![arg],
22999                                ))))
23000                            }
23001                            // CONCAT(x) single-arg: -> CONCAT(COALESCE(x, '')) for Spark
23002                            "CONCAT" if f.args.len() == 1 => {
23003                                let arg = f.args.into_iter().next().unwrap();
23004                                match target {
23005                                    DialectType::Presto
23006                                    | DialectType::Trino
23007                                    | DialectType::Athena => {
23008                                        // CONCAT(a) -> CAST(a AS VARCHAR)
23009                                        Ok(Expression::Cast(Box::new(Cast {
23010                                            this: arg,
23011                                            to: DataType::VarChar {
23012                                                length: None,
23013                                                parenthesized_length: false,
23014                                            },
23015                                            trailing_comments: vec![],
23016                                            double_colon_syntax: false,
23017                                            format: None,
23018                                            default: None,
23019                                            inferred_type: None,
23020                                        })))
23021                                    }
23022                                    DialectType::TSQL => {
23023                                        // CONCAT(a) -> a
23024                                        Ok(arg)
23025                                    }
23026                                    DialectType::DuckDB => {
23027                                        // Keep CONCAT(a) for DuckDB (native support)
23028                                        Ok(Expression::Function(Box::new(Function::new(
23029                                            "CONCAT".to_string(),
23030                                            vec![arg],
23031                                        ))))
23032                                    }
23033                                    DialectType::Spark | DialectType::Databricks => {
23034                                        let coalesced = Expression::Coalesce(Box::new(
23035                                            crate::expressions::VarArgFunc {
23036                                                expressions: vec![arg, Expression::string("")],
23037                                                original_name: None,
23038                                                inferred_type: None,
23039                                            },
23040                                        ));
23041                                        Ok(Expression::Function(Box::new(Function::new(
23042                                            "CONCAT".to_string(),
23043                                            vec![coalesced],
23044                                        ))))
23045                                    }
23046                                    _ => Ok(Expression::Function(Box::new(Function::new(
23047                                        "CONCAT".to_string(),
23048                                        vec![arg],
23049                                    )))),
23050                                }
23051                            }
23052                            // REGEXP_EXTRACT(a, p) 2-arg: BigQuery default group is 0 (no 3rd arg needed)
23053                            "REGEXP_EXTRACT"
23054                                if f.args.len() == 3 && matches!(target, DialectType::BigQuery) =>
23055                            {
23056                                // If group_index is 0, drop it
23057                                let drop_group = match &f.args[2] {
23058                                    Expression::Literal(lit)
23059                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
23060                                    {
23061                                        let Literal::Number(n) = lit.as_ref() else {
23062                                            unreachable!()
23063                                        };
23064                                        n == "0"
23065                                    }
23066                                    _ => false,
23067                                };
23068                                if drop_group {
23069                                    let mut args = f.args;
23070                                    args.truncate(2);
23071                                    Ok(Expression::Function(Box::new(Function::new(
23072                                        "REGEXP_EXTRACT".to_string(),
23073                                        args,
23074                                    ))))
23075                                } else {
23076                                    Ok(Expression::Function(f))
23077                                }
23078                            }
23079                            // REGEXP_EXTRACT(a, pattern, group, flags) 4-arg -> REGEXP_SUBSTR for Snowflake
23080                            "REGEXP_EXTRACT"
23081                                if f.args.len() == 4
23082                                    && matches!(target, DialectType::Snowflake) =>
23083                            {
23084                                // REGEXP_EXTRACT(a, 'pattern', 2, 'i') -> REGEXP_SUBSTR(a, 'pattern', 1, 1, 'i', 2)
23085                                let mut args = f.args;
23086                                let this = args.remove(0);
23087                                let pattern = args.remove(0);
23088                                let group = args.remove(0);
23089                                let flags = args.remove(0);
23090                                Ok(Expression::Function(Box::new(Function::new(
23091                                    "REGEXP_SUBSTR".to_string(),
23092                                    vec![
23093                                        this,
23094                                        pattern,
23095                                        Expression::number(1),
23096                                        Expression::number(1),
23097                                        flags,
23098                                        group,
23099                                    ],
23100                                ))))
23101                            }
23102                            // REGEXP_SUBSTR(a, pattern, position) 3-arg -> REGEXP_EXTRACT(SUBSTRING(a, pos), pattern)
23103                            "REGEXP_SUBSTR"
23104                                if f.args.len() == 3
23105                                    && matches!(
23106                                        target,
23107                                        DialectType::DuckDB
23108                                            | DialectType::Presto
23109                                            | DialectType::Trino
23110                                            | DialectType::Spark
23111                                            | DialectType::Databricks
23112                                    ) =>
23113                            {
23114                                let mut args = f.args;
23115                                let this = args.remove(0);
23116                                let pattern = args.remove(0);
23117                                let position = args.remove(0);
23118                                // Wrap subject in SUBSTRING(this, position) to apply the offset
23119                                let substring_expr = Expression::Function(Box::new(Function::new(
23120                                    "SUBSTRING".to_string(),
23121                                    vec![this, position],
23122                                )));
23123                                let target_name = match target {
23124                                    DialectType::DuckDB => "REGEXP_EXTRACT",
23125                                    _ => "REGEXP_EXTRACT",
23126                                };
23127                                Ok(Expression::Function(Box::new(Function::new(
23128                                    target_name.to_string(),
23129                                    vec![substring_expr, pattern],
23130                                ))))
23131                            }
23132                            // TO_DAYS(x) -> (DATEDIFF(x, '0000-01-01') + 1) or target-specific
23133                            "TO_DAYS" if f.args.len() == 1 => {
23134                                let x = f.args.into_iter().next().unwrap();
23135                                let epoch = Expression::string("0000-01-01");
23136                                // Build the final target-specific expression directly
23137                                let datediff_expr = match target {
23138                                    DialectType::MySQL | DialectType::SingleStore => {
23139                                        // MySQL: (DATEDIFF(x, '0000-01-01') + 1)
23140                                        Expression::Function(Box::new(Function::new(
23141                                            "DATEDIFF".to_string(),
23142                                            vec![x, epoch],
23143                                        )))
23144                                    }
23145                                    DialectType::DuckDB => {
23146                                        // DuckDB: (DATE_DIFF('DAY', CAST('0000-01-01' AS DATE), CAST(x AS DATE)) + 1)
23147                                        let cast_epoch = Expression::Cast(Box::new(Cast {
23148                                            this: epoch,
23149                                            to: DataType::Date,
23150                                            trailing_comments: Vec::new(),
23151                                            double_colon_syntax: false,
23152                                            format: None,
23153                                            default: None,
23154                                            inferred_type: None,
23155                                        }));
23156                                        let cast_x = Expression::Cast(Box::new(Cast {
23157                                            this: x,
23158                                            to: DataType::Date,
23159                                            trailing_comments: Vec::new(),
23160                                            double_colon_syntax: false,
23161                                            format: None,
23162                                            default: None,
23163                                            inferred_type: None,
23164                                        }));
23165                                        Expression::Function(Box::new(Function::new(
23166                                            "DATE_DIFF".to_string(),
23167                                            vec![Expression::string("DAY"), cast_epoch, cast_x],
23168                                        )))
23169                                    }
23170                                    DialectType::Presto
23171                                    | DialectType::Trino
23172                                    | DialectType::Athena => {
23173                                        // Presto: (DATE_DIFF('DAY', CAST(CAST('0000-01-01' AS TIMESTAMP) AS DATE), CAST(CAST(x AS TIMESTAMP) AS DATE)) + 1)
23174                                        let cast_epoch = Self::double_cast_timestamp_date(epoch);
23175                                        let cast_x = Self::double_cast_timestamp_date(x);
23176                                        Expression::Function(Box::new(Function::new(
23177                                            "DATE_DIFF".to_string(),
23178                                            vec![Expression::string("DAY"), cast_epoch, cast_x],
23179                                        )))
23180                                    }
23181                                    _ => {
23182                                        // Default: (DATEDIFF(x, '0000-01-01') + 1)
23183                                        Expression::Function(Box::new(Function::new(
23184                                            "DATEDIFF".to_string(),
23185                                            vec![x, epoch],
23186                                        )))
23187                                    }
23188                                };
23189                                let add_one = Expression::Add(Box::new(BinaryOp::new(
23190                                    datediff_expr,
23191                                    Expression::number(1),
23192                                )));
23193                                Ok(Expression::Paren(Box::new(crate::expressions::Paren {
23194                                    this: add_one,
23195                                    trailing_comments: Vec::new(),
23196                                })))
23197                            }
23198                            // STR_TO_DATE(x, format) -> DATE_PARSE / STRPTIME / TO_DATE etc.
23199                            "STR_TO_DATE"
23200                                if f.args.len() == 2
23201                                    && matches!(
23202                                        target,
23203                                        DialectType::Presto | DialectType::Trino
23204                                    ) =>
23205                            {
23206                                let mut args = f.args;
23207                                let x = args.remove(0);
23208                                let format_expr = args.remove(0);
23209                                // Check if the format contains time components
23210                                let has_time = if let Expression::Literal(ref lit) = format_expr {
23211                                    if let Literal::String(ref fmt) = lit.as_ref() {
23212                                        fmt.contains("%H")
23213                                            || fmt.contains("%T")
23214                                            || fmt.contains("%M")
23215                                            || fmt.contains("%S")
23216                                            || fmt.contains("%I")
23217                                            || fmt.contains("%p")
23218                                    } else {
23219                                        false
23220                                    }
23221                                } else {
23222                                    false
23223                                };
23224                                let date_parse = Expression::Function(Box::new(Function::new(
23225                                    "DATE_PARSE".to_string(),
23226                                    vec![x, format_expr],
23227                                )));
23228                                if has_time {
23229                                    // Has time components: just DATE_PARSE
23230                                    Ok(date_parse)
23231                                } else {
23232                                    // Date-only: CAST(DATE_PARSE(...) AS DATE)
23233                                    Ok(Expression::Cast(Box::new(Cast {
23234                                        this: date_parse,
23235                                        to: DataType::Date,
23236                                        trailing_comments: Vec::new(),
23237                                        double_colon_syntax: false,
23238                                        format: None,
23239                                        default: None,
23240                                        inferred_type: None,
23241                                    })))
23242                                }
23243                            }
23244                            "STR_TO_DATE"
23245                                if f.args.len() == 2
23246                                    && matches!(
23247                                        target,
23248                                        DialectType::PostgreSQL | DialectType::Redshift
23249                                    ) =>
23250                            {
23251                                let mut args = f.args;
23252                                let x = args.remove(0);
23253                                let fmt = args.remove(0);
23254                                let pg_fmt = match fmt {
23255                                    Expression::Literal(lit)
23256                                        if matches!(lit.as_ref(), Literal::String(_)) =>
23257                                    {
23258                                        let Literal::String(s) = lit.as_ref() else {
23259                                            unreachable!()
23260                                        };
23261                                        Expression::string(
23262                                            &s.replace("%Y", "YYYY")
23263                                                .replace("%m", "MM")
23264                                                .replace("%d", "DD")
23265                                                .replace("%H", "HH24")
23266                                                .replace("%M", "MI")
23267                                                .replace("%S", "SS"),
23268                                        )
23269                                    }
23270                                    other => other,
23271                                };
23272                                let to_date = Expression::Function(Box::new(Function::new(
23273                                    "TO_DATE".to_string(),
23274                                    vec![x, pg_fmt],
23275                                )));
23276                                Ok(Expression::Cast(Box::new(Cast {
23277                                    this: to_date,
23278                                    to: DataType::Timestamp {
23279                                        timezone: false,
23280                                        precision: None,
23281                                    },
23282                                    trailing_comments: Vec::new(),
23283                                    double_colon_syntax: false,
23284                                    format: None,
23285                                    default: None,
23286                                    inferred_type: None,
23287                                })))
23288                            }
23289                            // RANGE(start, end) -> GENERATE_SERIES for SQLite
23290                            "RANGE"
23291                                if (f.args.len() == 1 || f.args.len() == 2)
23292                                    && matches!(target, DialectType::SQLite) =>
23293                            {
23294                                if f.args.len() == 2 {
23295                                    // RANGE(start, end) -> (SELECT value AS col_alias FROM GENERATE_SERIES(start, end))
23296                                    // For SQLite, RANGE is exclusive on end, GENERATE_SERIES is inclusive
23297                                    let mut args = f.args;
23298                                    let start = args.remove(0);
23299                                    let end = args.remove(0);
23300                                    Ok(Expression::Function(Box::new(Function::new(
23301                                        "GENERATE_SERIES".to_string(),
23302                                        vec![start, end],
23303                                    ))))
23304                                } else {
23305                                    Ok(Expression::Function(f))
23306                                }
23307                            }
23308                            // UNIFORM(low, high[, seed]) -> UNIFORM(low, high, RANDOM([seed])) for Snowflake
23309                            // When source is Snowflake, keep as-is (args already in correct form)
23310                            "UNIFORM"
23311                                if matches!(target, DialectType::Snowflake)
23312                                    && (f.args.len() == 2 || f.args.len() == 3) =>
23313                            {
23314                                if matches!(source, DialectType::Snowflake) {
23315                                    // Snowflake -> Snowflake: keep as-is
23316                                    Ok(Expression::Function(f))
23317                                } else {
23318                                    let mut args = f.args;
23319                                    let low = args.remove(0);
23320                                    let high = args.remove(0);
23321                                    let random = if !args.is_empty() {
23322                                        let seed = args.remove(0);
23323                                        Expression::Function(Box::new(Function::new(
23324                                            "RANDOM".to_string(),
23325                                            vec![seed],
23326                                        )))
23327                                    } else {
23328                                        Expression::Function(Box::new(Function::new(
23329                                            "RANDOM".to_string(),
23330                                            vec![],
23331                                        )))
23332                                    };
23333                                    Ok(Expression::Function(Box::new(Function::new(
23334                                        "UNIFORM".to_string(),
23335                                        vec![low, high, random],
23336                                    ))))
23337                                }
23338                            }
23339                            // TO_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
23340                            "TO_UTC_TIMESTAMP" if f.args.len() == 2 => {
23341                                let mut args = f.args;
23342                                let ts_arg = args.remove(0);
23343                                let tz_arg = args.remove(0);
23344                                // Cast string literal to TIMESTAMP for all targets
23345                                let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
23346                                {
23347                                    Expression::Cast(Box::new(Cast {
23348                                        this: ts_arg,
23349                                        to: DataType::Timestamp {
23350                                            timezone: false,
23351                                            precision: None,
23352                                        },
23353                                        trailing_comments: vec![],
23354                                        double_colon_syntax: false,
23355                                        format: None,
23356                                        default: None,
23357                                        inferred_type: None,
23358                                    }))
23359                                } else {
23360                                    ts_arg
23361                                };
23362                                match target {
23363                                    DialectType::Spark | DialectType::Databricks => {
23364                                        Ok(Expression::Function(Box::new(Function::new(
23365                                            "TO_UTC_TIMESTAMP".to_string(),
23366                                            vec![ts_cast, tz_arg],
23367                                        ))))
23368                                    }
23369                                    DialectType::Snowflake => {
23370                                        // CONVERT_TIMEZONE(tz, 'UTC', CAST(ts AS TIMESTAMP))
23371                                        Ok(Expression::Function(Box::new(Function::new(
23372                                            "CONVERT_TIMEZONE".to_string(),
23373                                            vec![tz_arg, Expression::string("UTC"), ts_cast],
23374                                        ))))
23375                                    }
23376                                    DialectType::Presto
23377                                    | DialectType::Trino
23378                                    | DialectType::Athena => {
23379                                        // WITH_TIMEZONE(CAST(ts AS TIMESTAMP), tz) AT TIME ZONE 'UTC'
23380                                        let wtz = Expression::Function(Box::new(Function::new(
23381                                            "WITH_TIMEZONE".to_string(),
23382                                            vec![ts_cast, tz_arg],
23383                                        )));
23384                                        Ok(Expression::AtTimeZone(Box::new(
23385                                            crate::expressions::AtTimeZone {
23386                                                this: wtz,
23387                                                zone: Expression::string("UTC"),
23388                                            },
23389                                        )))
23390                                    }
23391                                    DialectType::BigQuery => {
23392                                        // DATETIME(TIMESTAMP(CAST(ts AS DATETIME), tz), 'UTC')
23393                                        let cast_dt = Expression::Cast(Box::new(Cast {
23394                                            this: if let Expression::Cast(c) = ts_cast {
23395                                                c.this
23396                                            } else {
23397                                                ts_cast.clone()
23398                                            },
23399                                            to: DataType::Custom {
23400                                                name: "DATETIME".to_string(),
23401                                            },
23402                                            trailing_comments: vec![],
23403                                            double_colon_syntax: false,
23404                                            format: None,
23405                                            default: None,
23406                                            inferred_type: None,
23407                                        }));
23408                                        let ts_func =
23409                                            Expression::Function(Box::new(Function::new(
23410                                                "TIMESTAMP".to_string(),
23411                                                vec![cast_dt, tz_arg],
23412                                            )));
23413                                        Ok(Expression::Function(Box::new(Function::new(
23414                                            "DATETIME".to_string(),
23415                                            vec![ts_func, Expression::string("UTC")],
23416                                        ))))
23417                                    }
23418                                    _ => {
23419                                        // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz AT TIME ZONE 'UTC'
23420                                        let atz1 = Expression::AtTimeZone(Box::new(
23421                                            crate::expressions::AtTimeZone {
23422                                                this: ts_cast,
23423                                                zone: tz_arg,
23424                                            },
23425                                        ));
23426                                        Ok(Expression::AtTimeZone(Box::new(
23427                                            crate::expressions::AtTimeZone {
23428                                                this: atz1,
23429                                                zone: Expression::string("UTC"),
23430                                            },
23431                                        )))
23432                                    }
23433                                }
23434                            }
23435                            // FROM_UTC_TIMESTAMP(ts, tz) -> target-specific UTC conversion
23436                            "FROM_UTC_TIMESTAMP" if f.args.len() == 2 => {
23437                                let mut args = f.args;
23438                                let ts_arg = args.remove(0);
23439                                let tz_arg = args.remove(0);
23440                                // Cast string literal to TIMESTAMP
23441                                let ts_cast = if matches!(&ts_arg, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
23442                                {
23443                                    Expression::Cast(Box::new(Cast {
23444                                        this: ts_arg,
23445                                        to: DataType::Timestamp {
23446                                            timezone: false,
23447                                            precision: None,
23448                                        },
23449                                        trailing_comments: vec![],
23450                                        double_colon_syntax: false,
23451                                        format: None,
23452                                        default: None,
23453                                        inferred_type: None,
23454                                    }))
23455                                } else {
23456                                    ts_arg
23457                                };
23458                                match target {
23459                                    DialectType::Spark | DialectType::Databricks => {
23460                                        Ok(Expression::Function(Box::new(Function::new(
23461                                            "FROM_UTC_TIMESTAMP".to_string(),
23462                                            vec![ts_cast, tz_arg],
23463                                        ))))
23464                                    }
23465                                    DialectType::Presto
23466                                    | DialectType::Trino
23467                                    | DialectType::Athena => {
23468                                        // AT_TIMEZONE(CAST(ts AS TIMESTAMP), tz)
23469                                        Ok(Expression::Function(Box::new(Function::new(
23470                                            "AT_TIMEZONE".to_string(),
23471                                            vec![ts_cast, tz_arg],
23472                                        ))))
23473                                    }
23474                                    DialectType::Snowflake => {
23475                                        // CONVERT_TIMEZONE('UTC', tz, CAST(ts AS TIMESTAMP))
23476                                        Ok(Expression::Function(Box::new(Function::new(
23477                                            "CONVERT_TIMEZONE".to_string(),
23478                                            vec![Expression::string("UTC"), tz_arg, ts_cast],
23479                                        ))))
23480                                    }
23481                                    _ => {
23482                                        // DuckDB, PostgreSQL, Redshift: CAST(ts AS TIMESTAMP) AT TIME ZONE tz
23483                                        Ok(Expression::AtTimeZone(Box::new(
23484                                            crate::expressions::AtTimeZone {
23485                                                this: ts_cast,
23486                                                zone: tz_arg,
23487                                            },
23488                                        )))
23489                                    }
23490                                }
23491                            }
23492                            // MAP_FROM_ARRAYS(keys, values) -> target-specific map construction
23493                            "MAP_FROM_ARRAYS" if f.args.len() == 2 => {
23494                                let name = match target {
23495                                    DialectType::Snowflake => "OBJECT_CONSTRUCT",
23496                                    _ => "MAP",
23497                                };
23498                                Ok(Expression::Function(Box::new(Function::new(
23499                                    name.to_string(),
23500                                    f.args,
23501                                ))))
23502                            }
23503                            // STR_TO_MAP(s, pair_delim, kv_delim) -> SPLIT_TO_MAP for Presto
23504                            "STR_TO_MAP" if f.args.len() >= 1 => match target {
23505                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
23506                                    Ok(Expression::Function(Box::new(Function::new(
23507                                        "SPLIT_TO_MAP".to_string(),
23508                                        f.args,
23509                                    ))))
23510                                }
23511                                _ => Ok(Expression::Function(f)),
23512                            },
23513                            // TIME_TO_STR(x, fmt) -> Expression::TimeToStr for proper generation
23514                            "TIME_TO_STR" if f.args.len() == 2 => {
23515                                let mut args = f.args;
23516                                let this = args.remove(0);
23517                                let fmt_expr = args.remove(0);
23518                                let format = if let Expression::Literal(lit) = fmt_expr {
23519                                    if let Literal::String(s) = lit.as_ref() {
23520                                        s.clone()
23521                                    } else {
23522                                        String::new()
23523                                    }
23524                                } else {
23525                                    "%Y-%m-%d %H:%M:%S".to_string()
23526                                };
23527                                Ok(Expression::TimeToStr(Box::new(
23528                                    crate::expressions::TimeToStr {
23529                                        this: Box::new(this),
23530                                        format,
23531                                        culture: None,
23532                                        zone: None,
23533                                    },
23534                                )))
23535                            }
23536                            // STR_TO_TIME(x, fmt) -> Expression::StrToTime for proper generation
23537                            "STR_TO_TIME" if f.args.len() == 2 => {
23538                                let mut args = f.args;
23539                                let this = args.remove(0);
23540                                let fmt_expr = args.remove(0);
23541                                let format = if let Expression::Literal(lit) = fmt_expr {
23542                                    if let Literal::String(s) = lit.as_ref() {
23543                                        s.clone()
23544                                    } else {
23545                                        String::new()
23546                                    }
23547                                } else {
23548                                    "%Y-%m-%d %H:%M:%S".to_string()
23549                                };
23550                                Ok(Expression::StrToTime(Box::new(
23551                                    crate::expressions::StrToTime {
23552                                        this: Box::new(this),
23553                                        format,
23554                                        zone: None,
23555                                        safe: None,
23556                                        target_type: None,
23557                                    },
23558                                )))
23559                            }
23560                            // STR_TO_UNIX(x, fmt) -> Expression::StrToUnix for proper generation
23561                            "STR_TO_UNIX" if f.args.len() >= 1 => {
23562                                let mut args = f.args;
23563                                let this = args.remove(0);
23564                                let format = if !args.is_empty() {
23565                                    if let Expression::Literal(lit) = args.remove(0) {
23566                                        if let Literal::String(s) = lit.as_ref() {
23567                                            Some(s.clone())
23568                                        } else {
23569                                            None
23570                                        }
23571                                    } else {
23572                                        None
23573                                    }
23574                                } else {
23575                                    None
23576                                };
23577                                Ok(Expression::StrToUnix(Box::new(
23578                                    crate::expressions::StrToUnix {
23579                                        this: Some(Box::new(this)),
23580                                        format,
23581                                    },
23582                                )))
23583                            }
23584                            // TIME_TO_UNIX(x) -> Expression::TimeToUnix for proper generation
23585                            "TIME_TO_UNIX" if f.args.len() == 1 => {
23586                                let mut args = f.args;
23587                                let this = args.remove(0);
23588                                Ok(Expression::TimeToUnix(Box::new(
23589                                    crate::expressions::UnaryFunc {
23590                                        this,
23591                                        original_name: None,
23592                                        inferred_type: None,
23593                                    },
23594                                )))
23595                            }
23596                            // UNIX_TO_STR(x, fmt) -> Expression::UnixToStr for proper generation
23597                            "UNIX_TO_STR" if f.args.len() >= 1 => {
23598                                let mut args = f.args;
23599                                let this = args.remove(0);
23600                                let format = if !args.is_empty() {
23601                                    if let Expression::Literal(lit) = args.remove(0) {
23602                                        if let Literal::String(s) = lit.as_ref() {
23603                                            Some(s.clone())
23604                                        } else {
23605                                            None
23606                                        }
23607                                    } else {
23608                                        None
23609                                    }
23610                                } else {
23611                                    None
23612                                };
23613                                Ok(Expression::UnixToStr(Box::new(
23614                                    crate::expressions::UnixToStr {
23615                                        this: Box::new(this),
23616                                        format,
23617                                    },
23618                                )))
23619                            }
23620                            // UNIX_TO_TIME(x) -> Expression::UnixToTime for proper generation
23621                            "UNIX_TO_TIME" if f.args.len() == 1 => {
23622                                let mut args = f.args;
23623                                let this = args.remove(0);
23624                                Ok(Expression::UnixToTime(Box::new(
23625                                    crate::expressions::UnixToTime {
23626                                        this: Box::new(this),
23627                                        scale: None,
23628                                        zone: None,
23629                                        hours: None,
23630                                        minutes: None,
23631                                        format: None,
23632                                        target_type: None,
23633                                    },
23634                                )))
23635                            }
23636                            // TIME_STR_TO_DATE(x) -> Expression::TimeStrToDate for proper generation
23637                            "TIME_STR_TO_DATE" if f.args.len() == 1 => {
23638                                let mut args = f.args;
23639                                let this = args.remove(0);
23640                                Ok(Expression::TimeStrToDate(Box::new(
23641                                    crate::expressions::UnaryFunc {
23642                                        this,
23643                                        original_name: None,
23644                                        inferred_type: None,
23645                                    },
23646                                )))
23647                            }
23648                            // TIME_STR_TO_TIME(x) -> Expression::TimeStrToTime for proper generation
23649                            "TIME_STR_TO_TIME" if f.args.len() == 1 => {
23650                                let mut args = f.args;
23651                                let this = args.remove(0);
23652                                Ok(Expression::TimeStrToTime(Box::new(
23653                                    crate::expressions::TimeStrToTime {
23654                                        this: Box::new(this),
23655                                        zone: None,
23656                                    },
23657                                )))
23658                            }
23659                            // MONTHS_BETWEEN(end, start) -> DuckDB complex expansion
23660                            "MONTHS_BETWEEN" if f.args.len() == 2 => {
23661                                match target {
23662                                    DialectType::DuckDB => {
23663                                        let mut args = f.args;
23664                                        let end_date = args.remove(0);
23665                                        let start_date = args.remove(0);
23666                                        let cast_end = Self::ensure_cast_date(end_date);
23667                                        let cast_start = Self::ensure_cast_date(start_date);
23668                                        // 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
23669                                        let dd = Expression::Function(Box::new(Function::new(
23670                                            "DATE_DIFF".to_string(),
23671                                            vec![
23672                                                Expression::string("MONTH"),
23673                                                cast_start.clone(),
23674                                                cast_end.clone(),
23675                                            ],
23676                                        )));
23677                                        let day_end =
23678                                            Expression::Function(Box::new(Function::new(
23679                                                "DAY".to_string(),
23680                                                vec![cast_end.clone()],
23681                                            )));
23682                                        let day_start =
23683                                            Expression::Function(Box::new(Function::new(
23684                                                "DAY".to_string(),
23685                                                vec![cast_start.clone()],
23686                                            )));
23687                                        let last_day_end =
23688                                            Expression::Function(Box::new(Function::new(
23689                                                "LAST_DAY".to_string(),
23690                                                vec![cast_end.clone()],
23691                                            )));
23692                                        let last_day_start =
23693                                            Expression::Function(Box::new(Function::new(
23694                                                "LAST_DAY".to_string(),
23695                                                vec![cast_start.clone()],
23696                                            )));
23697                                        let day_last_end = Expression::Function(Box::new(
23698                                            Function::new("DAY".to_string(), vec![last_day_end]),
23699                                        ));
23700                                        let day_last_start = Expression::Function(Box::new(
23701                                            Function::new("DAY".to_string(), vec![last_day_start]),
23702                                        ));
23703                                        let cond1 = Expression::Eq(Box::new(BinaryOp::new(
23704                                            day_end.clone(),
23705                                            day_last_end,
23706                                        )));
23707                                        let cond2 = Expression::Eq(Box::new(BinaryOp::new(
23708                                            day_start.clone(),
23709                                            day_last_start,
23710                                        )));
23711                                        let both_cond =
23712                                            Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
23713                                        let day_diff = Expression::Sub(Box::new(BinaryOp::new(
23714                                            day_end, day_start,
23715                                        )));
23716                                        let day_diff_paren = Expression::Paren(Box::new(
23717                                            crate::expressions::Paren {
23718                                                this: day_diff,
23719                                                trailing_comments: Vec::new(),
23720                                            },
23721                                        ));
23722                                        let frac = Expression::Div(Box::new(BinaryOp::new(
23723                                            day_diff_paren,
23724                                            Expression::Literal(Box::new(Literal::Number(
23725                                                "31.0".to_string(),
23726                                            ))),
23727                                        )));
23728                                        let case_expr = Expression::Case(Box::new(Case {
23729                                            operand: None,
23730                                            whens: vec![(both_cond, Expression::number(0))],
23731                                            else_: Some(frac),
23732                                            comments: Vec::new(),
23733                                            inferred_type: None,
23734                                        }));
23735                                        Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
23736                                    }
23737                                    DialectType::Snowflake | DialectType::Redshift => {
23738                                        let mut args = f.args;
23739                                        let end_date = args.remove(0);
23740                                        let start_date = args.remove(0);
23741                                        let unit = Expression::Identifier(Identifier::new("MONTH"));
23742                                        Ok(Expression::Function(Box::new(Function::new(
23743                                            "DATEDIFF".to_string(),
23744                                            vec![unit, start_date, end_date],
23745                                        ))))
23746                                    }
23747                                    DialectType::Presto
23748                                    | DialectType::Trino
23749                                    | DialectType::Athena => {
23750                                        let mut args = f.args;
23751                                        let end_date = args.remove(0);
23752                                        let start_date = args.remove(0);
23753                                        Ok(Expression::Function(Box::new(Function::new(
23754                                            "DATE_DIFF".to_string(),
23755                                            vec![Expression::string("MONTH"), start_date, end_date],
23756                                        ))))
23757                                    }
23758                                    _ => Ok(Expression::Function(f)),
23759                                }
23760                            }
23761                            // MONTHS_BETWEEN(end, start, roundOff) - 3-arg form (Spark-specific)
23762                            // Drop the roundOff arg for non-Spark targets, keep it for Spark
23763                            "MONTHS_BETWEEN" if f.args.len() == 3 => {
23764                                match target {
23765                                    DialectType::Spark | DialectType::Databricks => {
23766                                        Ok(Expression::Function(f))
23767                                    }
23768                                    _ => {
23769                                        // Drop the 3rd arg and delegate to the 2-arg logic
23770                                        let mut args = f.args;
23771                                        let end_date = args.remove(0);
23772                                        let start_date = args.remove(0);
23773                                        // Re-create as 2-arg and process
23774                                        let f2 = Function::new(
23775                                            "MONTHS_BETWEEN".to_string(),
23776                                            vec![end_date, start_date],
23777                                        );
23778                                        let e2 = Expression::Function(Box::new(f2));
23779                                        Self::cross_dialect_normalize(e2, source, target)
23780                                    }
23781                                }
23782                            }
23783                            // TO_TIMESTAMP(x) with 1 arg -> CAST(x AS TIMESTAMP) for most targets
23784                            "TO_TIMESTAMP"
23785                                if f.args.len() == 1
23786                                    && matches!(
23787                                        source,
23788                                        DialectType::Spark
23789                                            | DialectType::Databricks
23790                                            | DialectType::Hive
23791                                    ) =>
23792                            {
23793                                let arg = f.args.into_iter().next().unwrap();
23794                                Ok(Expression::Cast(Box::new(Cast {
23795                                    this: arg,
23796                                    to: DataType::Timestamp {
23797                                        timezone: false,
23798                                        precision: None,
23799                                    },
23800                                    trailing_comments: vec![],
23801                                    double_colon_syntax: false,
23802                                    format: None,
23803                                    default: None,
23804                                    inferred_type: None,
23805                                })))
23806                            }
23807                            // STRING(x) -> CAST(x AS STRING) for Spark target
23808                            "STRING"
23809                                if f.args.len() == 1
23810                                    && matches!(
23811                                        source,
23812                                        DialectType::Spark | DialectType::Databricks
23813                                    ) =>
23814                            {
23815                                let arg = f.args.into_iter().next().unwrap();
23816                                let dt = match target {
23817                                    DialectType::Spark
23818                                    | DialectType::Databricks
23819                                    | DialectType::Hive => DataType::Custom {
23820                                        name: "STRING".to_string(),
23821                                    },
23822                                    _ => DataType::Text,
23823                                };
23824                                Ok(Expression::Cast(Box::new(Cast {
23825                                    this: arg,
23826                                    to: dt,
23827                                    trailing_comments: vec![],
23828                                    double_colon_syntax: false,
23829                                    format: None,
23830                                    default: None,
23831                                    inferred_type: None,
23832                                })))
23833                            }
23834                            // LOGICAL_OR(x) -> BOOL_OR(x) for Spark target
23835                            "LOGICAL_OR" if f.args.len() == 1 => {
23836                                let name = match target {
23837                                    DialectType::Spark | DialectType::Databricks => "BOOL_OR",
23838                                    _ => "LOGICAL_OR",
23839                                };
23840                                Ok(Expression::Function(Box::new(Function::new(
23841                                    name.to_string(),
23842                                    f.args,
23843                                ))))
23844                            }
23845                            // SPLIT(x, pattern) from Spark -> STR_SPLIT_REGEX for DuckDB, REGEXP_SPLIT for Presto
23846                            "SPLIT"
23847                                if f.args.len() == 2
23848                                    && matches!(
23849                                        source,
23850                                        DialectType::Spark
23851                                            | DialectType::Databricks
23852                                            | DialectType::Hive
23853                                    ) =>
23854                            {
23855                                let name = match target {
23856                                    DialectType::DuckDB => "STR_SPLIT_REGEX",
23857                                    DialectType::Presto
23858                                    | DialectType::Trino
23859                                    | DialectType::Athena => "REGEXP_SPLIT",
23860                                    DialectType::Spark
23861                                    | DialectType::Databricks
23862                                    | DialectType::Hive => "SPLIT",
23863                                    _ => "SPLIT",
23864                                };
23865                                Ok(Expression::Function(Box::new(Function::new(
23866                                    name.to_string(),
23867                                    f.args,
23868                                ))))
23869                            }
23870                            // TRY_ELEMENT_AT -> ELEMENT_AT for Presto, array[idx] for DuckDB
23871                            "TRY_ELEMENT_AT" if f.args.len() == 2 => match target {
23872                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
23873                                    Ok(Expression::Function(Box::new(Function::new(
23874                                        "ELEMENT_AT".to_string(),
23875                                        f.args,
23876                                    ))))
23877                                }
23878                                DialectType::DuckDB => {
23879                                    let mut args = f.args;
23880                                    let arr = args.remove(0);
23881                                    let idx = args.remove(0);
23882                                    Ok(Expression::Subscript(Box::new(
23883                                        crate::expressions::Subscript {
23884                                            this: arr,
23885                                            index: idx,
23886                                        },
23887                                    )))
23888                                }
23889                                _ => Ok(Expression::Function(f)),
23890                            },
23891                            // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, LIST_FILTER for DuckDB
23892                            "ARRAY_FILTER" if f.args.len() == 2 => {
23893                                let name = match target {
23894                                    DialectType::DuckDB => "LIST_FILTER",
23895                                    DialectType::StarRocks => "ARRAY_FILTER",
23896                                    _ => "FILTER",
23897                                };
23898                                Ok(Expression::Function(Box::new(Function::new(
23899                                    name.to_string(),
23900                                    f.args,
23901                                ))))
23902                            }
23903                            // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
23904                            "FILTER" if f.args.len() == 2 => {
23905                                let name = match target {
23906                                    DialectType::DuckDB => "LIST_FILTER",
23907                                    DialectType::StarRocks => "ARRAY_FILTER",
23908                                    _ => "FILTER",
23909                                };
23910                                Ok(Expression::Function(Box::new(Function::new(
23911                                    name.to_string(),
23912                                    f.args,
23913                                ))))
23914                            }
23915                            // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
23916                            "REDUCE" if f.args.len() >= 3 => {
23917                                let name = match target {
23918                                    DialectType::Spark | DialectType::Databricks => "AGGREGATE",
23919                                    _ => "REDUCE",
23920                                };
23921                                Ok(Expression::Function(Box::new(Function::new(
23922                                    name.to_string(),
23923                                    f.args,
23924                                ))))
23925                            }
23926                            // CURRENT_SCHEMA() -> dialect-specific
23927                            "CURRENT_SCHEMA" => {
23928                                match target {
23929                                    DialectType::PostgreSQL => {
23930                                        // PostgreSQL: CURRENT_SCHEMA (no parens)
23931                                        Ok(Expression::Function(Box::new(Function {
23932                                            name: "CURRENT_SCHEMA".to_string(),
23933                                            args: vec![],
23934                                            distinct: false,
23935                                            trailing_comments: vec![],
23936                                            use_bracket_syntax: false,
23937                                            no_parens: true,
23938                                            quoted: false,
23939                                            span: None,
23940                                            inferred_type: None,
23941                                        })))
23942                                    }
23943                                    DialectType::MySQL
23944                                    | DialectType::Doris
23945                                    | DialectType::StarRocks => Ok(Expression::Function(Box::new(
23946                                        Function::new("SCHEMA".to_string(), vec![]),
23947                                    ))),
23948                                    DialectType::TSQL => Ok(Expression::Function(Box::new(
23949                                        Function::new("SCHEMA_NAME".to_string(), vec![]),
23950                                    ))),
23951                                    DialectType::SQLite => Ok(Expression::Literal(Box::new(
23952                                        Literal::String("main".to_string()),
23953                                    ))),
23954                                    _ => Ok(Expression::Function(f)),
23955                                }
23956                            }
23957                            // LTRIM(str, chars) 2-arg -> TRIM(LEADING chars FROM str) for Spark/Hive/Databricks/ClickHouse
23958                            "LTRIM" if f.args.len() == 2 => match target {
23959                                DialectType::Spark
23960                                | DialectType::Hive
23961                                | DialectType::Databricks
23962                                | DialectType::ClickHouse => {
23963                                    let mut args = f.args;
23964                                    let str_expr = args.remove(0);
23965                                    let chars = args.remove(0);
23966                                    Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
23967                                        this: str_expr,
23968                                        characters: Some(chars),
23969                                        position: crate::expressions::TrimPosition::Leading,
23970                                        sql_standard_syntax: true,
23971                                        position_explicit: true,
23972                                    })))
23973                                }
23974                                _ => Ok(Expression::Function(f)),
23975                            },
23976                            // RTRIM(str, chars) 2-arg -> TRIM(TRAILING chars FROM str) for Spark/Hive/Databricks/ClickHouse
23977                            "RTRIM" if f.args.len() == 2 => match target {
23978                                DialectType::Spark
23979                                | DialectType::Hive
23980                                | DialectType::Databricks
23981                                | DialectType::ClickHouse => {
23982                                    let mut args = f.args;
23983                                    let str_expr = args.remove(0);
23984                                    let chars = args.remove(0);
23985                                    Ok(Expression::Trim(Box::new(crate::expressions::TrimFunc {
23986                                        this: str_expr,
23987                                        characters: Some(chars),
23988                                        position: crate::expressions::TrimPosition::Trailing,
23989                                        sql_standard_syntax: true,
23990                                        position_explicit: true,
23991                                    })))
23992                                }
23993                                _ => Ok(Expression::Function(f)),
23994                            },
23995                            // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
23996                            "ARRAY_REVERSE" if f.args.len() == 1 => match target {
23997                                DialectType::ClickHouse => {
23998                                    let mut new_f = *f;
23999                                    new_f.name = "arrayReverse".to_string();
24000                                    Ok(Expression::Function(Box::new(new_f)))
24001                                }
24002                                _ => Ok(Expression::Function(f)),
24003                            },
24004                            // UUID() -> NEWID() for TSQL
24005                            "UUID" if f.args.is_empty() => match target {
24006                                DialectType::TSQL | DialectType::Fabric => {
24007                                    Ok(Expression::Function(Box::new(Function::new(
24008                                        "NEWID".to_string(),
24009                                        vec![],
24010                                    ))))
24011                                }
24012                                _ => Ok(Expression::Function(f)),
24013                            },
24014                            // FARM_FINGERPRINT(x) -> farmFingerprint64(x) for ClickHouse, FARMFINGERPRINT64(x) for Redshift
24015                            "FARM_FINGERPRINT" if f.args.len() == 1 => match target {
24016                                DialectType::ClickHouse => {
24017                                    let mut new_f = *f;
24018                                    new_f.name = "farmFingerprint64".to_string();
24019                                    Ok(Expression::Function(Box::new(new_f)))
24020                                }
24021                                DialectType::Redshift => {
24022                                    let mut new_f = *f;
24023                                    new_f.name = "FARMFINGERPRINT64".to_string();
24024                                    Ok(Expression::Function(Box::new(new_f)))
24025                                }
24026                                _ => Ok(Expression::Function(f)),
24027                            },
24028                            // JSON_KEYS(x) -> JSON_OBJECT_KEYS(x) for Databricks/Spark, OBJECT_KEYS(x) for Snowflake
24029                            "JSON_KEYS" => match target {
24030                                DialectType::Databricks | DialectType::Spark => {
24031                                    let mut new_f = *f;
24032                                    new_f.name = "JSON_OBJECT_KEYS".to_string();
24033                                    Ok(Expression::Function(Box::new(new_f)))
24034                                }
24035                                DialectType::Snowflake => {
24036                                    let mut new_f = *f;
24037                                    new_f.name = "OBJECT_KEYS".to_string();
24038                                    Ok(Expression::Function(Box::new(new_f)))
24039                                }
24040                                _ => Ok(Expression::Function(f)),
24041                            },
24042                            // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake
24043                            "WEEKOFYEAR" => match target {
24044                                DialectType::Snowflake => {
24045                                    let mut new_f = *f;
24046                                    new_f.name = "WEEKISO".to_string();
24047                                    Ok(Expression::Function(Box::new(new_f)))
24048                                }
24049                                _ => Ok(Expression::Function(f)),
24050                            },
24051                            // FORMAT(fmt, args...) -> FORMAT_STRING(fmt, args...) for Databricks
24052                            "FORMAT"
24053                                if f.args.len() >= 2 && matches!(source, DialectType::Generic) =>
24054                            {
24055                                match target {
24056                                    DialectType::Databricks | DialectType::Spark => {
24057                                        let mut new_f = *f;
24058                                        new_f.name = "FORMAT_STRING".to_string();
24059                                        Ok(Expression::Function(Box::new(new_f)))
24060                                    }
24061                                    _ => Ok(Expression::Function(f)),
24062                                }
24063                            }
24064                            // CONCAT_WS from Generic is null-propagating in SQLGlot fixtures.
24065                            // Trino also requires non-separator arguments cast to VARCHAR.
24066                            "CONCAT_WS" if f.args.len() >= 2 => {
24067                                fn concat_ws_null_case(
24068                                    args: Vec<Expression>,
24069                                    else_expr: Expression,
24070                                ) -> Expression {
24071                                    let mut null_checks = args.iter().cloned().map(|arg| {
24072                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
24073                                            this: arg,
24074                                            not: false,
24075                                            postfix_form: false,
24076                                        }))
24077                                    });
24078                                    let first_null_check = null_checks
24079                                        .next()
24080                                        .expect("CONCAT_WS with >= 2 args must yield a null check");
24081                                    let null_check =
24082                                        null_checks.fold(first_null_check, |left, right| {
24083                                            Expression::Or(Box::new(BinaryOp {
24084                                                left,
24085                                                right,
24086                                                left_comments: Vec::new(),
24087                                                operator_comments: Vec::new(),
24088                                                trailing_comments: Vec::new(),
24089                                                inferred_type: None,
24090                                            }))
24091                                        });
24092                                    Expression::Case(Box::new(Case {
24093                                        operand: None,
24094                                        whens: vec![(null_check, Expression::Null(Null))],
24095                                        else_: Some(else_expr),
24096                                        comments: vec![],
24097                                        inferred_type: None,
24098                                    }))
24099                                }
24100
24101                                match target {
24102                                    DialectType::Trino
24103                                        if matches!(source, DialectType::Generic) =>
24104                                    {
24105                                        let original_args = f.args.clone();
24106                                        let mut args = f.args;
24107                                        let sep = args.remove(0);
24108                                        let cast_args: Vec<Expression> = args
24109                                            .into_iter()
24110                                            .map(|a| {
24111                                                Expression::Cast(Box::new(Cast {
24112                                                    this: a,
24113                                                    to: DataType::VarChar {
24114                                                        length: None,
24115                                                        parenthesized_length: false,
24116                                                    },
24117                                                    double_colon_syntax: false,
24118                                                    trailing_comments: Vec::new(),
24119                                                    format: None,
24120                                                    default: None,
24121                                                    inferred_type: None,
24122                                                }))
24123                                            })
24124                                            .collect();
24125                                        let mut new_args = vec![sep];
24126                                        new_args.extend(cast_args);
24127                                        let else_expr = Expression::Function(Box::new(
24128                                            Function::new("CONCAT_WS".to_string(), new_args),
24129                                        ));
24130                                        Ok(concat_ws_null_case(original_args, else_expr))
24131                                    }
24132                                    DialectType::Presto
24133                                    | DialectType::Trino
24134                                    | DialectType::Athena => {
24135                                        let mut args = f.args;
24136                                        let sep = args.remove(0);
24137                                        let cast_args: Vec<Expression> = args
24138                                            .into_iter()
24139                                            .map(|a| {
24140                                                Expression::Cast(Box::new(Cast {
24141                                                    this: a,
24142                                                    to: DataType::VarChar {
24143                                                        length: None,
24144                                                        parenthesized_length: false,
24145                                                    },
24146                                                    double_colon_syntax: false,
24147                                                    trailing_comments: Vec::new(),
24148                                                    format: None,
24149                                                    default: None,
24150                                                    inferred_type: None,
24151                                                }))
24152                                            })
24153                                            .collect();
24154                                        let mut new_args = vec![sep];
24155                                        new_args.extend(cast_args);
24156                                        Ok(Expression::Function(Box::new(Function::new(
24157                                            "CONCAT_WS".to_string(),
24158                                            new_args,
24159                                        ))))
24160                                    }
24161                                    DialectType::Spark
24162                                    | DialectType::Hive
24163                                    | DialectType::DuckDB
24164                                        if matches!(source, DialectType::Generic) =>
24165                                    {
24166                                        let args = f.args;
24167                                        let else_expr = Expression::Function(Box::new(
24168                                            Function::new("CONCAT_WS".to_string(), args.clone()),
24169                                        ));
24170                                        Ok(concat_ws_null_case(args, else_expr))
24171                                    }
24172                                    _ => Ok(Expression::Function(f)),
24173                                }
24174                            }
24175                            // ARRAY_SLICE(x, start, end) -> SLICE(x, start, end) for Presto/Trino/Databricks, arraySlice for ClickHouse
24176                            "ARRAY_SLICE" if f.args.len() >= 2 => match target {
24177                                DialectType::DuckDB
24178                                    if f.args.len() == 3
24179                                        && matches!(source, DialectType::Snowflake) =>
24180                                {
24181                                    // Snowflake ARRAY_SLICE (0-indexed, exclusive end)
24182                                    // -> DuckDB ARRAY_SLICE (1-indexed, inclusive end)
24183                                    let mut args = f.args;
24184                                    let arr = args.remove(0);
24185                                    let start = args.remove(0);
24186                                    let end = args.remove(0);
24187
24188                                    // CASE WHEN start >= 0 THEN start + 1 ELSE start END
24189                                    let adjusted_start = Expression::Case(Box::new(Case {
24190                                        operand: None,
24191                                        whens: vec![(
24192                                            Expression::Gte(Box::new(BinaryOp {
24193                                                left: start.clone(),
24194                                                right: Expression::number(0),
24195                                                left_comments: vec![],
24196                                                operator_comments: vec![],
24197                                                trailing_comments: vec![],
24198                                                inferred_type: None,
24199                                            })),
24200                                            Expression::Add(Box::new(BinaryOp {
24201                                                left: start.clone(),
24202                                                right: Expression::number(1),
24203                                                left_comments: vec![],
24204                                                operator_comments: vec![],
24205                                                trailing_comments: vec![],
24206                                                inferred_type: None,
24207                                            })),
24208                                        )],
24209                                        else_: Some(start),
24210                                        comments: vec![],
24211                                        inferred_type: None,
24212                                    }));
24213
24214                                    // CASE WHEN end < 0 THEN end - 1 ELSE end END
24215                                    let adjusted_end = Expression::Case(Box::new(Case {
24216                                        operand: None,
24217                                        whens: vec![(
24218                                            Expression::Lt(Box::new(BinaryOp {
24219                                                left: end.clone(),
24220                                                right: Expression::number(0),
24221                                                left_comments: vec![],
24222                                                operator_comments: vec![],
24223                                                trailing_comments: vec![],
24224                                                inferred_type: None,
24225                                            })),
24226                                            Expression::Sub(Box::new(BinaryOp {
24227                                                left: end.clone(),
24228                                                right: Expression::number(1),
24229                                                left_comments: vec![],
24230                                                operator_comments: vec![],
24231                                                trailing_comments: vec![],
24232                                                inferred_type: None,
24233                                            })),
24234                                        )],
24235                                        else_: Some(end),
24236                                        comments: vec![],
24237                                        inferred_type: None,
24238                                    }));
24239
24240                                    Ok(Expression::Function(Box::new(Function::new(
24241                                        "ARRAY_SLICE".to_string(),
24242                                        vec![arr, adjusted_start, adjusted_end],
24243                                    ))))
24244                                }
24245                                DialectType::Presto
24246                                | DialectType::Trino
24247                                | DialectType::Athena
24248                                | DialectType::Databricks
24249                                | DialectType::Spark => {
24250                                    let mut new_f = *f;
24251                                    new_f.name = "SLICE".to_string();
24252                                    Ok(Expression::Function(Box::new(new_f)))
24253                                }
24254                                DialectType::ClickHouse => {
24255                                    let mut new_f = *f;
24256                                    new_f.name = "arraySlice".to_string();
24257                                    Ok(Expression::Function(Box::new(new_f)))
24258                                }
24259                                _ => Ok(Expression::Function(f)),
24260                            },
24261                            // ARRAY_PREPEND(arr, x) -> LIST_PREPEND(x, arr) for DuckDB (swap args)
24262                            "ARRAY_PREPEND" if f.args.len() == 2 => match target {
24263                                DialectType::DuckDB => {
24264                                    let mut args = f.args;
24265                                    let arr = args.remove(0);
24266                                    let val = args.remove(0);
24267                                    Ok(Expression::Function(Box::new(Function::new(
24268                                        "LIST_PREPEND".to_string(),
24269                                        vec![val, arr],
24270                                    ))))
24271                                }
24272                                _ => Ok(Expression::Function(f)),
24273                            },
24274                            // ARRAY_REMOVE(arr, target) -> dialect-specific
24275                            "ARRAY_REMOVE" if f.args.len() == 2 => {
24276                                match target {
24277                                    DialectType::DuckDB => {
24278                                        let mut args = f.args;
24279                                        let arr = args.remove(0);
24280                                        let target_val = args.remove(0);
24281                                        let u_id = crate::expressions::Identifier::new("_u");
24282                                        // LIST_FILTER(arr, _u -> _u <> target)
24283                                        let lambda = Expression::Lambda(Box::new(
24284                                            crate::expressions::LambdaExpr {
24285                                                parameters: vec![u_id.clone()],
24286                                                body: Expression::Neq(Box::new(BinaryOp {
24287                                                    left: Expression::Identifier(u_id),
24288                                                    right: target_val,
24289                                                    left_comments: Vec::new(),
24290                                                    operator_comments: Vec::new(),
24291                                                    trailing_comments: Vec::new(),
24292                                                    inferred_type: None,
24293                                                })),
24294                                                colon: false,
24295                                                parameter_types: Vec::new(),
24296                                            },
24297                                        ));
24298                                        Ok(Expression::Function(Box::new(Function::new(
24299                                            "LIST_FILTER".to_string(),
24300                                            vec![arr, lambda],
24301                                        ))))
24302                                    }
24303                                    DialectType::ClickHouse => {
24304                                        let mut args = f.args;
24305                                        let arr = args.remove(0);
24306                                        let target_val = args.remove(0);
24307                                        let u_id = crate::expressions::Identifier::new("_u");
24308                                        // arrayFilter(_u -> _u <> target, arr)
24309                                        let lambda = Expression::Lambda(Box::new(
24310                                            crate::expressions::LambdaExpr {
24311                                                parameters: vec![u_id.clone()],
24312                                                body: Expression::Neq(Box::new(BinaryOp {
24313                                                    left: Expression::Identifier(u_id),
24314                                                    right: target_val,
24315                                                    left_comments: Vec::new(),
24316                                                    operator_comments: Vec::new(),
24317                                                    trailing_comments: Vec::new(),
24318                                                    inferred_type: None,
24319                                                })),
24320                                                colon: false,
24321                                                parameter_types: Vec::new(),
24322                                            },
24323                                        ));
24324                                        Ok(Expression::Function(Box::new(Function::new(
24325                                            "arrayFilter".to_string(),
24326                                            vec![lambda, arr],
24327                                        ))))
24328                                    }
24329                                    DialectType::BigQuery => {
24330                                        // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
24331                                        let mut args = f.args;
24332                                        let arr = args.remove(0);
24333                                        let target_val = args.remove(0);
24334                                        let u_id = crate::expressions::Identifier::new("_u");
24335                                        let u_col = Expression::Column(Box::new(
24336                                            crate::expressions::Column {
24337                                                name: u_id.clone(),
24338                                                table: None,
24339                                                join_mark: false,
24340                                                trailing_comments: Vec::new(),
24341                                                span: None,
24342                                                inferred_type: None,
24343                                            },
24344                                        ));
24345                                        // UNNEST(the_array) AS _u
24346                                        let unnest_expr = Expression::Unnest(Box::new(
24347                                            crate::expressions::UnnestFunc {
24348                                                this: arr,
24349                                                expressions: Vec::new(),
24350                                                with_ordinality: false,
24351                                                alias: None,
24352                                                offset_alias: None,
24353                                            },
24354                                        ));
24355                                        let aliased_unnest = Expression::Alias(Box::new(
24356                                            crate::expressions::Alias {
24357                                                this: unnest_expr,
24358                                                alias: u_id.clone(),
24359                                                column_aliases: Vec::new(),
24360                                                alias_explicit_as: false,
24361                                                alias_keyword: None,
24362                                                pre_alias_comments: Vec::new(),
24363                                                trailing_comments: Vec::new(),
24364                                                inferred_type: None,
24365                                            },
24366                                        ));
24367                                        // _u <> target
24368                                        let where_cond = Expression::Neq(Box::new(BinaryOp {
24369                                            left: u_col.clone(),
24370                                            right: target_val,
24371                                            left_comments: Vec::new(),
24372                                            operator_comments: Vec::new(),
24373                                            trailing_comments: Vec::new(),
24374                                            inferred_type: None,
24375                                        }));
24376                                        // SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target
24377                                        let subquery = Expression::Select(Box::new(
24378                                            crate::expressions::Select::new()
24379                                                .column(u_col)
24380                                                .from(aliased_unnest)
24381                                                .where_(where_cond),
24382                                        ));
24383                                        // ARRAY(subquery) -- use ArrayFunc with subquery as single element
24384                                        Ok(Expression::ArrayFunc(Box::new(
24385                                            crate::expressions::ArrayConstructor {
24386                                                expressions: vec![subquery],
24387                                                bracket_notation: false,
24388                                                use_list_keyword: false,
24389                                            },
24390                                        )))
24391                                    }
24392                                    _ => Ok(Expression::Function(f)),
24393                                }
24394                            }
24395                            // PARSE_JSON(str) -> remove for SQLite/Doris (just use the string literal)
24396                            "PARSE_JSON" if f.args.len() == 1 => {
24397                                match target {
24398                                    DialectType::SQLite
24399                                    | DialectType::Doris
24400                                    | DialectType::MySQL
24401                                    | DialectType::StarRocks => {
24402                                        // Strip PARSE_JSON, return the inner argument
24403                                        Ok(f.args.into_iter().next().unwrap())
24404                                    }
24405                                    _ => Ok(Expression::Function(f)),
24406                                }
24407                            }
24408                            // JSON_REMOVE(PARSE_JSON(str), path...) -> for SQLite strip PARSE_JSON
24409                            // This is handled by PARSE_JSON stripping above; JSON_REMOVE is passed through
24410                            "JSON_REMOVE" => Ok(Expression::Function(f)),
24411                            // JSON_SET(PARSE_JSON(str), path, PARSE_JSON(val)) -> for SQLite strip PARSE_JSON
24412                            // This is handled by PARSE_JSON stripping above; JSON_SET is passed through
24413                            "JSON_SET" => Ok(Expression::Function(f)),
24414                            // DECODE(x, search1, result1, ..., default) -> CASE WHEN
24415                            // Behavior per search value type:
24416                            //   NULL literal -> CASE WHEN x IS NULL THEN result
24417                            //   Literal (number, string, bool) -> CASE WHEN x = literal THEN result
24418                            //   Non-literal (column, expr) -> CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
24419                            "DECODE" if f.args.len() >= 3 => {
24420                                // Keep as DECODE for targets that support it natively
24421                                let keep_as_decode = matches!(
24422                                    target,
24423                                    DialectType::Oracle
24424                                        | DialectType::Snowflake
24425                                        | DialectType::Redshift
24426                                        | DialectType::Teradata
24427                                        | DialectType::Spark
24428                                        | DialectType::Databricks
24429                                );
24430                                if keep_as_decode {
24431                                    return Ok(Expression::Function(f));
24432                                }
24433
24434                                let mut args = f.args;
24435                                let this_expr = args.remove(0);
24436                                let mut pairs = Vec::new();
24437                                let mut default = None;
24438                                let mut i = 0;
24439                                while i + 1 < args.len() {
24440                                    pairs.push((args[i].clone(), args[i + 1].clone()));
24441                                    i += 2;
24442                                }
24443                                if i < args.len() {
24444                                    default = Some(args[i].clone());
24445                                }
24446                                // Helper: check if expression is a literal value
24447                                fn is_literal(e: &Expression) -> bool {
24448                                    matches!(
24449                                        e,
24450                                        Expression::Literal(_)
24451                                            | Expression::Boolean(_)
24452                                            | Expression::Neg(_)
24453                                    )
24454                                }
24455                                let whens: Vec<(Expression, Expression)> = pairs
24456                                    .into_iter()
24457                                    .map(|(search, result)| {
24458                                        if matches!(&search, Expression::Null(_)) {
24459                                            // NULL search -> IS NULL
24460                                            let condition = Expression::Is(Box::new(BinaryOp {
24461                                                left: this_expr.clone(),
24462                                                right: Expression::Null(crate::expressions::Null),
24463                                                left_comments: Vec::new(),
24464                                                operator_comments: Vec::new(),
24465                                                trailing_comments: Vec::new(),
24466                                                inferred_type: None,
24467                                            }));
24468                                            (condition, result)
24469                                        } else if is_literal(&search) {
24470                                            // Literal search -> simple equality
24471                                            let eq = Expression::Eq(Box::new(BinaryOp {
24472                                                left: this_expr.clone(),
24473                                                right: search,
24474                                                left_comments: Vec::new(),
24475                                                operator_comments: Vec::new(),
24476                                                trailing_comments: Vec::new(),
24477                                                inferred_type: None,
24478                                            }));
24479                                            (eq, result)
24480                                        } else {
24481                                            // Non-literal (column ref, expression) -> null-safe comparison
24482                                            let needs_paren = matches!(
24483                                                &search,
24484                                                Expression::Eq(_)
24485                                                    | Expression::Neq(_)
24486                                                    | Expression::Gt(_)
24487                                                    | Expression::Gte(_)
24488                                                    | Expression::Lt(_)
24489                                                    | Expression::Lte(_)
24490                                            );
24491                                            let search_for_eq = if needs_paren {
24492                                                Expression::Paren(Box::new(
24493                                                    crate::expressions::Paren {
24494                                                        this: search.clone(),
24495                                                        trailing_comments: Vec::new(),
24496                                                    },
24497                                                ))
24498                                            } else {
24499                                                search.clone()
24500                                            };
24501                                            let eq = Expression::Eq(Box::new(BinaryOp {
24502                                                left: this_expr.clone(),
24503                                                right: search_for_eq,
24504                                                left_comments: Vec::new(),
24505                                                operator_comments: Vec::new(),
24506                                                trailing_comments: Vec::new(),
24507                                                inferred_type: None,
24508                                            }));
24509                                            let search_for_null = if needs_paren {
24510                                                Expression::Paren(Box::new(
24511                                                    crate::expressions::Paren {
24512                                                        this: search.clone(),
24513                                                        trailing_comments: Vec::new(),
24514                                                    },
24515                                                ))
24516                                            } else {
24517                                                search.clone()
24518                                            };
24519                                            let x_is_null = Expression::Is(Box::new(BinaryOp {
24520                                                left: this_expr.clone(),
24521                                                right: Expression::Null(crate::expressions::Null),
24522                                                left_comments: Vec::new(),
24523                                                operator_comments: Vec::new(),
24524                                                trailing_comments: Vec::new(),
24525                                                inferred_type: None,
24526                                            }));
24527                                            let s_is_null = Expression::Is(Box::new(BinaryOp {
24528                                                left: search_for_null,
24529                                                right: Expression::Null(crate::expressions::Null),
24530                                                left_comments: Vec::new(),
24531                                                operator_comments: Vec::new(),
24532                                                trailing_comments: Vec::new(),
24533                                                inferred_type: None,
24534                                            }));
24535                                            let both_null = Expression::And(Box::new(BinaryOp {
24536                                                left: x_is_null,
24537                                                right: s_is_null,
24538                                                left_comments: Vec::new(),
24539                                                operator_comments: Vec::new(),
24540                                                trailing_comments: Vec::new(),
24541                                                inferred_type: None,
24542                                            }));
24543                                            let condition = Expression::Or(Box::new(BinaryOp {
24544                                                left: eq,
24545                                                right: Expression::Paren(Box::new(
24546                                                    crate::expressions::Paren {
24547                                                        this: both_null,
24548                                                        trailing_comments: Vec::new(),
24549                                                    },
24550                                                )),
24551                                                left_comments: Vec::new(),
24552                                                operator_comments: Vec::new(),
24553                                                trailing_comments: Vec::new(),
24554                                                inferred_type: None,
24555                                            }));
24556                                            (condition, result)
24557                                        }
24558                                    })
24559                                    .collect();
24560                                Ok(Expression::Case(Box::new(Case {
24561                                    operand: None,
24562                                    whens,
24563                                    else_: default,
24564                                    comments: Vec::new(),
24565                                    inferred_type: None,
24566                                })))
24567                            }
24568                            // LEVENSHTEIN(a, b, ...) -> dialect-specific
24569                            "LEVENSHTEIN" => {
24570                                match target {
24571                                    DialectType::BigQuery => {
24572                                        let mut new_f = *f;
24573                                        new_f.name = "EDIT_DISTANCE".to_string();
24574                                        Ok(Expression::Function(Box::new(new_f)))
24575                                    }
24576                                    DialectType::Drill => {
24577                                        let mut new_f = *f;
24578                                        new_f.name = "LEVENSHTEIN_DISTANCE".to_string();
24579                                        Ok(Expression::Function(Box::new(new_f)))
24580                                    }
24581                                    DialectType::PostgreSQL if f.args.len() == 6 => {
24582                                        // PostgreSQL: LEVENSHTEIN(src, tgt, ins, del, sub, max_d) -> LEVENSHTEIN_LESS_EQUAL
24583                                        // 2 args: basic, 5 args: with costs, 6 args: with costs + max_distance
24584                                        let mut new_f = *f;
24585                                        new_f.name = "LEVENSHTEIN_LESS_EQUAL".to_string();
24586                                        Ok(Expression::Function(Box::new(new_f)))
24587                                    }
24588                                    _ => Ok(Expression::Function(f)),
24589                                }
24590                            }
24591                            // ARRAY_MAX(x) -> arrayMax(x) for ClickHouse, LIST_MAX(x) for DuckDB
24592                            "ARRAY_MAX" => {
24593                                let name = match target {
24594                                    DialectType::ClickHouse => "arrayMax",
24595                                    DialectType::DuckDB => "LIST_MAX",
24596                                    _ => "ARRAY_MAX",
24597                                };
24598                                let mut new_f = *f;
24599                                new_f.name = name.to_string();
24600                                Ok(Expression::Function(Box::new(new_f)))
24601                            }
24602                            // ARRAY_MIN(x) -> arrayMin(x) for ClickHouse, LIST_MIN(x) for DuckDB
24603                            "ARRAY_MIN" => {
24604                                let name = match target {
24605                                    DialectType::ClickHouse => "arrayMin",
24606                                    DialectType::DuckDB => "LIST_MIN",
24607                                    _ => "ARRAY_MIN",
24608                                };
24609                                let mut new_f = *f;
24610                                new_f.name = name.to_string();
24611                                Ok(Expression::Function(Box::new(new_f)))
24612                            }
24613                            // JAROWINKLER_SIMILARITY(a, b) -> jaroWinklerSimilarity(UPPER(a), UPPER(b)) for ClickHouse
24614                            // -> JARO_WINKLER_SIMILARITY(UPPER(a), UPPER(b)) for DuckDB
24615                            "JAROWINKLER_SIMILARITY" if f.args.len() == 2 => {
24616                                let mut args = f.args;
24617                                let b = args.pop().unwrap();
24618                                let a = args.pop().unwrap();
24619                                match target {
24620                                    DialectType::ClickHouse => {
24621                                        let upper_a = Expression::Upper(Box::new(
24622                                            crate::expressions::UnaryFunc::new(a),
24623                                        ));
24624                                        let upper_b = Expression::Upper(Box::new(
24625                                            crate::expressions::UnaryFunc::new(b),
24626                                        ));
24627                                        Ok(Expression::Function(Box::new(Function::new(
24628                                            "jaroWinklerSimilarity".to_string(),
24629                                            vec![upper_a, upper_b],
24630                                        ))))
24631                                    }
24632                                    DialectType::DuckDB => {
24633                                        let upper_a = Expression::Upper(Box::new(
24634                                            crate::expressions::UnaryFunc::new(a),
24635                                        ));
24636                                        let upper_b = Expression::Upper(Box::new(
24637                                            crate::expressions::UnaryFunc::new(b),
24638                                        ));
24639                                        let score = Expression::Function(Box::new(Function::new(
24640                                            "JARO_WINKLER_SIMILARITY".to_string(),
24641                                            vec![upper_a, upper_b],
24642                                        )));
24643                                        let scaled = Expression::Mul(Box::new(BinaryOp {
24644                                            left: score,
24645                                            right: Expression::number(100),
24646                                            left_comments: Vec::new(),
24647                                            operator_comments: Vec::new(),
24648                                            trailing_comments: Vec::new(),
24649                                            inferred_type: None,
24650                                        }));
24651                                        Ok(Expression::Cast(Box::new(Cast {
24652                                            this: scaled,
24653                                            to: DataType::Int {
24654                                                length: None,
24655                                                integer_spelling: false,
24656                                            },
24657                                            trailing_comments: Vec::new(),
24658                                            double_colon_syntax: false,
24659                                            format: None,
24660                                            default: None,
24661                                            inferred_type: None,
24662                                        })))
24663                                    }
24664                                    _ => Ok(Expression::Function(Box::new(Function::new(
24665                                        "JAROWINKLER_SIMILARITY".to_string(),
24666                                        vec![a, b],
24667                                    )))),
24668                                }
24669                            }
24670                            // CURRENT_SCHEMAS(x) -> CURRENT_SCHEMAS() for Snowflake (drop arg)
24671                            "CURRENT_SCHEMAS" => match target {
24672                                DialectType::Snowflake => Ok(Expression::Function(Box::new(
24673                                    Function::new("CURRENT_SCHEMAS".to_string(), vec![]),
24674                                ))),
24675                                _ => Ok(Expression::Function(f)),
24676                            },
24677                            // TRUNC/TRUNCATE (numeric) -> dialect-specific
24678                            "TRUNC" | "TRUNCATE" if f.args.len() <= 2 => {
24679                                match target {
24680                                    DialectType::TSQL | DialectType::Fabric => {
24681                                        // ROUND(x, decimals, 1) - the 1 flag means truncation
24682                                        let mut args = f.args;
24683                                        let this = if args.is_empty() {
24684                                            return Ok(Expression::Function(Box::new(
24685                                                Function::new("TRUNC".to_string(), args),
24686                                            )));
24687                                        } else {
24688                                            args.remove(0)
24689                                        };
24690                                        let decimals = if args.is_empty() {
24691                                            Expression::Literal(Box::new(Literal::Number(
24692                                                "0".to_string(),
24693                                            )))
24694                                        } else {
24695                                            args.remove(0)
24696                                        };
24697                                        Ok(Expression::Function(Box::new(Function::new(
24698                                            "ROUND".to_string(),
24699                                            vec![
24700                                                this,
24701                                                decimals,
24702                                                Expression::Literal(Box::new(Literal::Number(
24703                                                    "1".to_string(),
24704                                                ))),
24705                                            ],
24706                                        ))))
24707                                    }
24708                                    DialectType::Presto
24709                                    | DialectType::Trino
24710                                    | DialectType::Athena => {
24711                                        // TRUNCATE(x, decimals)
24712                                        let mut new_f = *f;
24713                                        new_f.name = "TRUNCATE".to_string();
24714                                        Ok(Expression::Function(Box::new(new_f)))
24715                                    }
24716                                    DialectType::MySQL
24717                                    | DialectType::SingleStore
24718                                    | DialectType::TiDB => {
24719                                        // TRUNCATE(x, decimals)
24720                                        let mut new_f = *f;
24721                                        new_f.name = "TRUNCATE".to_string();
24722                                        Ok(Expression::Function(Box::new(new_f)))
24723                                    }
24724                                    DialectType::DuckDB => {
24725                                        // DuckDB supports TRUNC(x, decimals) — preserve both args
24726                                        let mut args = f.args;
24727                                        // Snowflake fractions_supported: wrap non-INT decimals in CAST(... AS INT)
24728                                        if args.len() == 2
24729                                            && matches!(source, DialectType::Snowflake)
24730                                        {
24731                                            let decimals = args.remove(1);
24732                                            let is_int = matches!(&decimals, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)))
24733                                                || matches!(&decimals, Expression::Cast(c) if matches!(c.to, DataType::Int { .. } | DataType::SmallInt { .. } | DataType::BigInt { .. } | DataType::TinyInt { .. }));
24734                                            let wrapped = if !is_int {
24735                                                Expression::Cast(Box::new(
24736                                                    crate::expressions::Cast {
24737                                                        this: decimals,
24738                                                        to: DataType::Int {
24739                                                            length: None,
24740                                                            integer_spelling: false,
24741                                                        },
24742                                                        double_colon_syntax: false,
24743                                                        trailing_comments: Vec::new(),
24744                                                        format: None,
24745                                                        default: None,
24746                                                        inferred_type: None,
24747                                                    },
24748                                                ))
24749                                            } else {
24750                                                decimals
24751                                            };
24752                                            args.push(wrapped);
24753                                        }
24754                                        Ok(Expression::Function(Box::new(Function::new(
24755                                            "TRUNC".to_string(),
24756                                            args,
24757                                        ))))
24758                                    }
24759                                    DialectType::ClickHouse => {
24760                                        // trunc(x, decimals) - lowercase
24761                                        let mut new_f = *f;
24762                                        new_f.name = "trunc".to_string();
24763                                        Ok(Expression::Function(Box::new(new_f)))
24764                                    }
24765                                    DialectType::Spark | DialectType::Databricks => {
24766                                        // Spark: TRUNC is date-only; numeric TRUNC → CAST(x AS BIGINT)
24767                                        let this = f.args.into_iter().next().unwrap_or(
24768                                            Expression::Literal(Box::new(Literal::Number(
24769                                                "0".to_string(),
24770                                            ))),
24771                                        );
24772                                        Ok(Expression::Cast(Box::new(crate::expressions::Cast {
24773                                            this,
24774                                            to: crate::expressions::DataType::BigInt {
24775                                                length: None,
24776                                            },
24777                                            double_colon_syntax: false,
24778                                            trailing_comments: Vec::new(),
24779                                            format: None,
24780                                            default: None,
24781                                            inferred_type: None,
24782                                        })))
24783                                    }
24784                                    _ => {
24785                                        // TRUNC(x, decimals) for PostgreSQL, Oracle, Snowflake, etc.
24786                                        let mut new_f = *f;
24787                                        new_f.name = "TRUNC".to_string();
24788                                        Ok(Expression::Function(Box::new(new_f)))
24789                                    }
24790                                }
24791                            }
24792                            // CURRENT_VERSION() -> VERSION() for most dialects
24793                            "CURRENT_VERSION" => match target {
24794                                DialectType::Snowflake
24795                                | DialectType::Databricks
24796                                | DialectType::StarRocks => Ok(Expression::Function(f)),
24797                                DialectType::SQLite => {
24798                                    let mut new_f = *f;
24799                                    new_f.name = "SQLITE_VERSION".to_string();
24800                                    Ok(Expression::Function(Box::new(new_f)))
24801                                }
24802                                _ => {
24803                                    let mut new_f = *f;
24804                                    new_f.name = "VERSION".to_string();
24805                                    Ok(Expression::Function(Box::new(new_f)))
24806                                }
24807                            },
24808                            // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
24809                            "ARRAY_REVERSE" => match target {
24810                                DialectType::ClickHouse => {
24811                                    let mut new_f = *f;
24812                                    new_f.name = "arrayReverse".to_string();
24813                                    Ok(Expression::Function(Box::new(new_f)))
24814                                }
24815                                _ => Ok(Expression::Function(f)),
24816                            },
24817                            // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
24818                            "GENERATE_DATE_ARRAY" => {
24819                                let mut args = f.args;
24820                                if matches!(target, DialectType::BigQuery) {
24821                                    // BigQuery keeps GENERATE_DATE_ARRAY; add default interval if not present
24822                                    if args.len() == 2 {
24823                                        let default_interval = Expression::Interval(Box::new(
24824                                            crate::expressions::Interval {
24825                                                this: Some(Expression::Literal(Box::new(
24826                                                    Literal::String("1".to_string()),
24827                                                ))),
24828                                                unit: Some(
24829                                                    crate::expressions::IntervalUnitSpec::Simple {
24830                                                        unit: crate::expressions::IntervalUnit::Day,
24831                                                        use_plural: false,
24832                                                    },
24833                                                ),
24834                                            },
24835                                        ));
24836                                        args.push(default_interval);
24837                                    }
24838                                    Ok(Expression::Function(Box::new(Function::new(
24839                                        "GENERATE_DATE_ARRAY".to_string(),
24840                                        args,
24841                                    ))))
24842                                } else if matches!(target, DialectType::DuckDB) {
24843                                    // DuckDB: CAST(GENERATE_SERIES(start, end, step) AS DATE[])
24844                                    let start = args.get(0).cloned();
24845                                    let end = args.get(1).cloned();
24846                                    let step = args.get(2).cloned().or_else(|| {
24847                                        Some(Expression::Interval(Box::new(
24848                                            crate::expressions::Interval {
24849                                                this: Some(Expression::Literal(Box::new(
24850                                                    Literal::String("1".to_string()),
24851                                                ))),
24852                                                unit: Some(
24853                                                    crate::expressions::IntervalUnitSpec::Simple {
24854                                                        unit: crate::expressions::IntervalUnit::Day,
24855                                                        use_plural: false,
24856                                                    },
24857                                                ),
24858                                            },
24859                                        )))
24860                                    });
24861                                    let gen_series = Expression::GenerateSeries(Box::new(
24862                                        crate::expressions::GenerateSeries {
24863                                            start: start.map(Box::new),
24864                                            end: end.map(Box::new),
24865                                            step: step.map(Box::new),
24866                                            is_end_exclusive: None,
24867                                        },
24868                                    ));
24869                                    Ok(Expression::Cast(Box::new(Cast {
24870                                        this: gen_series,
24871                                        to: DataType::Array {
24872                                            element_type: Box::new(DataType::Date),
24873                                            dimension: None,
24874                                        },
24875                                        trailing_comments: vec![],
24876                                        double_colon_syntax: false,
24877                                        format: None,
24878                                        default: None,
24879                                        inferred_type: None,
24880                                    })))
24881                                } else if matches!(
24882                                    target,
24883                                    DialectType::Presto | DialectType::Trino | DialectType::Athena
24884                                ) {
24885                                    // Presto/Trino: SEQUENCE(start, end, interval) with interval normalization
24886                                    let start = args.get(0).cloned();
24887                                    let end = args.get(1).cloned();
24888                                    let step = args.get(2).cloned().or_else(|| {
24889                                        Some(Expression::Interval(Box::new(
24890                                            crate::expressions::Interval {
24891                                                this: Some(Expression::Literal(Box::new(
24892                                                    Literal::String("1".to_string()),
24893                                                ))),
24894                                                unit: Some(
24895                                                    crate::expressions::IntervalUnitSpec::Simple {
24896                                                        unit: crate::expressions::IntervalUnit::Day,
24897                                                        use_plural: false,
24898                                                    },
24899                                                ),
24900                                            },
24901                                        )))
24902                                    });
24903                                    let gen_series = Expression::GenerateSeries(Box::new(
24904                                        crate::expressions::GenerateSeries {
24905                                            start: start.map(Box::new),
24906                                            end: end.map(Box::new),
24907                                            step: step.map(Box::new),
24908                                            is_end_exclusive: None,
24909                                        },
24910                                    ));
24911                                    Ok(gen_series)
24912                                } else if matches!(
24913                                    target,
24914                                    DialectType::Spark | DialectType::Databricks
24915                                ) {
24916                                    // Spark/Databricks: SEQUENCE(start, end, step) - keep step as-is
24917                                    let start = args.get(0).cloned();
24918                                    let end = args.get(1).cloned();
24919                                    let step = args.get(2).cloned().or_else(|| {
24920                                        Some(Expression::Interval(Box::new(
24921                                            crate::expressions::Interval {
24922                                                this: Some(Expression::Literal(Box::new(
24923                                                    Literal::String("1".to_string()),
24924                                                ))),
24925                                                unit: Some(
24926                                                    crate::expressions::IntervalUnitSpec::Simple {
24927                                                        unit: crate::expressions::IntervalUnit::Day,
24928                                                        use_plural: false,
24929                                                    },
24930                                                ),
24931                                            },
24932                                        )))
24933                                    });
24934                                    let gen_series = Expression::GenerateSeries(Box::new(
24935                                        crate::expressions::GenerateSeries {
24936                                            start: start.map(Box::new),
24937                                            end: end.map(Box::new),
24938                                            step: step.map(Box::new),
24939                                            is_end_exclusive: None,
24940                                        },
24941                                    ));
24942                                    Ok(gen_series)
24943                                } else if matches!(target, DialectType::Snowflake) {
24944                                    // Snowflake: keep as GENERATE_DATE_ARRAY for later transform
24945                                    if args.len() == 2 {
24946                                        let default_interval = Expression::Interval(Box::new(
24947                                            crate::expressions::Interval {
24948                                                this: Some(Expression::Literal(Box::new(
24949                                                    Literal::String("1".to_string()),
24950                                                ))),
24951                                                unit: Some(
24952                                                    crate::expressions::IntervalUnitSpec::Simple {
24953                                                        unit: crate::expressions::IntervalUnit::Day,
24954                                                        use_plural: false,
24955                                                    },
24956                                                ),
24957                                            },
24958                                        ));
24959                                        args.push(default_interval);
24960                                    }
24961                                    Ok(Expression::Function(Box::new(Function::new(
24962                                        "GENERATE_DATE_ARRAY".to_string(),
24963                                        args,
24964                                    ))))
24965                                } else if matches!(
24966                                    target,
24967                                    DialectType::MySQL
24968                                        | DialectType::TSQL
24969                                        | DialectType::Fabric
24970                                        | DialectType::Redshift
24971                                ) {
24972                                    // MySQL/TSQL/Redshift: keep as GENERATE_DATE_ARRAY for the preprocess
24973                                    // step (unnest_generate_date_array_using_recursive_cte) to convert to CTE
24974                                    Ok(Expression::Function(Box::new(Function::new(
24975                                        "GENERATE_DATE_ARRAY".to_string(),
24976                                        args,
24977                                    ))))
24978                                } else {
24979                                    // PostgreSQL/others: convert to GenerateSeries
24980                                    let start = args.get(0).cloned();
24981                                    let end = args.get(1).cloned();
24982                                    let step = args.get(2).cloned().or_else(|| {
24983                                        Some(Expression::Interval(Box::new(
24984                                            crate::expressions::Interval {
24985                                                this: Some(Expression::Literal(Box::new(
24986                                                    Literal::String("1".to_string()),
24987                                                ))),
24988                                                unit: Some(
24989                                                    crate::expressions::IntervalUnitSpec::Simple {
24990                                                        unit: crate::expressions::IntervalUnit::Day,
24991                                                        use_plural: false,
24992                                                    },
24993                                                ),
24994                                            },
24995                                        )))
24996                                    });
24997                                    Ok(Expression::GenerateSeries(Box::new(
24998                                        crate::expressions::GenerateSeries {
24999                                            start: start.map(Box::new),
25000                                            end: end.map(Box::new),
25001                                            step: step.map(Box::new),
25002                                            is_end_exclusive: None,
25003                                        },
25004                                    )))
25005                                }
25006                            }
25007                            // ARRAYS_OVERLAP(arr1, arr2) from Snowflake -> DuckDB:
25008                            // (arr1 && arr2) OR (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
25009                            "ARRAYS_OVERLAP"
25010                                if f.args.len() == 2
25011                                    && matches!(source, DialectType::Snowflake)
25012                                    && matches!(target, DialectType::DuckDB) =>
25013                            {
25014                                let mut args = f.args;
25015                                let arr1 = args.remove(0);
25016                                let arr2 = args.remove(0);
25017
25018                                // (arr1 && arr2)
25019                                let overlap = Expression::Paren(Box::new(Paren {
25020                                    this: Expression::ArrayOverlaps(Box::new(BinaryOp {
25021                                        left: arr1.clone(),
25022                                        right: arr2.clone(),
25023                                        left_comments: vec![],
25024                                        operator_comments: vec![],
25025                                        trailing_comments: vec![],
25026                                        inferred_type: None,
25027                                    })),
25028                                    trailing_comments: vec![],
25029                                }));
25030
25031                                // ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1)
25032                                let arr1_has_null = Expression::Neq(Box::new(BinaryOp {
25033                                    left: Expression::Function(Box::new(Function::new(
25034                                        "ARRAY_LENGTH".to_string(),
25035                                        vec![arr1.clone()],
25036                                    ))),
25037                                    right: Expression::Function(Box::new(Function::new(
25038                                        "LIST_COUNT".to_string(),
25039                                        vec![arr1],
25040                                    ))),
25041                                    left_comments: vec![],
25042                                    operator_comments: vec![],
25043                                    trailing_comments: vec![],
25044                                    inferred_type: None,
25045                                }));
25046
25047                                // ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2)
25048                                let arr2_has_null = Expression::Neq(Box::new(BinaryOp {
25049                                    left: Expression::Function(Box::new(Function::new(
25050                                        "ARRAY_LENGTH".to_string(),
25051                                        vec![arr2.clone()],
25052                                    ))),
25053                                    right: Expression::Function(Box::new(Function::new(
25054                                        "LIST_COUNT".to_string(),
25055                                        vec![arr2],
25056                                    ))),
25057                                    left_comments: vec![],
25058                                    operator_comments: vec![],
25059                                    trailing_comments: vec![],
25060                                    inferred_type: None,
25061                                }));
25062
25063                                // (ARRAY_LENGTH(arr1) <> LIST_COUNT(arr1) AND ARRAY_LENGTH(arr2) <> LIST_COUNT(arr2))
25064                                let null_check = Expression::Paren(Box::new(Paren {
25065                                    this: Expression::And(Box::new(BinaryOp {
25066                                        left: arr1_has_null,
25067                                        right: arr2_has_null,
25068                                        left_comments: vec![],
25069                                        operator_comments: vec![],
25070                                        trailing_comments: vec![],
25071                                        inferred_type: None,
25072                                    })),
25073                                    trailing_comments: vec![],
25074                                }));
25075
25076                                // (arr1 && arr2) OR (null_check)
25077                                Ok(Expression::Or(Box::new(BinaryOp {
25078                                    left: overlap,
25079                                    right: null_check,
25080                                    left_comments: vec![],
25081                                    operator_comments: vec![],
25082                                    trailing_comments: vec![],
25083                                    inferred_type: None,
25084                                })))
25085                            }
25086                            // ARRAY_INTERSECTION([1, 2], [2, 3]) from Snowflake -> DuckDB:
25087                            // Bag semantics using LIST_TRANSFORM/LIST_FILTER with GENERATE_SERIES
25088                            "ARRAY_INTERSECTION"
25089                                if f.args.len() == 2
25090                                    && matches!(source, DialectType::Snowflake)
25091                                    && matches!(target, DialectType::DuckDB) =>
25092                            {
25093                                let mut args = f.args;
25094                                let arr1 = args.remove(0);
25095                                let arr2 = args.remove(0);
25096
25097                                // Build: arr1 IS NULL
25098                                let arr1_is_null = Expression::IsNull(Box::new(IsNull {
25099                                    this: arr1.clone(),
25100                                    not: false,
25101                                    postfix_form: false,
25102                                }));
25103                                let arr2_is_null = Expression::IsNull(Box::new(IsNull {
25104                                    this: arr2.clone(),
25105                                    not: false,
25106                                    postfix_form: false,
25107                                }));
25108                                let null_check = Expression::Or(Box::new(BinaryOp {
25109                                    left: arr1_is_null,
25110                                    right: arr2_is_null,
25111                                    left_comments: vec![],
25112                                    operator_comments: vec![],
25113                                    trailing_comments: vec![],
25114                                    inferred_type: None,
25115                                }));
25116
25117                                // GENERATE_SERIES(1, LENGTH(arr1))
25118                                let gen_series = Expression::Function(Box::new(Function::new(
25119                                    "GENERATE_SERIES".to_string(),
25120                                    vec![
25121                                        Expression::number(1),
25122                                        Expression::Function(Box::new(Function::new(
25123                                            "LENGTH".to_string(),
25124                                            vec![arr1.clone()],
25125                                        ))),
25126                                    ],
25127                                )));
25128
25129                                // LIST_ZIP(arr1, GENERATE_SERIES(1, LENGTH(arr1)))
25130                                let list_zip = Expression::Function(Box::new(Function::new(
25131                                    "LIST_ZIP".to_string(),
25132                                    vec![arr1.clone(), gen_series],
25133                                )));
25134
25135                                // pair[1] and pair[2]
25136                                let pair_col = Expression::column("pair");
25137                                let pair_1 = Expression::Subscript(Box::new(
25138                                    crate::expressions::Subscript {
25139                                        this: pair_col.clone(),
25140                                        index: Expression::number(1),
25141                                    },
25142                                ));
25143                                let pair_2 = Expression::Subscript(Box::new(
25144                                    crate::expressions::Subscript {
25145                                        this: pair_col.clone(),
25146                                        index: Expression::number(2),
25147                                    },
25148                                ));
25149
25150                                // arr1[1:pair[2]]
25151                                let arr1_slice = Expression::ArraySlice(Box::new(
25152                                    crate::expressions::ArraySlice {
25153                                        this: arr1.clone(),
25154                                        start: Some(Expression::number(1)),
25155                                        end: Some(pair_2),
25156                                    },
25157                                ));
25158
25159                                // e IS NOT DISTINCT FROM pair[1]
25160                                let e_col = Expression::column("e");
25161                                let is_not_distinct = Expression::NullSafeEq(Box::new(BinaryOp {
25162                                    left: e_col.clone(),
25163                                    right: pair_1.clone(),
25164                                    left_comments: vec![],
25165                                    operator_comments: vec![],
25166                                    trailing_comments: vec![],
25167                                    inferred_type: None,
25168                                }));
25169
25170                                // e -> e IS NOT DISTINCT FROM pair[1]
25171                                let inner_lambda1 =
25172                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25173                                        parameters: vec![crate::expressions::Identifier::new("e")],
25174                                        body: is_not_distinct,
25175                                        colon: false,
25176                                        parameter_types: vec![],
25177                                    }));
25178
25179                                // LIST_FILTER(arr1[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
25180                                let inner_filter1 = Expression::Function(Box::new(Function::new(
25181                                    "LIST_FILTER".to_string(),
25182                                    vec![arr1_slice, inner_lambda1],
25183                                )));
25184
25185                                // LENGTH(LIST_FILTER(arr1[1:pair[2]], ...))
25186                                let len1 = Expression::Function(Box::new(Function::new(
25187                                    "LENGTH".to_string(),
25188                                    vec![inner_filter1],
25189                                )));
25190
25191                                // e -> e IS NOT DISTINCT FROM pair[1]
25192                                let inner_lambda2 =
25193                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25194                                        parameters: vec![crate::expressions::Identifier::new("e")],
25195                                        body: Expression::NullSafeEq(Box::new(BinaryOp {
25196                                            left: e_col,
25197                                            right: pair_1.clone(),
25198                                            left_comments: vec![],
25199                                            operator_comments: vec![],
25200                                            trailing_comments: vec![],
25201                                            inferred_type: None,
25202                                        })),
25203                                        colon: false,
25204                                        parameter_types: vec![],
25205                                    }));
25206
25207                                // LIST_FILTER(arr2, e -> e IS NOT DISTINCT FROM pair[1])
25208                                let inner_filter2 = Expression::Function(Box::new(Function::new(
25209                                    "LIST_FILTER".to_string(),
25210                                    vec![arr2.clone(), inner_lambda2],
25211                                )));
25212
25213                                // LENGTH(LIST_FILTER(arr2, ...))
25214                                let len2 = Expression::Function(Box::new(Function::new(
25215                                    "LENGTH".to_string(),
25216                                    vec![inner_filter2],
25217                                )));
25218
25219                                // LENGTH(...) <= LENGTH(...)
25220                                let cond = Expression::Paren(Box::new(Paren {
25221                                    this: Expression::Lte(Box::new(BinaryOp {
25222                                        left: len1,
25223                                        right: len2,
25224                                        left_comments: vec![],
25225                                        operator_comments: vec![],
25226                                        trailing_comments: vec![],
25227                                        inferred_type: None,
25228                                    })),
25229                                    trailing_comments: vec![],
25230                                }));
25231
25232                                // pair -> (condition)
25233                                let filter_lambda =
25234                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25235                                        parameters: vec![crate::expressions::Identifier::new(
25236                                            "pair",
25237                                        )],
25238                                        body: cond,
25239                                        colon: false,
25240                                        parameter_types: vec![],
25241                                    }));
25242
25243                                // LIST_FILTER(LIST_ZIP(...), pair -> ...)
25244                                let outer_filter = Expression::Function(Box::new(Function::new(
25245                                    "LIST_FILTER".to_string(),
25246                                    vec![list_zip, filter_lambda],
25247                                )));
25248
25249                                // pair -> pair[1]
25250                                let transform_lambda =
25251                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
25252                                        parameters: vec![crate::expressions::Identifier::new(
25253                                            "pair",
25254                                        )],
25255                                        body: pair_1,
25256                                        colon: false,
25257                                        parameter_types: vec![],
25258                                    }));
25259
25260                                // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
25261                                let list_transform = Expression::Function(Box::new(Function::new(
25262                                    "LIST_TRANSFORM".to_string(),
25263                                    vec![outer_filter, transform_lambda],
25264                                )));
25265
25266                                // CASE WHEN arr1 IS NULL OR arr2 IS NULL THEN NULL
25267                                // ELSE LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
25268                                // END
25269                                Ok(Expression::Case(Box::new(Case {
25270                                    operand: None,
25271                                    whens: vec![(null_check, Expression::Null(Null))],
25272                                    else_: Some(list_transform),
25273                                    comments: vec![],
25274                                    inferred_type: None,
25275                                })))
25276                            }
25277                            // ARRAY_CONSTRUCT(args) -> Expression::Array for all targets
25278                            "ARRAY_CONSTRUCT" => {
25279                                if matches!(target, DialectType::Snowflake) {
25280                                    Ok(Expression::Function(f))
25281                                } else {
25282                                    Ok(Expression::Array(Box::new(crate::expressions::Array {
25283                                        expressions: f.args,
25284                                    })))
25285                                }
25286                            }
25287                            // ARRAY(args) function -> Expression::Array for DuckDB/Snowflake/Presto/Trino/Athena
25288                            "ARRAY"
25289                                if !f.args.iter().any(|a| {
25290                                    matches!(a, Expression::Select(_) | Expression::Subquery(_))
25291                                }) =>
25292                            {
25293                                match target {
25294                                    DialectType::DuckDB
25295                                    | DialectType::Snowflake
25296                                    | DialectType::Presto
25297                                    | DialectType::Trino
25298                                    | DialectType::Athena => {
25299                                        Ok(Expression::Array(Box::new(crate::expressions::Array {
25300                                            expressions: f.args,
25301                                        })))
25302                                    }
25303                                    _ => Ok(Expression::Function(f)),
25304                                }
25305                            }
25306                            _ => Ok(Expression::Function(f)),
25307                        }
25308                    } else if let Expression::AggregateFunction(mut af) = e {
25309                        let name = af.name.to_ascii_uppercase();
25310                        match name.as_str() {
25311                            "ARBITRARY" if af.args.len() == 1 => {
25312                                let arg = af.args.into_iter().next().unwrap();
25313                                Ok(convert_arbitrary(arg, target))
25314                            }
25315                            "JSON_ARRAYAGG" => {
25316                                match target {
25317                                    DialectType::PostgreSQL => {
25318                                        af.name = "JSON_AGG".to_string();
25319                                        // Add NULLS FIRST to ORDER BY items for PostgreSQL
25320                                        for ordered in af.order_by.iter_mut() {
25321                                            if ordered.nulls_first.is_none() {
25322                                                ordered.nulls_first = Some(true);
25323                                            }
25324                                        }
25325                                        Ok(Expression::AggregateFunction(af))
25326                                    }
25327                                    _ => Ok(Expression::AggregateFunction(af)),
25328                                }
25329                            }
25330                            _ => Ok(Expression::AggregateFunction(af)),
25331                        }
25332                    } else if let Expression::JSONArrayAgg(ja) = e {
25333                        // JSONArrayAgg -> JSON_AGG for PostgreSQL, JSON_ARRAYAGG for others
25334                        match target {
25335                            DialectType::PostgreSQL => {
25336                                let mut order_by = Vec::new();
25337                                if let Some(order_expr) = ja.order {
25338                                    if let Expression::OrderBy(ob) = *order_expr {
25339                                        for mut ordered in ob.expressions {
25340                                            if ordered.nulls_first.is_none() {
25341                                                ordered.nulls_first = Some(true);
25342                                            }
25343                                            order_by.push(ordered);
25344                                        }
25345                                    }
25346                                }
25347                                Ok(Expression::AggregateFunction(Box::new(
25348                                    crate::expressions::AggregateFunction {
25349                                        name: "JSON_AGG".to_string(),
25350                                        args: vec![*ja.this],
25351                                        distinct: false,
25352                                        filter: None,
25353                                        order_by,
25354                                        limit: None,
25355                                        ignore_nulls: None,
25356                                        inferred_type: None,
25357                                    },
25358                                )))
25359                            }
25360                            _ => Ok(Expression::JSONArrayAgg(ja)),
25361                        }
25362                    } else if let Expression::JSONArray(ja) = e {
25363                        match target {
25364                            DialectType::Snowflake
25365                                if ja.null_handling.is_none()
25366                                    && ja.return_type.is_none()
25367                                    && ja.strict.is_none() =>
25368                            {
25369                                let array_construct = Expression::ArrayFunc(Box::new(
25370                                    crate::expressions::ArrayConstructor {
25371                                        expressions: ja.expressions,
25372                                        bracket_notation: false,
25373                                        use_list_keyword: false,
25374                                    },
25375                                ));
25376                                Ok(Expression::Function(Box::new(Function::new(
25377                                    "TO_VARIANT".to_string(),
25378                                    vec![array_construct],
25379                                ))))
25380                            }
25381                            _ => Ok(Expression::JSONArray(ja)),
25382                        }
25383                    } else if let Expression::JsonArray(f) = e {
25384                        match target {
25385                            DialectType::Snowflake => {
25386                                let array_construct = Expression::ArrayFunc(Box::new(
25387                                    crate::expressions::ArrayConstructor {
25388                                        expressions: f.expressions,
25389                                        bracket_notation: false,
25390                                        use_list_keyword: false,
25391                                    },
25392                                ));
25393                                Ok(Expression::Function(Box::new(Function::new(
25394                                    "TO_VARIANT".to_string(),
25395                                    vec![array_construct],
25396                                ))))
25397                            }
25398                            _ => Ok(Expression::JsonArray(f)),
25399                        }
25400                    } else if let Expression::CombinedParameterizedAgg(cpa) = e {
25401                        let function_name = match cpa.this.as_ref() {
25402                            Expression::Identifier(ident) => Some(ident.name.as_str()),
25403                            _ => None,
25404                        };
25405                        match function_name {
25406                            Some(name)
25407                                if name.eq_ignore_ascii_case("groupConcat")
25408                                    && cpa.expressions.len() == 1 =>
25409                            {
25410                                match target {
25411                                    DialectType::MySQL | DialectType::SingleStore => {
25412                                        let this = cpa.expressions[0].clone();
25413                                        let separator = cpa.params.first().cloned();
25414                                        Ok(Expression::GroupConcat(Box::new(
25415                                            crate::expressions::GroupConcatFunc {
25416                                                this,
25417                                                separator,
25418                                                order_by: None,
25419                                                distinct: false,
25420                                                filter: None,
25421                                                limit: None,
25422                                                inferred_type: None,
25423                                            },
25424                                        )))
25425                                    }
25426                                    DialectType::DuckDB => Ok(Expression::ListAgg(Box::new({
25427                                        let this = cpa.expressions[0].clone();
25428                                        let separator = cpa.params.first().cloned();
25429                                        crate::expressions::ListAggFunc {
25430                                            this,
25431                                            separator,
25432                                            on_overflow: None,
25433                                            order_by: None,
25434                                            distinct: false,
25435                                            filter: None,
25436                                            inferred_type: None,
25437                                        }
25438                                    }))),
25439                                    _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
25440                                }
25441                            }
25442                            _ => Ok(Expression::CombinedParameterizedAgg(cpa)),
25443                        }
25444                    } else if let Expression::ToNumber(tn) = e {
25445                        // TO_NUMBER(x) with no format/precision/scale -> CAST(x AS DOUBLE)
25446                        let arg = *tn.this;
25447                        Ok(Expression::Cast(Box::new(crate::expressions::Cast {
25448                            this: arg,
25449                            to: crate::expressions::DataType::Double {
25450                                precision: None,
25451                                scale: None,
25452                            },
25453                            double_colon_syntax: false,
25454                            trailing_comments: Vec::new(),
25455                            format: None,
25456                            default: None,
25457                            inferred_type: None,
25458                        })))
25459                    } else {
25460                        Ok(e)
25461                    }
25462                }
25463
25464                Action::RegexpLikeToDuckDB => {
25465                    if let Expression::RegexpLike(f) = e {
25466                        let mut args = vec![f.this, f.pattern];
25467                        if let Some(flags) = f.flags {
25468                            args.push(flags);
25469                        }
25470                        Ok(Expression::Function(Box::new(Function::new(
25471                            "REGEXP_MATCHES".to_string(),
25472                            args,
25473                        ))))
25474                    } else {
25475                        Ok(e)
25476                    }
25477                }
25478                Action::RegexpLikeToTsqlPatindex => {
25479                    match e {
25480                        Expression::RegexpLike(f) => Ok(Self::build_tsql_regex_patindex_predicate(
25481                            f.this, f.pattern, false,
25482                        )),
25483                        Expression::RegexpILike(f) => Ok(
25484                            Self::build_tsql_regex_patindex_predicate(*f.this, *f.expression, true),
25485                        ),
25486                        _ => Ok(e),
25487                    }
25488                }
25489                Action::SimilarToToTsqlLike => match e {
25490                    Expression::SimilarTo(f) => {
25491                        let like = Expression::Like(Box::new(LikeOp {
25492                            left: f.this,
25493                            right: f.pattern,
25494                            escape: f.escape,
25495                            quantifier: None,
25496                            inferred_type: None,
25497                        }));
25498                        if f.not {
25499                            Ok(Expression::Not(Box::new(crate::expressions::UnaryOp::new(
25500                                like,
25501                            ))))
25502                        } else {
25503                            Ok(like)
25504                        }
25505                    }
25506                    _ => Ok(e),
25507                },
25508                Action::PostgresJsonBuildObjectToJsonObject => match e {
25509                    Expression::Function(f) => Ok(Expression::JsonObject(
25510                        Self::build_json_object_from_pairs(f.args)
25511                            .expect("action selected only for even key/value argument lists"),
25512                    )),
25513                    _ => Ok(e),
25514                },
25515                Action::PostgresJsonAggToJsonArrayAgg => match e {
25516                    Expression::Function(f) if f.args.len() == 1 => {
25517                        let mut args = f.args;
25518                        Ok(Expression::JsonArrayAgg(Box::new(
25519                            crate::expressions::JsonArrayAggFunc {
25520                                this: args.remove(0),
25521                                order_by: None,
25522                                null_handling: None,
25523                                filter: None,
25524                            },
25525                        )))
25526                    }
25527                    Expression::AggregateFunction(mut af) => {
25528                        af.name = "JSON_ARRAYAGG".to_string();
25529                        Ok(Expression::AggregateFunction(af))
25530                    }
25531                    _ => Ok(e),
25532                },
25533                Action::EpochConvert => {
25534                    if let Expression::Epoch(f) = e {
25535                        let arg = f.this;
25536                        let name = match target {
25537                            DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
25538                                "UNIX_TIMESTAMP"
25539                            }
25540                            DialectType::Presto | DialectType::Trino => "TO_UNIXTIME",
25541                            DialectType::BigQuery => "TIME_TO_UNIX",
25542                            _ => "EPOCH",
25543                        };
25544                        Ok(Expression::Function(Box::new(Function::new(
25545                            name.to_string(),
25546                            vec![arg],
25547                        ))))
25548                    } else {
25549                        Ok(e)
25550                    }
25551                }
25552                Action::EpochMsConvert => {
25553                    use crate::expressions::{BinaryOp, Cast};
25554                    if let Expression::EpochMs(f) = e {
25555                        let arg = f.this;
25556                        match target {
25557                            DialectType::Spark | DialectType::Databricks => {
25558                                Ok(Expression::Function(Box::new(Function::new(
25559                                    "TIMESTAMP_MILLIS".to_string(),
25560                                    vec![arg],
25561                                ))))
25562                            }
25563                            DialectType::BigQuery => Ok(Expression::Function(Box::new(
25564                                Function::new("TIMESTAMP_MILLIS".to_string(), vec![arg]),
25565                            ))),
25566                            DialectType::Presto | DialectType::Trino => {
25567                                // FROM_UNIXTIME(CAST(x AS DOUBLE) / POW(10, 3))
25568                                let cast_arg = Expression::Cast(Box::new(Cast {
25569                                    this: arg,
25570                                    to: DataType::Double {
25571                                        precision: None,
25572                                        scale: None,
25573                                    },
25574                                    trailing_comments: Vec::new(),
25575                                    double_colon_syntax: false,
25576                                    format: None,
25577                                    default: None,
25578                                    inferred_type: None,
25579                                }));
25580                                let div = Expression::Div(Box::new(BinaryOp::new(
25581                                    cast_arg,
25582                                    Expression::Function(Box::new(Function::new(
25583                                        "POW".to_string(),
25584                                        vec![Expression::number(10), Expression::number(3)],
25585                                    ))),
25586                                )));
25587                                Ok(Expression::Function(Box::new(Function::new(
25588                                    "FROM_UNIXTIME".to_string(),
25589                                    vec![div],
25590                                ))))
25591                            }
25592                            DialectType::MySQL => {
25593                                // FROM_UNIXTIME(x / POWER(10, 3))
25594                                let div = Expression::Div(Box::new(BinaryOp::new(
25595                                    arg,
25596                                    Expression::Function(Box::new(Function::new(
25597                                        "POWER".to_string(),
25598                                        vec![Expression::number(10), Expression::number(3)],
25599                                    ))),
25600                                )));
25601                                Ok(Expression::Function(Box::new(Function::new(
25602                                    "FROM_UNIXTIME".to_string(),
25603                                    vec![div],
25604                                ))))
25605                            }
25606                            DialectType::PostgreSQL | DialectType::Redshift => {
25607                                // TO_TIMESTAMP(CAST(x AS DOUBLE PRECISION) / POWER(10, 3))
25608                                let cast_arg = Expression::Cast(Box::new(Cast {
25609                                    this: arg,
25610                                    to: DataType::Custom {
25611                                        name: "DOUBLE PRECISION".to_string(),
25612                                    },
25613                                    trailing_comments: Vec::new(),
25614                                    double_colon_syntax: false,
25615                                    format: None,
25616                                    default: None,
25617                                    inferred_type: None,
25618                                }));
25619                                let div = Expression::Div(Box::new(BinaryOp::new(
25620                                    cast_arg,
25621                                    Expression::Function(Box::new(Function::new(
25622                                        "POWER".to_string(),
25623                                        vec![Expression::number(10), Expression::number(3)],
25624                                    ))),
25625                                )));
25626                                Ok(Expression::Function(Box::new(Function::new(
25627                                    "TO_TIMESTAMP".to_string(),
25628                                    vec![div],
25629                                ))))
25630                            }
25631                            DialectType::ClickHouse => {
25632                                // fromUnixTimestamp64Milli(CAST(x AS Nullable(Int64)))
25633                                let cast_arg = Expression::Cast(Box::new(Cast {
25634                                    this: arg,
25635                                    to: DataType::Nullable {
25636                                        inner: Box::new(DataType::BigInt { length: None }),
25637                                    },
25638                                    trailing_comments: Vec::new(),
25639                                    double_colon_syntax: false,
25640                                    format: None,
25641                                    default: None,
25642                                    inferred_type: None,
25643                                }));
25644                                Ok(Expression::Function(Box::new(Function::new(
25645                                    "fromUnixTimestamp64Milli".to_string(),
25646                                    vec![cast_arg],
25647                                ))))
25648                            }
25649                            _ => Ok(Expression::Function(Box::new(Function::new(
25650                                "EPOCH_MS".to_string(),
25651                                vec![arg],
25652                            )))),
25653                        }
25654                    } else {
25655                        Ok(e)
25656                    }
25657                }
25658                Action::TSQLTypeNormalize => {
25659                    if let Expression::DataType(dt) = e {
25660                        let new_dt = match &dt {
25661                            DataType::Custom { name } if name.eq_ignore_ascii_case("MONEY") => {
25662                                DataType::Decimal {
25663                                    precision: Some(15),
25664                                    scale: Some(4),
25665                                }
25666                            }
25667                            DataType::Custom { name }
25668                                if name.eq_ignore_ascii_case("SMALLMONEY") =>
25669                            {
25670                                DataType::Decimal {
25671                                    precision: Some(6),
25672                                    scale: Some(4),
25673                                }
25674                            }
25675                            DataType::Custom { name } if name.eq_ignore_ascii_case("DATETIME2") => {
25676                                DataType::Timestamp {
25677                                    timezone: false,
25678                                    precision: None,
25679                                }
25680                            }
25681                            DataType::Custom { name } if name.eq_ignore_ascii_case("REAL") => {
25682                                DataType::Float {
25683                                    precision: None,
25684                                    scale: None,
25685                                    real_spelling: false,
25686                                }
25687                            }
25688                            DataType::Float {
25689                                real_spelling: true,
25690                                ..
25691                            } => DataType::Float {
25692                                precision: None,
25693                                scale: None,
25694                                real_spelling: false,
25695                            },
25696                            DataType::Custom { name } if name.eq_ignore_ascii_case("IMAGE") => {
25697                                DataType::Custom {
25698                                    name: "BLOB".to_string(),
25699                                }
25700                            }
25701                            DataType::Custom { name } if name.eq_ignore_ascii_case("BIT") => {
25702                                DataType::Boolean
25703                            }
25704                            DataType::Custom { name }
25705                                if name.eq_ignore_ascii_case("ROWVERSION") =>
25706                            {
25707                                DataType::Custom {
25708                                    name: "BINARY".to_string(),
25709                                }
25710                            }
25711                            DataType::Custom { name }
25712                                if name.eq_ignore_ascii_case("UNIQUEIDENTIFIER") =>
25713                            {
25714                                match target {
25715                                    DialectType::Spark
25716                                    | DialectType::Databricks
25717                                    | DialectType::Hive => DataType::Custom {
25718                                        name: "STRING".to_string(),
25719                                    },
25720                                    _ => DataType::VarChar {
25721                                        length: Some(36),
25722                                        parenthesized_length: true,
25723                                    },
25724                                }
25725                            }
25726                            DataType::Custom { name }
25727                                if name.eq_ignore_ascii_case("DATETIMEOFFSET") =>
25728                            {
25729                                match target {
25730                                    DialectType::Spark
25731                                    | DialectType::Databricks
25732                                    | DialectType::Hive => DataType::Timestamp {
25733                                        timezone: false,
25734                                        precision: None,
25735                                    },
25736                                    _ => DataType::Timestamp {
25737                                        timezone: true,
25738                                        precision: None,
25739                                    },
25740                                }
25741                            }
25742                            DataType::Custom { ref name }
25743                                if name.len() >= 10
25744                                    && name[..10].eq_ignore_ascii_case("DATETIME2(") =>
25745                            {
25746                                // DATETIME2(n) -> TIMESTAMP
25747                                DataType::Timestamp {
25748                                    timezone: false,
25749                                    precision: None,
25750                                }
25751                            }
25752                            DataType::Custom { ref name }
25753                                if name.len() >= 5 && name[..5].eq_ignore_ascii_case("TIME(") =>
25754                            {
25755                                // TIME(n) -> TIMESTAMP for Spark, keep as TIME for others
25756                                match target {
25757                                    DialectType::Spark
25758                                    | DialectType::Databricks
25759                                    | DialectType::Hive => DataType::Timestamp {
25760                                        timezone: false,
25761                                        precision: None,
25762                                    },
25763                                    _ => return Ok(Expression::DataType(dt)),
25764                                }
25765                            }
25766                            DataType::Custom { ref name }
25767                                if name.len() >= 7 && name[..7].eq_ignore_ascii_case("NUMERIC") =>
25768                            {
25769                                // Parse NUMERIC(p,s) back to Decimal(p,s)
25770                                let upper = name.to_ascii_uppercase();
25771                                if let Some(inner) = upper
25772                                    .strip_prefix("NUMERIC(")
25773                                    .and_then(|s| s.strip_suffix(')'))
25774                                {
25775                                    let parts: Vec<&str> = inner.split(',').collect();
25776                                    let precision =
25777                                        parts.first().and_then(|s| s.trim().parse::<u32>().ok());
25778                                    let scale =
25779                                        parts.get(1).and_then(|s| s.trim().parse::<u32>().ok());
25780                                    DataType::Decimal { precision, scale }
25781                                } else if upper == "NUMERIC" {
25782                                    DataType::Decimal {
25783                                        precision: None,
25784                                        scale: None,
25785                                    }
25786                                } else {
25787                                    return Ok(Expression::DataType(dt));
25788                                }
25789                            }
25790                            DataType::Float {
25791                                precision: Some(p), ..
25792                            } => {
25793                                // For Hive/Spark: FLOAT(1-32) -> FLOAT, FLOAT(33+) -> DOUBLE (IEEE 754 boundary)
25794                                // For other targets: FLOAT(1-24) -> FLOAT, FLOAT(25+) -> DOUBLE (TSQL boundary)
25795                                let boundary = match target {
25796                                    DialectType::Hive
25797                                    | DialectType::Spark
25798                                    | DialectType::Databricks => 32,
25799                                    _ => 24,
25800                                };
25801                                if *p <= boundary {
25802                                    DataType::Float {
25803                                        precision: None,
25804                                        scale: None,
25805                                        real_spelling: false,
25806                                    }
25807                                } else {
25808                                    DataType::Double {
25809                                        precision: None,
25810                                        scale: None,
25811                                    }
25812                                }
25813                            }
25814                            DataType::TinyInt { .. } => match target {
25815                                DialectType::DuckDB => DataType::Custom {
25816                                    name: "UTINYINT".to_string(),
25817                                },
25818                                DialectType::Hive
25819                                | DialectType::Spark
25820                                | DialectType::Databricks => DataType::SmallInt { length: None },
25821                                _ => return Ok(Expression::DataType(dt)),
25822                            },
25823                            // INTEGER -> INT for Spark/Databricks
25824                            DataType::Int {
25825                                length,
25826                                integer_spelling: true,
25827                            } => DataType::Int {
25828                                length: *length,
25829                                integer_spelling: false,
25830                            },
25831                            _ => return Ok(Expression::DataType(dt)),
25832                        };
25833                        Ok(Expression::DataType(new_dt))
25834                    } else {
25835                        Ok(e)
25836                    }
25837                }
25838                Action::MySQLSafeDivide => {
25839                    use crate::expressions::{BinaryOp, Cast};
25840                    if let Expression::Div(op) = e {
25841                        let left = op.left;
25842                        let right = op.right;
25843                        // For SQLite: CAST left as REAL but NO NULLIF wrapping
25844                        if matches!(target, DialectType::SQLite) {
25845                            let new_left = Expression::Cast(Box::new(Cast {
25846                                this: left,
25847                                to: DataType::Float {
25848                                    precision: None,
25849                                    scale: None,
25850                                    real_spelling: true,
25851                                },
25852                                trailing_comments: Vec::new(),
25853                                double_colon_syntax: false,
25854                                format: None,
25855                                default: None,
25856                                inferred_type: None,
25857                            }));
25858                            return Ok(Expression::Div(Box::new(BinaryOp::new(new_left, right))));
25859                        }
25860                        // Wrap right in NULLIF(right, 0)
25861                        let nullif_right = Expression::Function(Box::new(Function::new(
25862                            "NULLIF".to_string(),
25863                            vec![right, Expression::number(0)],
25864                        )));
25865                        // For some dialects, also CAST the left side
25866                        let new_left = match target {
25867                            DialectType::PostgreSQL
25868                            | DialectType::Redshift
25869                            | DialectType::Teradata
25870                            | DialectType::Materialize
25871                            | DialectType::RisingWave => Expression::Cast(Box::new(Cast {
25872                                this: left,
25873                                to: DataType::Custom {
25874                                    name: "DOUBLE PRECISION".to_string(),
25875                                },
25876                                trailing_comments: Vec::new(),
25877                                double_colon_syntax: false,
25878                                format: None,
25879                                default: None,
25880                                inferred_type: None,
25881                            })),
25882                            DialectType::Drill
25883                            | DialectType::Trino
25884                            | DialectType::Presto
25885                            | DialectType::Athena => Expression::Cast(Box::new(Cast {
25886                                this: left,
25887                                to: DataType::Double {
25888                                    precision: None,
25889                                    scale: None,
25890                                },
25891                                trailing_comments: Vec::new(),
25892                                double_colon_syntax: false,
25893                                format: None,
25894                                default: None,
25895                                inferred_type: None,
25896                            })),
25897                            DialectType::TSQL => Expression::Cast(Box::new(Cast {
25898                                this: left,
25899                                to: DataType::Float {
25900                                    precision: None,
25901                                    scale: None,
25902                                    real_spelling: false,
25903                                },
25904                                trailing_comments: Vec::new(),
25905                                double_colon_syntax: false,
25906                                format: None,
25907                                default: None,
25908                                inferred_type: None,
25909                            })),
25910                            _ => left,
25911                        };
25912                        Ok(Expression::Div(Box::new(BinaryOp::new(
25913                            new_left,
25914                            nullif_right,
25915                        ))))
25916                    } else {
25917                        Ok(e)
25918                    }
25919                }
25920                Action::AlterTableRenameStripSchema => {
25921                    if let Expression::AlterTable(mut at) = e {
25922                        if let Some(crate::expressions::AlterTableAction::RenameTable(
25923                            ref mut new_tbl,
25924                        )) = at.actions.first_mut()
25925                        {
25926                            new_tbl.schema = None;
25927                            new_tbl.catalog = None;
25928                        }
25929                        Ok(Expression::AlterTable(at))
25930                    } else {
25931                        Ok(e)
25932                    }
25933                }
25934                Action::NullsOrdering => {
25935                    // Fill in the source dialect's implied null ordering default.
25936                    // This makes implicit null ordering explicit so the target generator
25937                    // can correctly strip or keep it.
25938                    //
25939                    // Dialect null ordering categories:
25940                    // nulls_are_large (Oracle, PostgreSQL, Redshift, Snowflake):
25941                    //   ASC -> NULLS LAST, DESC -> NULLS FIRST
25942                    // nulls_are_small (Spark, Hive, BigQuery, MySQL, Databricks, ClickHouse, etc.):
25943                    //   ASC -> NULLS FIRST, DESC -> NULLS LAST
25944                    // nulls_are_last (DuckDB, Presto, Trino, Dremio, Athena):
25945                    //   NULLS LAST always (both ASC and DESC)
25946                    if let Expression::Ordered(mut o) = e {
25947                        let is_asc = !o.desc;
25948
25949                        let is_source_nulls_large = matches!(
25950                            source,
25951                            DialectType::Oracle
25952                                | DialectType::PostgreSQL
25953                                | DialectType::Redshift
25954                                | DialectType::Snowflake
25955                        );
25956                        let is_source_nulls_last = matches!(
25957                            source,
25958                            DialectType::DuckDB
25959                                | DialectType::Presto
25960                                | DialectType::Trino
25961                                | DialectType::Dremio
25962                                | DialectType::Athena
25963                                | DialectType::ClickHouse
25964                                | DialectType::Drill
25965                                | DialectType::Exasol
25966                                | DialectType::DataFusion
25967                        );
25968
25969                        // Determine target category to check if default matches
25970                        let is_target_nulls_large = matches!(
25971                            target,
25972                            DialectType::Oracle
25973                                | DialectType::PostgreSQL
25974                                | DialectType::Redshift
25975                                | DialectType::Snowflake
25976                        );
25977                        let is_target_nulls_last = matches!(
25978                            target,
25979                            DialectType::DuckDB
25980                                | DialectType::Presto
25981                                | DialectType::Trino
25982                                | DialectType::Dremio
25983                                | DialectType::Athena
25984                                | DialectType::ClickHouse
25985                                | DialectType::Drill
25986                                | DialectType::Exasol
25987                                | DialectType::DataFusion
25988                        );
25989
25990                        // Compute the implied nulls_first for source
25991                        let source_nulls_first = if is_source_nulls_large {
25992                            !is_asc // ASC -> NULLS LAST (false), DESC -> NULLS FIRST (true)
25993                        } else if is_source_nulls_last {
25994                            false // NULLS LAST always
25995                        } else {
25996                            is_asc // nulls_are_small: ASC -> NULLS FIRST (true), DESC -> NULLS LAST (false)
25997                        };
25998
25999                        // Compute the target's default
26000                        let target_nulls_first = if is_target_nulls_large {
26001                            !is_asc
26002                        } else if is_target_nulls_last {
26003                            false
26004                        } else {
26005                            is_asc
26006                        };
26007
26008                        // Only add explicit nulls ordering if source and target defaults differ
26009                        if source_nulls_first != target_nulls_first {
26010                            o.nulls_first = Some(source_nulls_first);
26011                        }
26012                        // If they match, leave nulls_first as None so the generator won't output it
26013
26014                        Ok(Expression::Ordered(o))
26015                    } else {
26016                        Ok(e)
26017                    }
26018                }
26019                Action::StringAggConvert => {
26020                    match e {
26021                        Expression::WithinGroup(wg) => {
26022                            // STRING_AGG(x, sep) WITHIN GROUP (ORDER BY z) -> target-specific
26023                            // Extract args and distinct flag from either Function, AggregateFunction, or StringAgg
26024                            let (x_opt, sep_opt, distinct) = match wg.this {
26025                                Expression::AggregateFunction(ref af)
26026                                    if af.name.eq_ignore_ascii_case("STRING_AGG")
26027                                        && af.args.len() >= 2 =>
26028                                {
26029                                    (
26030                                        Some(af.args[0].clone()),
26031                                        Some(af.args[1].clone()),
26032                                        af.distinct,
26033                                    )
26034                                }
26035                                Expression::Function(ref f)
26036                                    if f.name.eq_ignore_ascii_case("STRING_AGG")
26037                                        && f.args.len() >= 2 =>
26038                                {
26039                                    (Some(f.args[0].clone()), Some(f.args[1].clone()), false)
26040                                }
26041                                Expression::StringAgg(ref sa) => {
26042                                    (Some(sa.this.clone()), sa.separator.clone(), sa.distinct)
26043                                }
26044                                _ => (None, None, false),
26045                            };
26046                            if let (Some(x), Some(sep)) = (x_opt, sep_opt) {
26047                                let order_by = wg.order_by;
26048
26049                                match target {
26050                                    DialectType::TSQL | DialectType::Fabric => {
26051                                        // Keep as WithinGroup(StringAgg) for TSQL
26052                                        Ok(Expression::WithinGroup(Box::new(
26053                                            crate::expressions::WithinGroup {
26054                                                this: Expression::StringAgg(Box::new(
26055                                                    crate::expressions::StringAggFunc {
26056                                                        this: x,
26057                                                        separator: Some(sep),
26058                                                        order_by: None, // order_by goes in WithinGroup, not StringAgg
26059                                                        distinct,
26060                                                        filter: None,
26061                                                        limit: None,
26062                                                        inferred_type: None,
26063                                                    },
26064                                                )),
26065                                                order_by,
26066                                            },
26067                                        )))
26068                                    }
26069                                    DialectType::MySQL
26070                                    | DialectType::SingleStore
26071                                    | DialectType::Doris
26072                                    | DialectType::StarRocks => {
26073                                        // GROUP_CONCAT(x ORDER BY z SEPARATOR sep)
26074                                        Ok(Expression::GroupConcat(Box::new(
26075                                            crate::expressions::GroupConcatFunc {
26076                                                this: x,
26077                                                separator: Some(sep),
26078                                                order_by: Some(order_by),
26079                                                distinct,
26080                                                filter: None,
26081                                                limit: None,
26082                                                inferred_type: None,
26083                                            },
26084                                        )))
26085                                    }
26086                                    DialectType::SQLite => {
26087                                        // GROUP_CONCAT(x, sep) - no ORDER BY support
26088                                        Ok(Expression::GroupConcat(Box::new(
26089                                            crate::expressions::GroupConcatFunc {
26090                                                this: x,
26091                                                separator: Some(sep),
26092                                                order_by: None,
26093                                                distinct,
26094                                                filter: None,
26095                                                limit: None,
26096                                                inferred_type: None,
26097                                            },
26098                                        )))
26099                                    }
26100                                    DialectType::PostgreSQL | DialectType::Redshift => {
26101                                        // STRING_AGG(x, sep ORDER BY z)
26102                                        Ok(Expression::StringAgg(Box::new(
26103                                            crate::expressions::StringAggFunc {
26104                                                this: x,
26105                                                separator: Some(sep),
26106                                                order_by: Some(order_by),
26107                                                distinct,
26108                                                filter: None,
26109                                                limit: None,
26110                                                inferred_type: None,
26111                                            },
26112                                        )))
26113                                    }
26114                                    _ => {
26115                                        // Default: keep as STRING_AGG(x, sep) with ORDER BY inside
26116                                        Ok(Expression::StringAgg(Box::new(
26117                                            crate::expressions::StringAggFunc {
26118                                                this: x,
26119                                                separator: Some(sep),
26120                                                order_by: Some(order_by),
26121                                                distinct,
26122                                                filter: None,
26123                                                limit: None,
26124                                                inferred_type: None,
26125                                            },
26126                                        )))
26127                                    }
26128                                }
26129                            } else {
26130                                Ok(Expression::WithinGroup(wg))
26131                            }
26132                        }
26133                        Expression::StringAgg(sa) => {
26134                            match target {
26135                                DialectType::MySQL
26136                                | DialectType::SingleStore
26137                                | DialectType::Doris
26138                                | DialectType::StarRocks => {
26139                                    // STRING_AGG(x, sep) -> GROUP_CONCAT(x SEPARATOR sep)
26140                                    Ok(Expression::GroupConcat(Box::new(
26141                                        crate::expressions::GroupConcatFunc {
26142                                            this: sa.this,
26143                                            separator: sa.separator,
26144                                            order_by: sa.order_by,
26145                                            distinct: sa.distinct,
26146                                            filter: sa.filter,
26147                                            limit: None,
26148                                            inferred_type: None,
26149                                        },
26150                                    )))
26151                                }
26152                                DialectType::SQLite => {
26153                                    // STRING_AGG(x, sep) -> GROUP_CONCAT(x, sep)
26154                                    Ok(Expression::GroupConcat(Box::new(
26155                                        crate::expressions::GroupConcatFunc {
26156                                            this: sa.this,
26157                                            separator: sa.separator,
26158                                            order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
26159                                            distinct: sa.distinct,
26160                                            filter: sa.filter,
26161                                            limit: None,
26162                                            inferred_type: None,
26163                                        },
26164                                    )))
26165                                }
26166                                DialectType::Spark | DialectType::Databricks => {
26167                                    // STRING_AGG(x, sep) -> LISTAGG(x, sep)
26168                                    Ok(Expression::ListAgg(Box::new(
26169                                        crate::expressions::ListAggFunc {
26170                                            this: sa.this,
26171                                            separator: sa.separator,
26172                                            on_overflow: None,
26173                                            order_by: sa.order_by,
26174                                            distinct: sa.distinct,
26175                                            filter: None,
26176                                            inferred_type: None,
26177                                        },
26178                                    )))
26179                                }
26180                                _ => Ok(Expression::StringAgg(sa)),
26181                            }
26182                        }
26183                        _ => Ok(e),
26184                    }
26185                }
26186                Action::GroupConcatConvert => {
26187                    // Helper to expand CONCAT(a, b, c) -> a || b || c (for PostgreSQL/SQLite)
26188                    // or CONCAT(a, b, c) -> a + b + c (for TSQL)
26189                    fn expand_concat_to_dpipe(expr: Expression) -> Expression {
26190                        if let Expression::Function(ref f) = expr {
26191                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26192                                let mut result = f.args[0].clone();
26193                                for arg in &f.args[1..] {
26194                                    result = Expression::Concat(Box::new(BinaryOp {
26195                                        left: result,
26196                                        right: arg.clone(),
26197                                        left_comments: vec![],
26198                                        operator_comments: vec![],
26199                                        trailing_comments: vec![],
26200                                        inferred_type: None,
26201                                    }));
26202                                }
26203                                return result;
26204                            }
26205                        }
26206                        expr
26207                    }
26208                    fn expand_concat_to_plus(expr: Expression) -> Expression {
26209                        if let Expression::Function(ref f) = expr {
26210                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26211                                let mut result = f.args[0].clone();
26212                                for arg in &f.args[1..] {
26213                                    result = Expression::Add(Box::new(BinaryOp {
26214                                        left: result,
26215                                        right: arg.clone(),
26216                                        left_comments: vec![],
26217                                        operator_comments: vec![],
26218                                        trailing_comments: vec![],
26219                                        inferred_type: None,
26220                                    }));
26221                                }
26222                                return result;
26223                            }
26224                        }
26225                        expr
26226                    }
26227                    // Helper to wrap each arg in CAST(arg AS VARCHAR) for Presto/Trino CONCAT
26228                    fn wrap_concat_args_in_varchar_cast(expr: Expression) -> Expression {
26229                        if let Expression::Function(ref f) = expr {
26230                            if f.name.eq_ignore_ascii_case("CONCAT") && f.args.len() > 1 {
26231                                let new_args: Vec<Expression> = f
26232                                    .args
26233                                    .iter()
26234                                    .map(|arg| {
26235                                        Expression::Cast(Box::new(crate::expressions::Cast {
26236                                            this: arg.clone(),
26237                                            to: crate::expressions::DataType::VarChar {
26238                                                length: None,
26239                                                parenthesized_length: false,
26240                                            },
26241                                            trailing_comments: Vec::new(),
26242                                            double_colon_syntax: false,
26243                                            format: None,
26244                                            default: None,
26245                                            inferred_type: None,
26246                                        }))
26247                                    })
26248                                    .collect();
26249                                return Expression::Function(Box::new(
26250                                    crate::expressions::Function::new(
26251                                        "CONCAT".to_string(),
26252                                        new_args,
26253                                    ),
26254                                ));
26255                            }
26256                        }
26257                        expr
26258                    }
26259                    if let Expression::GroupConcat(gc) = e {
26260                        match target {
26261                            DialectType::Presto => {
26262                                // GROUP_CONCAT(x [, sep]) -> ARRAY_JOIN(ARRAY_AGG(x), sep)
26263                                let sep = gc.separator.unwrap_or(Expression::string(","));
26264                                // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
26265                                let this = wrap_concat_args_in_varchar_cast(gc.this);
26266                                let array_agg =
26267                                    Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
26268                                        this,
26269                                        distinct: gc.distinct,
26270                                        filter: gc.filter,
26271                                        order_by: gc.order_by.unwrap_or_default(),
26272                                        name: None,
26273                                        ignore_nulls: None,
26274                                        having_max: None,
26275                                        limit: None,
26276                                        inferred_type: None,
26277                                    }));
26278                                Ok(Expression::ArrayJoin(Box::new(
26279                                    crate::expressions::ArrayJoinFunc {
26280                                        this: array_agg,
26281                                        separator: sep,
26282                                        null_replacement: None,
26283                                    },
26284                                )))
26285                            }
26286                            DialectType::Trino => {
26287                                // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
26288                                let sep = gc.separator.unwrap_or(Expression::string(","));
26289                                // For multi-arg CONCAT, wrap each arg in CAST(... AS VARCHAR)
26290                                let this = wrap_concat_args_in_varchar_cast(gc.this);
26291                                Ok(Expression::ListAgg(Box::new(
26292                                    crate::expressions::ListAggFunc {
26293                                        this,
26294                                        separator: Some(sep),
26295                                        on_overflow: None,
26296                                        order_by: gc.order_by,
26297                                        distinct: gc.distinct,
26298                                        filter: gc.filter,
26299                                        inferred_type: None,
26300                                    },
26301                                )))
26302                            }
26303                            DialectType::PostgreSQL
26304                            | DialectType::Redshift
26305                            | DialectType::Snowflake
26306                            | DialectType::DuckDB
26307                            | DialectType::Hive
26308                            | DialectType::ClickHouse => {
26309                                // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep)
26310                                let sep = gc.separator.unwrap_or(Expression::string(","));
26311                                // Expand CONCAT(a,b,c) -> a || b || c for || dialects
26312                                let this = expand_concat_to_dpipe(gc.this);
26313                                // For PostgreSQL, add NULLS LAST for DESC / NULLS FIRST for ASC
26314                                let order_by = if target == DialectType::PostgreSQL {
26315                                    gc.order_by.map(|ords| {
26316                                        ords.into_iter()
26317                                            .map(|mut o| {
26318                                                if o.nulls_first.is_none() {
26319                                                    if o.desc {
26320                                                        o.nulls_first = Some(false);
26321                                                    // NULLS LAST
26322                                                    } else {
26323                                                        o.nulls_first = Some(true);
26324                                                        // NULLS FIRST
26325                                                    }
26326                                                }
26327                                                o
26328                                            })
26329                                            .collect()
26330                                    })
26331                                } else {
26332                                    gc.order_by
26333                                };
26334                                Ok(Expression::StringAgg(Box::new(
26335                                    crate::expressions::StringAggFunc {
26336                                        this,
26337                                        separator: Some(sep),
26338                                        order_by,
26339                                        distinct: gc.distinct,
26340                                        filter: gc.filter,
26341                                        limit: None,
26342                                        inferred_type: None,
26343                                    },
26344                                )))
26345                            }
26346                            DialectType::TSQL => {
26347                                // GROUP_CONCAT(x [, sep]) -> STRING_AGG(x, sep) WITHIN GROUP (ORDER BY ...)
26348                                // TSQL doesn't support DISTINCT in STRING_AGG
26349                                let sep = gc.separator.unwrap_or(Expression::string(","));
26350                                // Expand CONCAT(a,b,c) -> a + b + c for TSQL
26351                                let this = expand_concat_to_plus(gc.this);
26352                                Ok(Expression::StringAgg(Box::new(
26353                                    crate::expressions::StringAggFunc {
26354                                        this,
26355                                        separator: Some(sep),
26356                                        order_by: gc.order_by,
26357                                        distinct: false, // TSQL doesn't support DISTINCT in STRING_AGG
26358                                        filter: gc.filter,
26359                                        limit: None,
26360                                        inferred_type: None,
26361                                    },
26362                                )))
26363                            }
26364                            DialectType::SQLite => {
26365                                // GROUP_CONCAT stays as GROUP_CONCAT but ORDER BY is removed
26366                                // SQLite GROUP_CONCAT doesn't support ORDER BY
26367                                // Expand CONCAT(a,b,c) -> a || b || c
26368                                let this = expand_concat_to_dpipe(gc.this);
26369                                Ok(Expression::GroupConcat(Box::new(
26370                                    crate::expressions::GroupConcatFunc {
26371                                        this,
26372                                        separator: gc.separator,
26373                                        order_by: None, // SQLite doesn't support ORDER BY in GROUP_CONCAT
26374                                        distinct: gc.distinct,
26375                                        filter: gc.filter,
26376                                        limit: None,
26377                                        inferred_type: None,
26378                                    },
26379                                )))
26380                            }
26381                            DialectType::Spark | DialectType::Databricks => {
26382                                // GROUP_CONCAT(x [, sep]) -> LISTAGG(x, sep)
26383                                let sep = gc.separator.unwrap_or(Expression::string(","));
26384                                Ok(Expression::ListAgg(Box::new(
26385                                    crate::expressions::ListAggFunc {
26386                                        this: gc.this,
26387                                        separator: Some(sep),
26388                                        on_overflow: None,
26389                                        order_by: gc.order_by,
26390                                        distinct: gc.distinct,
26391                                        filter: None,
26392                                        inferred_type: None,
26393                                    },
26394                                )))
26395                            }
26396                            DialectType::MySQL
26397                            | DialectType::SingleStore
26398                            | DialectType::StarRocks => {
26399                                // MySQL GROUP_CONCAT should have explicit SEPARATOR (default ',')
26400                                if gc.separator.is_none() {
26401                                    let mut gc = gc;
26402                                    gc.separator = Some(Expression::string(","));
26403                                    Ok(Expression::GroupConcat(gc))
26404                                } else {
26405                                    Ok(Expression::GroupConcat(gc))
26406                                }
26407                            }
26408                            _ => Ok(Expression::GroupConcat(gc)),
26409                        }
26410                    } else {
26411                        Ok(e)
26412                    }
26413                }
26414                Action::TempTableHash => {
26415                    match e {
26416                        Expression::CreateTable(mut ct) => {
26417                            // TSQL #table -> TEMPORARY TABLE with # stripped from name
26418                            let name = &ct.name.name.name;
26419                            if name.starts_with('#') {
26420                                ct.name.name.name = name.trim_start_matches('#').to_string();
26421                            }
26422                            // Set temporary flag
26423                            ct.temporary = true;
26424                            Ok(Expression::CreateTable(ct))
26425                        }
26426                        Expression::Table(mut tr) => {
26427                            // Strip # from table references
26428                            let name = &tr.name.name;
26429                            if name.starts_with('#') {
26430                                tr.name.name = name.trim_start_matches('#').to_string();
26431                            }
26432                            Ok(Expression::Table(tr))
26433                        }
26434                        Expression::DropTable(mut dt) => {
26435                            // Strip # from DROP TABLE names
26436                            for table_ref in &mut dt.names {
26437                                if table_ref.name.name.starts_with('#') {
26438                                    table_ref.name.name =
26439                                        table_ref.name.name.trim_start_matches('#').to_string();
26440                                }
26441                            }
26442                            Ok(Expression::DropTable(dt))
26443                        }
26444                        _ => Ok(e),
26445                    }
26446                }
26447                Action::NvlClearOriginal => {
26448                    if let Expression::Nvl(mut f) = e {
26449                        f.original_name = None;
26450                        Ok(Expression::Nvl(f))
26451                    } else {
26452                        Ok(e)
26453                    }
26454                }
26455                Action::HiveCastToTryCast => {
26456                    // Convert Hive/Spark CAST to TRY_CAST for targets that support it
26457                    if let Expression::Cast(mut c) = e {
26458                        // For Spark/Hive -> DuckDB: TIMESTAMP -> TIMESTAMPTZ
26459                        // (Spark's TIMESTAMP is always timezone-aware)
26460                        if matches!(target, DialectType::DuckDB)
26461                            && matches!(source, DialectType::Spark | DialectType::Databricks)
26462                            && matches!(
26463                                c.to,
26464                                DataType::Timestamp {
26465                                    timezone: false,
26466                                    ..
26467                                }
26468                            )
26469                        {
26470                            c.to = DataType::Custom {
26471                                name: "TIMESTAMPTZ".to_string(),
26472                            };
26473                        }
26474                        // For Spark source -> Databricks: VARCHAR/CHAR -> STRING
26475                        // Spark parses VARCHAR(n)/CHAR(n) as TEXT, normalize to STRING
26476                        if matches!(target, DialectType::Databricks | DialectType::Spark)
26477                            && matches!(
26478                                source,
26479                                DialectType::Spark | DialectType::Databricks | DialectType::Hive
26480                            )
26481                            && Self::has_varchar_char_type(&c.to)
26482                        {
26483                            c.to = Self::normalize_varchar_to_string(c.to);
26484                        }
26485                        Ok(Expression::TryCast(c))
26486                    } else {
26487                        Ok(e)
26488                    }
26489                }
26490                Action::XorExpand => {
26491                    // Expand XOR to (a AND NOT b) OR (NOT a AND b) for dialects without XOR keyword
26492                    // Snowflake: use BOOLXOR(a, b) instead
26493                    if let Expression::Xor(xor) = e {
26494                        // Collect all XOR operands
26495                        let mut operands = Vec::new();
26496                        if let Some(this) = xor.this {
26497                            operands.push(*this);
26498                        }
26499                        if let Some(expr) = xor.expression {
26500                            operands.push(*expr);
26501                        }
26502                        operands.extend(xor.expressions);
26503
26504                        // Snowflake: use BOOLXOR(a, b)
26505                        if matches!(target, DialectType::Snowflake) && operands.len() == 2 {
26506                            let a = operands.remove(0);
26507                            let b = operands.remove(0);
26508                            return Ok(Expression::Function(Box::new(Function::new(
26509                                "BOOLXOR".to_string(),
26510                                vec![a, b],
26511                            ))));
26512                        }
26513
26514                        // Helper to build (a AND NOT b) OR (NOT a AND b)
26515                        let make_xor = |a: Expression, b: Expression| -> Expression {
26516                            let not_b = Expression::Not(Box::new(
26517                                crate::expressions::UnaryOp::new(b.clone()),
26518                            ));
26519                            let not_a = Expression::Not(Box::new(
26520                                crate::expressions::UnaryOp::new(a.clone()),
26521                            ));
26522                            let left_and = Expression::And(Box::new(BinaryOp {
26523                                left: a,
26524                                right: Expression::Paren(Box::new(Paren {
26525                                    this: not_b,
26526                                    trailing_comments: Vec::new(),
26527                                })),
26528                                left_comments: Vec::new(),
26529                                operator_comments: Vec::new(),
26530                                trailing_comments: Vec::new(),
26531                                inferred_type: None,
26532                            }));
26533                            let right_and = Expression::And(Box::new(BinaryOp {
26534                                left: Expression::Paren(Box::new(Paren {
26535                                    this: not_a,
26536                                    trailing_comments: Vec::new(),
26537                                })),
26538                                right: b,
26539                                left_comments: Vec::new(),
26540                                operator_comments: Vec::new(),
26541                                trailing_comments: Vec::new(),
26542                                inferred_type: None,
26543                            }));
26544                            Expression::Or(Box::new(BinaryOp {
26545                                left: Expression::Paren(Box::new(Paren {
26546                                    this: left_and,
26547                                    trailing_comments: Vec::new(),
26548                                })),
26549                                right: Expression::Paren(Box::new(Paren {
26550                                    this: right_and,
26551                                    trailing_comments: Vec::new(),
26552                                })),
26553                                left_comments: Vec::new(),
26554                                operator_comments: Vec::new(),
26555                                trailing_comments: Vec::new(),
26556                                inferred_type: None,
26557                            }))
26558                        };
26559
26560                        if operands.len() >= 2 {
26561                            let mut result = make_xor(operands.remove(0), operands.remove(0));
26562                            for operand in operands {
26563                                result = make_xor(result, operand);
26564                            }
26565                            Ok(result)
26566                        } else if operands.len() == 1 {
26567                            Ok(operands.remove(0))
26568                        } else {
26569                            // No operands - return FALSE (shouldn't happen)
26570                            Ok(Expression::Boolean(crate::expressions::BooleanLiteral {
26571                                value: false,
26572                            }))
26573                        }
26574                    } else {
26575                        Ok(e)
26576                    }
26577                }
26578                Action::DatePartUnquote => {
26579                    // DATE_PART('month', x) -> DATE_PART(month, x) for Snowflake target
26580                    // Convert the quoted string first arg to a bare Column/Identifier
26581                    if let Expression::Function(mut f) = e {
26582                        if let Some(Expression::Literal(lit)) = f.args.first() {
26583                            if let crate::expressions::Literal::String(s) = lit.as_ref() {
26584                                let bare_name = s.to_ascii_lowercase();
26585                                f.args[0] =
26586                                    Expression::Column(Box::new(crate::expressions::Column {
26587                                        name: Identifier::new(bare_name),
26588                                        table: None,
26589                                        join_mark: false,
26590                                        trailing_comments: Vec::new(),
26591                                        span: None,
26592                                        inferred_type: None,
26593                                    }));
26594                            }
26595                        }
26596                        Ok(Expression::Function(f))
26597                    } else {
26598                        Ok(e)
26599                    }
26600                }
26601                Action::ArrayLengthConvert => {
26602                    // Extract the argument from the expression
26603                    let arg = match e {
26604                        Expression::Cardinality(ref f) => f.this.clone(),
26605                        Expression::ArrayLength(ref f) => f.this.clone(),
26606                        Expression::ArraySize(ref f) => f.this.clone(),
26607                        _ => return Ok(e),
26608                    };
26609                    match target {
26610                        DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
26611                            Ok(Expression::Function(Box::new(Function::new(
26612                                "SIZE".to_string(),
26613                                vec![arg],
26614                            ))))
26615                        }
26616                        DialectType::Presto | DialectType::Trino | DialectType::Athena => {
26617                            Ok(Expression::Cardinality(Box::new(
26618                                crate::expressions::UnaryFunc::new(arg),
26619                            )))
26620                        }
26621                        DialectType::BigQuery => Ok(Expression::ArrayLength(Box::new(
26622                            crate::expressions::UnaryFunc::new(arg),
26623                        ))),
26624                        DialectType::DuckDB => Ok(Expression::ArrayLength(Box::new(
26625                            crate::expressions::UnaryFunc::new(arg),
26626                        ))),
26627                        DialectType::PostgreSQL | DialectType::Redshift => {
26628                            // PostgreSQL ARRAY_LENGTH requires dimension arg
26629                            Ok(Expression::Function(Box::new(Function::new(
26630                                "ARRAY_LENGTH".to_string(),
26631                                vec![arg, Expression::number(1)],
26632                            ))))
26633                        }
26634                        DialectType::Snowflake => Ok(Expression::ArraySize(Box::new(
26635                            crate::expressions::UnaryFunc::new(arg),
26636                        ))),
26637                        _ => Ok(e), // Keep original
26638                    }
26639                }
26640
26641                Action::JsonExtractToArrow => {
26642                    // JSON_EXTRACT(x, path) -> x -> path for SQLite/DuckDB (set arrow_syntax = true)
26643                    if let Expression::JsonExtract(mut f) = e {
26644                        f.arrow_syntax = true;
26645                        // Transform path: convert bracket notation to dot notation
26646                        // SQLite strips wildcards, DuckDB preserves them
26647                        if let Expression::Literal(ref lit) = f.path {
26648                            if let Literal::String(ref s) = lit.as_ref() {
26649                                let mut transformed = s.clone();
26650                                if matches!(target, DialectType::SQLite) {
26651                                    transformed = Self::strip_json_wildcards(&transformed);
26652                                }
26653                                transformed = Self::bracket_to_dot_notation(&transformed);
26654                                if transformed != *s {
26655                                    f.path = Expression::string(&transformed);
26656                                }
26657                            }
26658                        }
26659                        Ok(Expression::JsonExtract(f))
26660                    } else {
26661                        Ok(e)
26662                    }
26663                }
26664
26665                Action::JsonExtractToGetJsonObject => {
26666                    if let Expression::JsonExtract(f) = e {
26667                        if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
26668                            // JSON_EXTRACT(x, '$.key') -> JSON_EXTRACT_PATH(x, 'key') for PostgreSQL
26669                            // Use proper decomposition that handles brackets
26670                            let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path {
26671                                if let Literal::String(ref s) = lit.as_ref() {
26672                                    let parts = Self::decompose_json_path(s);
26673                                    parts.into_iter().map(|k| Expression::string(&k)).collect()
26674                                } else {
26675                                    vec![]
26676                                }
26677                            } else {
26678                                vec![f.path]
26679                            };
26680                            let func_name = if matches!(target, DialectType::Redshift) {
26681                                "JSON_EXTRACT_PATH_TEXT"
26682                            } else {
26683                                "JSON_EXTRACT_PATH"
26684                            };
26685                            let mut args = vec![f.this];
26686                            args.extend(keys);
26687                            Ok(Expression::Function(Box::new(Function::new(
26688                                func_name.to_string(),
26689                                args,
26690                            ))))
26691                        } else {
26692                            // GET_JSON_OBJECT(x, '$.path') for Hive/Spark
26693                            // Convert bracket double quotes to single quotes
26694                            let path = if let Expression::Literal(ref lit) = f.path {
26695                                if let Literal::String(ref s) = lit.as_ref() {
26696                                    let normalized = Self::bracket_to_single_quotes(s);
26697                                    if normalized != *s {
26698                                        Expression::string(&normalized)
26699                                    } else {
26700                                        f.path.clone()
26701                                    }
26702                                } else {
26703                                    f.path.clone()
26704                                }
26705                            } else {
26706                                f.path.clone()
26707                            };
26708                            Ok(Expression::Function(Box::new(Function::new(
26709                                "GET_JSON_OBJECT".to_string(),
26710                                vec![f.this, path],
26711                            ))))
26712                        }
26713                    } else {
26714                        Ok(e)
26715                    }
26716                }
26717
26718                Action::JsonExtractScalarToGetJsonObject => {
26719                    // JSON_EXTRACT_SCALAR(x, '$.path') -> GET_JSON_OBJECT(x, '$.path') for Hive/Spark
26720                    if let Expression::JsonExtractScalar(f) = e {
26721                        Ok(Expression::Function(Box::new(Function::new(
26722                            "GET_JSON_OBJECT".to_string(),
26723                            vec![f.this, f.path],
26724                        ))))
26725                    } else {
26726                        Ok(e)
26727                    }
26728                }
26729
26730                Action::JsonExtractToTsql => {
26731                    // PostgreSQL JSON operators need rooted T-SQL JSON paths. Generic
26732                    // JSON_EXTRACT keeps the historical SQLGlot-style query/value fallback.
26733                    let (this, path, func_name) = match e {
26734                        Expression::JsonExtract(f) => {
26735                            let path = Self::normalize_tsql_json_path_expr(f.path, false, false);
26736                            if f.arrow_syntax {
26737                                (f.this, path, Some("JSON_QUERY"))
26738                            } else {
26739                                (f.this, path, None)
26740                            }
26741                        }
26742                        Expression::JsonExtractScalar(f) => {
26743                            let path = Self::normalize_tsql_json_path_expr(
26744                                f.path,
26745                                f.hash_arrow_syntax,
26746                                f.hash_arrow_syntax,
26747                            );
26748                            if f.arrow_syntax || f.hash_arrow_syntax {
26749                                (f.this, path, Some("JSON_VALUE"))
26750                            } else {
26751                                (f.this, path, None)
26752                            }
26753                        }
26754                        Expression::JsonExtractPath(f) => {
26755                            let path = Self::normalize_tsql_json_path_parts(f.paths);
26756                            (f.this, path, Some("JSON_QUERY"))
26757                        }
26758                        _ => return Ok(e),
26759                    };
26760
26761                    if let Some(func_name) = func_name {
26762                        return Ok(Self::build_tsql_json_function(func_name, this, path));
26763                    }
26764
26765                    // Transform path: strip wildcards, convert bracket notation to dot notation,
26766                    // and make sure the path is rooted for T-SQL JSON functions.
26767                    let transformed_path = if let Expression::Literal(ref lit) = path {
26768                        if let Literal::String(ref s) = lit.as_ref() {
26769                            let stripped = Self::strip_json_wildcards(s);
26770                            let dotted = Self::bracket_to_dot_notation(&stripped);
26771                            Self::normalize_tsql_json_path_expr(
26772                                Expression::string(&dotted),
26773                                false,
26774                                false,
26775                            )
26776                        } else {
26777                            path.clone()
26778                        }
26779                    } else {
26780                        path
26781                    };
26782                    let json_query = Expression::Function(Box::new(Function::new(
26783                        "JSON_QUERY".to_string(),
26784                        vec![this.clone(), transformed_path.clone()],
26785                    )));
26786                    let json_value = Expression::Function(Box::new(Function::new(
26787                        "JSON_VALUE".to_string(),
26788                        vec![this, transformed_path],
26789                    )));
26790                    Ok(Expression::Function(Box::new(Function::new(
26791                        "ISNULL".to_string(),
26792                        vec![json_query, json_value],
26793                    ))))
26794                }
26795
26796                Action::JsonExtractToClickHouse => {
26797                    // JSON_EXTRACT/JSON_EXTRACT_SCALAR -> JSONExtractString(x, 'key1', idx, 'key2') for ClickHouse
26798                    let (this, path) = match e {
26799                        Expression::JsonExtract(f) => (f.this, f.path),
26800                        Expression::JsonExtractScalar(f) => (f.this, f.path),
26801                        _ => return Ok(e),
26802                    };
26803                    let args: Vec<Expression> = if let Expression::Literal(lit) = path {
26804                        if let Literal::String(ref s) = lit.as_ref() {
26805                            let parts = Self::decompose_json_path(s);
26806                            let mut result = vec![this];
26807                            for part in parts {
26808                                // ClickHouse uses 1-based integer indices for array access
26809                                if let Ok(idx) = part.parse::<i64>() {
26810                                    result.push(Expression::number(idx + 1));
26811                                } else {
26812                                    result.push(Expression::string(&part));
26813                                }
26814                            }
26815                            result
26816                        } else {
26817                            vec![]
26818                        }
26819                    } else {
26820                        vec![this, path]
26821                    };
26822                    Ok(Expression::Function(Box::new(Function::new(
26823                        "JSONExtractString".to_string(),
26824                        args,
26825                    ))))
26826                }
26827
26828                Action::JsonExtractScalarConvert => {
26829                    // JSON_EXTRACT_SCALAR -> target-specific
26830                    if let Expression::JsonExtractScalar(f) = e {
26831                        match target {
26832                            DialectType::PostgreSQL | DialectType::Redshift => {
26833                                // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'key1', 'key2')
26834                                let keys: Vec<Expression> = if let Expression::Literal(lit) = f.path
26835                                {
26836                                    if let Literal::String(ref s) = lit.as_ref() {
26837                                        let parts = Self::decompose_json_path(s);
26838                                        parts.into_iter().map(|k| Expression::string(&k)).collect()
26839                                    } else {
26840                                        vec![]
26841                                    }
26842                                } else {
26843                                    vec![f.path]
26844                                };
26845                                let mut args = vec![f.this];
26846                                args.extend(keys);
26847                                Ok(Expression::Function(Box::new(Function::new(
26848                                    "JSON_EXTRACT_PATH_TEXT".to_string(),
26849                                    args,
26850                                ))))
26851                            }
26852                            DialectType::Snowflake => {
26853                                // JSON_EXTRACT_SCALAR(x, '$.path') -> JSON_EXTRACT_PATH_TEXT(x, 'stripped_path')
26854                                let stripped_path = if let Expression::Literal(ref lit) = f.path {
26855                                    if let Literal::String(ref s) = lit.as_ref() {
26856                                        let stripped = Self::strip_json_dollar_prefix(s);
26857                                        Expression::string(&stripped)
26858                                    } else {
26859                                        f.path.clone()
26860                                    }
26861                                } else {
26862                                    f.path
26863                                };
26864                                Ok(Expression::Function(Box::new(Function::new(
26865                                    "JSON_EXTRACT_PATH_TEXT".to_string(),
26866                                    vec![f.this, stripped_path],
26867                                ))))
26868                            }
26869                            DialectType::SQLite | DialectType::DuckDB => {
26870                                // JSON_EXTRACT_SCALAR(x, '$.path') -> x ->> '$.path'
26871                                Ok(Expression::JsonExtractScalar(Box::new(
26872                                    crate::expressions::JsonExtractFunc {
26873                                        this: f.this,
26874                                        path: f.path,
26875                                        returning: f.returning,
26876                                        arrow_syntax: true,
26877                                        hash_arrow_syntax: false,
26878                                        wrapper_option: None,
26879                                        quotes_option: None,
26880                                        on_scalar_string: false,
26881                                        on_error: None,
26882                                    },
26883                                )))
26884                            }
26885                            _ => Ok(Expression::JsonExtractScalar(f)),
26886                        }
26887                    } else {
26888                        Ok(e)
26889                    }
26890                }
26891
26892                Action::JsonPathNormalize => {
26893                    // Normalize JSON path format for BigQuery, MySQL, etc.
26894                    if let Expression::JsonExtract(mut f) = e {
26895                        if let Expression::Literal(ref lit) = f.path {
26896                            if let Literal::String(ref s) = lit.as_ref() {
26897                                let mut normalized = s.clone();
26898                                // Convert bracket notation and handle wildcards per dialect
26899                                match target {
26900                                    DialectType::BigQuery => {
26901                                        // BigQuery strips wildcards and uses single quotes in brackets
26902                                        normalized = Self::strip_json_wildcards(&normalized);
26903                                        normalized = Self::bracket_to_single_quotes(&normalized);
26904                                    }
26905                                    DialectType::MySQL => {
26906                                        // MySQL preserves wildcards, converts brackets to dot notation
26907                                        normalized = Self::bracket_to_dot_notation(&normalized);
26908                                    }
26909                                    _ => {}
26910                                }
26911                                if normalized != *s {
26912                                    f.path = Expression::string(&normalized);
26913                                }
26914                            }
26915                        }
26916                        Ok(Expression::JsonExtract(f))
26917                    } else {
26918                        Ok(e)
26919                    }
26920                }
26921
26922                Action::JsonQueryValueConvert => {
26923                    // JsonQuery/JsonValue -> target-specific
26924                    let (f, is_query) = match e {
26925                        Expression::JsonQuery(f) => (f, true),
26926                        Expression::JsonValue(f) => (f, false),
26927                        _ => return Ok(e),
26928                    };
26929                    match target {
26930                        DialectType::TSQL | DialectType::Fabric => {
26931                            // ISNULL(JSON_QUERY(...), JSON_VALUE(...))
26932                            let json_query = Expression::Function(Box::new(Function::new(
26933                                "JSON_QUERY".to_string(),
26934                                vec![f.this.clone(), f.path.clone()],
26935                            )));
26936                            let json_value = Expression::Function(Box::new(Function::new(
26937                                "JSON_VALUE".to_string(),
26938                                vec![f.this, f.path],
26939                            )));
26940                            Ok(Expression::Function(Box::new(Function::new(
26941                                "ISNULL".to_string(),
26942                                vec![json_query, json_value],
26943                            ))))
26944                        }
26945                        DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
26946                            Ok(Expression::Function(Box::new(Function::new(
26947                                "GET_JSON_OBJECT".to_string(),
26948                                vec![f.this, f.path],
26949                            ))))
26950                        }
26951                        DialectType::PostgreSQL | DialectType::Redshift => {
26952                            Ok(Expression::Function(Box::new(Function::new(
26953                                "JSON_EXTRACT_PATH_TEXT".to_string(),
26954                                vec![f.this, f.path],
26955                            ))))
26956                        }
26957                        DialectType::DuckDB | DialectType::SQLite => {
26958                            // json -> path arrow syntax
26959                            Ok(Expression::JsonExtract(Box::new(
26960                                crate::expressions::JsonExtractFunc {
26961                                    this: f.this,
26962                                    path: f.path,
26963                                    returning: f.returning,
26964                                    arrow_syntax: true,
26965                                    hash_arrow_syntax: false,
26966                                    wrapper_option: f.wrapper_option,
26967                                    quotes_option: f.quotes_option,
26968                                    on_scalar_string: f.on_scalar_string,
26969                                    on_error: f.on_error,
26970                                },
26971                            )))
26972                        }
26973                        DialectType::Snowflake => {
26974                            // GET_PATH(PARSE_JSON(json), 'path')
26975                            // Strip $. prefix from path
26976                            // Only wrap in PARSE_JSON if not already a PARSE_JSON call or ParseJson expression
26977                            let json_expr = match &f.this {
26978                                Expression::Function(ref inner_f)
26979                                    if inner_f.name.eq_ignore_ascii_case("PARSE_JSON") =>
26980                                {
26981                                    f.this
26982                                }
26983                                Expression::ParseJson(_) => {
26984                                    // Already a ParseJson expression, which generates as PARSE_JSON(...)
26985                                    f.this
26986                                }
26987                                _ => Expression::Function(Box::new(Function::new(
26988                                    "PARSE_JSON".to_string(),
26989                                    vec![f.this],
26990                                ))),
26991                            };
26992                            let path_str = match &f.path {
26993                                Expression::Literal(lit)
26994                                    if matches!(lit.as_ref(), Literal::String(_)) =>
26995                                {
26996                                    let Literal::String(s) = lit.as_ref() else {
26997                                        unreachable!()
26998                                    };
26999                                    let stripped = s.strip_prefix("$.").unwrap_or(s);
27000                                    Expression::Literal(Box::new(Literal::String(
27001                                        stripped.to_string(),
27002                                    )))
27003                                }
27004                                other => other.clone(),
27005                            };
27006                            Ok(Expression::Function(Box::new(Function::new(
27007                                "GET_PATH".to_string(),
27008                                vec![json_expr, path_str],
27009                            ))))
27010                        }
27011                        _ => {
27012                            // Default: keep as JSON_QUERY/JSON_VALUE function
27013                            let func_name = if is_query { "JSON_QUERY" } else { "JSON_VALUE" };
27014                            Ok(Expression::Function(Box::new(Function::new(
27015                                func_name.to_string(),
27016                                vec![f.this, f.path],
27017                            ))))
27018                        }
27019                    }
27020                }
27021
27022                Action::JsonLiteralToJsonParse => {
27023                    // CAST('x' AS JSON) -> JSON_PARSE('x') for Presto, PARSE_JSON for Snowflake
27024                    // Also DuckDB CAST(x AS JSON) -> JSON_PARSE(x) for Trino/Presto/Athena
27025                    if let Expression::Cast(c) = e {
27026                        let func_name = if matches!(target, DialectType::Snowflake) {
27027                            "PARSE_JSON"
27028                        } else {
27029                            "JSON_PARSE"
27030                        };
27031                        Ok(Expression::Function(Box::new(Function::new(
27032                            func_name.to_string(),
27033                            vec![c.this],
27034                        ))))
27035                    } else {
27036                        Ok(e)
27037                    }
27038                }
27039
27040                Action::DuckDBCastJsonToVariant => {
27041                    if let Expression::Cast(c) = e {
27042                        Ok(Expression::Cast(Box::new(Cast {
27043                            this: c.this,
27044                            to: DataType::Custom {
27045                                name: "VARIANT".to_string(),
27046                            },
27047                            trailing_comments: c.trailing_comments,
27048                            double_colon_syntax: false,
27049                            format: None,
27050                            default: None,
27051                            inferred_type: None,
27052                        })))
27053                    } else {
27054                        Ok(e)
27055                    }
27056                }
27057
27058                Action::DuckDBTryCastJsonToTryJsonParse => {
27059                    // DuckDB TRY_CAST(x AS JSON) -> TRY(JSON_PARSE(x)) for Trino/Presto/Athena
27060                    if let Expression::TryCast(c) = e {
27061                        let json_parse = Expression::Function(Box::new(Function::new(
27062                            "JSON_PARSE".to_string(),
27063                            vec![c.this],
27064                        )));
27065                        Ok(Expression::Function(Box::new(Function::new(
27066                            "TRY".to_string(),
27067                            vec![json_parse],
27068                        ))))
27069                    } else {
27070                        Ok(e)
27071                    }
27072                }
27073
27074                Action::DuckDBJsonFuncToJsonParse => {
27075                    // DuckDB json(x) -> JSON_PARSE(x) for Trino/Presto/Athena
27076                    if let Expression::Function(f) = e {
27077                        let args = f.args;
27078                        Ok(Expression::Function(Box::new(Function::new(
27079                            "JSON_PARSE".to_string(),
27080                            args,
27081                        ))))
27082                    } else {
27083                        Ok(e)
27084                    }
27085                }
27086
27087                Action::DuckDBJsonValidToIsJson => {
27088                    // DuckDB json_valid(x) -> x IS JSON (SQL:2016 predicate) for Trino/Presto/Athena
27089                    if let Expression::Function(mut f) = e {
27090                        let arg = f.args.remove(0);
27091                        Ok(Expression::IsJson(Box::new(crate::expressions::IsJson {
27092                            this: arg,
27093                            json_type: None,
27094                            unique_keys: None,
27095                            negated: false,
27096                        })))
27097                    } else {
27098                        Ok(e)
27099                    }
27100                }
27101
27102                Action::AtTimeZoneConvert => {
27103                    // AT TIME ZONE -> target-specific conversion
27104                    if let Expression::AtTimeZone(atz) = e {
27105                        match target {
27106                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
27107                                Ok(Expression::Function(Box::new(Function::new(
27108                                    "AT_TIMEZONE".to_string(),
27109                                    vec![atz.this, atz.zone],
27110                                ))))
27111                            }
27112                            DialectType::Spark | DialectType::Databricks => {
27113                                Ok(Expression::Function(Box::new(Function::new(
27114                                    "FROM_UTC_TIMESTAMP".to_string(),
27115                                    vec![atz.this, atz.zone],
27116                                ))))
27117                            }
27118                            DialectType::Snowflake => {
27119                                // CONVERT_TIMEZONE('zone', expr)
27120                                Ok(Expression::Function(Box::new(Function::new(
27121                                    "CONVERT_TIMEZONE".to_string(),
27122                                    vec![atz.zone, atz.this],
27123                                ))))
27124                            }
27125                            DialectType::BigQuery => {
27126                                // TIMESTAMP(DATETIME(expr, 'zone'))
27127                                let datetime_call = Expression::Function(Box::new(Function::new(
27128                                    "DATETIME".to_string(),
27129                                    vec![atz.this, atz.zone],
27130                                )));
27131                                Ok(Expression::Function(Box::new(Function::new(
27132                                    "TIMESTAMP".to_string(),
27133                                    vec![datetime_call],
27134                                ))))
27135                            }
27136                            _ => Ok(Expression::Function(Box::new(Function::new(
27137                                "AT_TIMEZONE".to_string(),
27138                                vec![atz.this, atz.zone],
27139                            )))),
27140                        }
27141                    } else {
27142                        Ok(e)
27143                    }
27144                }
27145
27146                Action::DayOfWeekConvert => {
27147                    // DAY_OF_WEEK -> ISODOW for DuckDB, ((DAYOFWEEK(x) % 7) + 1) for Spark
27148                    if let Expression::DayOfWeek(f) = e {
27149                        match target {
27150                            DialectType::DuckDB => Ok(Expression::Function(Box::new(
27151                                Function::new("ISODOW".to_string(), vec![f.this]),
27152                            ))),
27153                            DialectType::Spark | DialectType::Databricks => {
27154                                // ((DAYOFWEEK(x) % 7) + 1)
27155                                let dayofweek = Expression::Function(Box::new(Function::new(
27156                                    "DAYOFWEEK".to_string(),
27157                                    vec![f.this],
27158                                )));
27159                                let modulo = Expression::Mod(Box::new(BinaryOp {
27160                                    left: dayofweek,
27161                                    right: Expression::number(7),
27162                                    left_comments: Vec::new(),
27163                                    operator_comments: Vec::new(),
27164                                    trailing_comments: Vec::new(),
27165                                    inferred_type: None,
27166                                }));
27167                                let paren_mod = Expression::Paren(Box::new(Paren {
27168                                    this: modulo,
27169                                    trailing_comments: Vec::new(),
27170                                }));
27171                                let add_one = Expression::Add(Box::new(BinaryOp {
27172                                    left: paren_mod,
27173                                    right: Expression::number(1),
27174                                    left_comments: Vec::new(),
27175                                    operator_comments: Vec::new(),
27176                                    trailing_comments: Vec::new(),
27177                                    inferred_type: None,
27178                                }));
27179                                Ok(Expression::Paren(Box::new(Paren {
27180                                    this: add_one,
27181                                    trailing_comments: Vec::new(),
27182                                })))
27183                            }
27184                            _ => Ok(Expression::DayOfWeek(f)),
27185                        }
27186                    } else {
27187                        Ok(e)
27188                    }
27189                }
27190
27191                Action::MaxByMinByConvert => {
27192                    // MAX_BY -> argMax for ClickHouse, drop 3rd arg for Spark
27193                    // MIN_BY -> argMin for ClickHouse, ARG_MIN for DuckDB, drop 3rd arg for Spark/ClickHouse
27194                    // Handle both Expression::Function and Expression::AggregateFunction
27195                    let (is_max, args) = match &e {
27196                        Expression::Function(f) => {
27197                            (f.name.eq_ignore_ascii_case("MAX_BY"), f.args.clone())
27198                        }
27199                        Expression::AggregateFunction(af) => {
27200                            (af.name.eq_ignore_ascii_case("MAX_BY"), af.args.clone())
27201                        }
27202                        _ => return Ok(e),
27203                    };
27204                    match target {
27205                        DialectType::ClickHouse => {
27206                            let name = if is_max { "argMax" } else { "argMin" };
27207                            let mut args = args;
27208                            args.truncate(2);
27209                            Ok(Expression::Function(Box::new(Function::new(
27210                                name.to_string(),
27211                                args,
27212                            ))))
27213                        }
27214                        DialectType::DuckDB => {
27215                            let name = if is_max { "ARG_MAX" } else { "ARG_MIN" };
27216                            Ok(Expression::Function(Box::new(Function::new(
27217                                name.to_string(),
27218                                args,
27219                            ))))
27220                        }
27221                        DialectType::Spark | DialectType::Databricks => {
27222                            let mut args = args;
27223                            args.truncate(2);
27224                            let name = if is_max { "MAX_BY" } else { "MIN_BY" };
27225                            Ok(Expression::Function(Box::new(Function::new(
27226                                name.to_string(),
27227                                args,
27228                            ))))
27229                        }
27230                        _ => Ok(e),
27231                    }
27232                }
27233
27234                Action::ElementAtConvert => {
27235                    // ELEMENT_AT(arr, idx) -> arr[idx] for PostgreSQL, arr[SAFE_ORDINAL(idx)] for BigQuery
27236                    let (arr, idx) = if let Expression::ElementAt(bf) = e {
27237                        (bf.this, bf.expression)
27238                    } else if let Expression::Function(ref f) = e {
27239                        if f.args.len() >= 2 {
27240                            if let Expression::Function(f) = e {
27241                                let mut args = f.args;
27242                                let arr = args.remove(0);
27243                                let idx = args.remove(0);
27244                                (arr, idx)
27245                            } else {
27246                                unreachable!("outer condition already matched Expression::Function")
27247                            }
27248                        } else {
27249                            return Ok(e);
27250                        }
27251                    } else {
27252                        return Ok(e);
27253                    };
27254                    match target {
27255                        DialectType::PostgreSQL => {
27256                            // Wrap array in parens for PostgreSQL: (ARRAY[1,2,3])[4]
27257                            let arr_expr = Expression::Paren(Box::new(Paren {
27258                                this: arr,
27259                                trailing_comments: vec![],
27260                            }));
27261                            Ok(Expression::Subscript(Box::new(
27262                                crate::expressions::Subscript {
27263                                    this: arr_expr,
27264                                    index: idx,
27265                                },
27266                            )))
27267                        }
27268                        DialectType::BigQuery => {
27269                            // BigQuery: convert ARRAY[...] to bare [...] for subscript
27270                            let arr_expr = match arr {
27271                                Expression::ArrayFunc(af) => Expression::ArrayFunc(Box::new(
27272                                    crate::expressions::ArrayConstructor {
27273                                        expressions: af.expressions,
27274                                        bracket_notation: true,
27275                                        use_list_keyword: false,
27276                                    },
27277                                )),
27278                                other => other,
27279                            };
27280                            let safe_ordinal = Expression::Function(Box::new(Function::new(
27281                                "SAFE_ORDINAL".to_string(),
27282                                vec![idx],
27283                            )));
27284                            Ok(Expression::Subscript(Box::new(
27285                                crate::expressions::Subscript {
27286                                    this: arr_expr,
27287                                    index: safe_ordinal,
27288                                },
27289                            )))
27290                        }
27291                        _ => Ok(Expression::Function(Box::new(Function::new(
27292                            "ELEMENT_AT".to_string(),
27293                            vec![arr, idx],
27294                        )))),
27295                    }
27296                }
27297
27298                Action::CurrentUserParens => {
27299                    // CURRENT_USER -> CURRENT_USER() for Snowflake
27300                    Ok(Expression::Function(Box::new(Function::new(
27301                        "CURRENT_USER".to_string(),
27302                        vec![],
27303                    ))))
27304                }
27305
27306                Action::ArrayAggToCollectList => {
27307                    // ARRAY_AGG(x ORDER BY ...) -> COLLECT_LIST(x) for Hive/Spark
27308                    // Python sqlglot Hive.arrayagg_sql strips ORDER BY for simple cases
27309                    // but preserves it when DISTINCT/IGNORE NULLS/LIMIT are present
27310                    match e {
27311                        Expression::AggregateFunction(mut af) => {
27312                            let is_simple =
27313                                !af.distinct && af.ignore_nulls.is_none() && af.limit.is_none();
27314                            let args = if af.args.is_empty() {
27315                                vec![]
27316                            } else {
27317                                vec![af.args[0].clone()]
27318                            };
27319                            af.name = "COLLECT_LIST".to_string();
27320                            af.args = args;
27321                            if is_simple {
27322                                af.order_by = Vec::new();
27323                            }
27324                            Ok(Expression::AggregateFunction(af))
27325                        }
27326                        Expression::ArrayAgg(agg) => {
27327                            let is_simple =
27328                                !agg.distinct && agg.ignore_nulls.is_none() && agg.limit.is_none();
27329                            Ok(Expression::AggregateFunction(Box::new(
27330                                crate::expressions::AggregateFunction {
27331                                    name: "COLLECT_LIST".to_string(),
27332                                    args: vec![agg.this.clone()],
27333                                    distinct: agg.distinct,
27334                                    filter: agg.filter.clone(),
27335                                    order_by: if is_simple {
27336                                        Vec::new()
27337                                    } else {
27338                                        agg.order_by.clone()
27339                                    },
27340                                    limit: agg.limit.clone(),
27341                                    ignore_nulls: agg.ignore_nulls,
27342                                    inferred_type: None,
27343                                },
27344                            )))
27345                        }
27346                        _ => Ok(e),
27347                    }
27348                }
27349
27350                Action::ArraySyntaxConvert => {
27351                    match e {
27352                        // ARRAY[1, 2] (ArrayFunc bracket_notation=false) -> set bracket_notation=true
27353                        // so the generator uses dialect-specific output (ARRAY() for Spark, [] for BigQuery)
27354                        Expression::ArrayFunc(arr) if !arr.bracket_notation => Ok(
27355                            Expression::ArrayFunc(Box::new(crate::expressions::ArrayConstructor {
27356                                expressions: arr.expressions,
27357                                bracket_notation: true,
27358                                use_list_keyword: false,
27359                            })),
27360                        ),
27361                        // ARRAY(y) function style -> ArrayFunc for target dialect
27362                        // bracket_notation=true for BigQuery/DuckDB/ClickHouse/StarRocks (output []), false for Presto (output ARRAY[])
27363                        Expression::Function(f) if f.name.eq_ignore_ascii_case("ARRAY") => {
27364                            let bracket = matches!(
27365                                target,
27366                                DialectType::BigQuery
27367                                    | DialectType::DuckDB
27368                                    | DialectType::Snowflake
27369                                    | DialectType::ClickHouse
27370                                    | DialectType::StarRocks
27371                            );
27372                            Ok(Expression::ArrayFunc(Box::new(
27373                                crate::expressions::ArrayConstructor {
27374                                    expressions: f.args,
27375                                    bracket_notation: bracket,
27376                                    use_list_keyword: false,
27377                                },
27378                            )))
27379                        }
27380                        _ => Ok(e),
27381                    }
27382                }
27383
27384                Action::CastToJsonForSpark => {
27385                    // CAST(x AS JSON) -> TO_JSON(x) for Spark
27386                    if let Expression::Cast(c) = e {
27387                        Ok(Expression::Function(Box::new(Function::new(
27388                            "TO_JSON".to_string(),
27389                            vec![c.this],
27390                        ))))
27391                    } else {
27392                        Ok(e)
27393                    }
27394                }
27395
27396                Action::CastJsonToFromJson => {
27397                    // CAST(ParseJson(literal) AS ARRAY/MAP/STRUCT) -> FROM_JSON(literal, type_string) for Spark
27398                    if let Expression::Cast(c) = e {
27399                        // Extract the string literal from ParseJson
27400                        let literal_expr = if let Expression::ParseJson(pj) = c.this {
27401                            pj.this
27402                        } else {
27403                            c.this
27404                        };
27405                        // Convert the target DataType to Spark's type string format
27406                        let type_str = Self::data_type_to_spark_string(&c.to);
27407                        Ok(Expression::Function(Box::new(Function::new(
27408                            "FROM_JSON".to_string(),
27409                            vec![
27410                                literal_expr,
27411                                Expression::Literal(Box::new(Literal::String(type_str))),
27412                            ],
27413                        ))))
27414                    } else {
27415                        Ok(e)
27416                    }
27417                }
27418
27419                Action::ToJsonConvert => {
27420                    // TO_JSON(x) -> target-specific conversion
27421                    if let Expression::ToJson(f) = e {
27422                        let arg = f.this;
27423                        match target {
27424                            DialectType::Presto | DialectType::Trino => {
27425                                // JSON_FORMAT(CAST(x AS JSON))
27426                                let cast_json = Expression::Cast(Box::new(Cast {
27427                                    this: arg,
27428                                    to: DataType::Custom {
27429                                        name: "JSON".to_string(),
27430                                    },
27431                                    trailing_comments: vec![],
27432                                    double_colon_syntax: false,
27433                                    format: None,
27434                                    default: None,
27435                                    inferred_type: None,
27436                                }));
27437                                Ok(Expression::Function(Box::new(Function::new(
27438                                    "JSON_FORMAT".to_string(),
27439                                    vec![cast_json],
27440                                ))))
27441                            }
27442                            DialectType::BigQuery => Ok(Expression::Function(Box::new(
27443                                Function::new("TO_JSON_STRING".to_string(), vec![arg]),
27444                            ))),
27445                            DialectType::DuckDB => {
27446                                // CAST(TO_JSON(x) AS TEXT)
27447                                let to_json =
27448                                    Expression::ToJson(Box::new(crate::expressions::UnaryFunc {
27449                                        this: arg,
27450                                        original_name: None,
27451                                        inferred_type: None,
27452                                    }));
27453                                Ok(Expression::Cast(Box::new(Cast {
27454                                    this: to_json,
27455                                    to: DataType::Text,
27456                                    trailing_comments: vec![],
27457                                    double_colon_syntax: false,
27458                                    format: None,
27459                                    default: None,
27460                                    inferred_type: None,
27461                                })))
27462                            }
27463                            _ => Ok(Expression::ToJson(Box::new(
27464                                crate::expressions::UnaryFunc {
27465                                    this: arg,
27466                                    original_name: None,
27467                                    inferred_type: None,
27468                                },
27469                            ))),
27470                        }
27471                    } else {
27472                        Ok(e)
27473                    }
27474                }
27475
27476                Action::VarianceToClickHouse => {
27477                    if let Expression::Variance(f) = e {
27478                        Ok(Expression::Function(Box::new(Function::new(
27479                            "varSamp".to_string(),
27480                            vec![f.this],
27481                        ))))
27482                    } else {
27483                        Ok(e)
27484                    }
27485                }
27486
27487                Action::StddevToClickHouse => {
27488                    if let Expression::Stddev(f) = e {
27489                        Ok(Expression::Function(Box::new(Function::new(
27490                            "stddevSamp".to_string(),
27491                            vec![f.this],
27492                        ))))
27493                    } else {
27494                        Ok(e)
27495                    }
27496                }
27497
27498                Action::ApproxQuantileConvert => {
27499                    if let Expression::ApproxQuantile(aq) = e {
27500                        let mut args = vec![*aq.this];
27501                        if let Some(q) = aq.quantile {
27502                            args.push(*q);
27503                        }
27504                        Ok(Expression::Function(Box::new(Function::new(
27505                            "APPROX_PERCENTILE".to_string(),
27506                            args,
27507                        ))))
27508                    } else {
27509                        Ok(e)
27510                    }
27511                }
27512
27513                Action::DollarParamConvert => {
27514                    if let Expression::Parameter(p) = e {
27515                        Ok(Expression::Parameter(Box::new(
27516                            crate::expressions::Parameter {
27517                                name: p.name,
27518                                index: p.index,
27519                                style: crate::expressions::ParameterStyle::At,
27520                                quoted: p.quoted,
27521                                string_quoted: p.string_quoted,
27522                                expression: p.expression,
27523                            },
27524                        )))
27525                    } else {
27526                        Ok(e)
27527                    }
27528                }
27529
27530                Action::EscapeStringNormalize => {
27531                    if let Expression::Literal(ref lit) = e {
27532                        if let Literal::EscapeString(s) = lit.as_ref() {
27533                            // Strip prefix (e.g., "e:" or "E:") if present from tokenizer
27534                            let stripped = if s.starts_with("e:") || s.starts_with("E:") {
27535                                s[2..].to_string()
27536                            } else {
27537                                s.clone()
27538                            };
27539                            let normalized = stripped
27540                                .replace('\n', "\\n")
27541                                .replace('\r', "\\r")
27542                                .replace('\t', "\\t");
27543                            match target {
27544                                DialectType::BigQuery => {
27545                                    // BigQuery: e'...' -> CAST(b'...' AS STRING)
27546                                    // Use Raw for the b'...' part to avoid double-escaping
27547                                    let raw_sql = format!("CAST(b'{}' AS STRING)", normalized);
27548                                    Ok(Expression::Raw(crate::expressions::Raw { sql: raw_sql }))
27549                                }
27550                                _ => Ok(Expression::Literal(Box::new(Literal::EscapeString(
27551                                    normalized,
27552                                )))),
27553                            }
27554                        } else {
27555                            Ok(e)
27556                        }
27557                    } else {
27558                        Ok(e)
27559                    }
27560                }
27561
27562                Action::StraightJoinCase => {
27563                    // straight_join: keep lowercase for DuckDB, quote for MySQL
27564                    if let Expression::Column(col) = e {
27565                        if col.name.name == "STRAIGHT_JOIN" {
27566                            let mut new_col = col;
27567                            new_col.name.name = "straight_join".to_string();
27568                            if matches!(target, DialectType::MySQL) {
27569                                // MySQL: needs quoting since it's a reserved keyword
27570                                new_col.name.quoted = true;
27571                            }
27572                            Ok(Expression::Column(new_col))
27573                        } else {
27574                            Ok(Expression::Column(col))
27575                        }
27576                    } else {
27577                        Ok(e)
27578                    }
27579                }
27580
27581                Action::TablesampleReservoir => {
27582                    // TABLESAMPLE -> TABLESAMPLE RESERVOIR for DuckDB
27583                    if let Expression::TableSample(mut ts) = e {
27584                        if let Some(ref mut sample) = ts.sample {
27585                            sample.method = crate::expressions::SampleMethod::Reservoir;
27586                            sample.explicit_method = true;
27587                        }
27588                        Ok(Expression::TableSample(ts))
27589                    } else {
27590                        Ok(e)
27591                    }
27592                }
27593
27594                Action::TablesampleSnowflakeStrip => {
27595                    // Strip method and PERCENT for Snowflake target from non-Snowflake source
27596                    match e {
27597                        Expression::TableSample(mut ts) => {
27598                            if let Some(ref mut sample) = ts.sample {
27599                                sample.suppress_method_output = true;
27600                                sample.unit_after_size = false;
27601                                sample.is_percent = false;
27602                            }
27603                            Ok(Expression::TableSample(ts))
27604                        }
27605                        Expression::Table(mut t) => {
27606                            if let Some(ref mut sample) = t.table_sample {
27607                                sample.suppress_method_output = true;
27608                                sample.unit_after_size = false;
27609                                sample.is_percent = false;
27610                            }
27611                            Ok(Expression::Table(t))
27612                        }
27613                        _ => Ok(e),
27614                    }
27615                }
27616
27617                Action::FirstToAnyValue => {
27618                    // FIRST(col) IGNORE NULLS -> ANY_VALUE(col) for DuckDB
27619                    if let Expression::First(mut agg) = e {
27620                        agg.ignore_nulls = None;
27621                        agg.name = Some("ANY_VALUE".to_string());
27622                        Ok(Expression::AnyValue(agg))
27623                    } else {
27624                        Ok(e)
27625                    }
27626                }
27627
27628                Action::ArrayIndexConvert => {
27629                    // Subscript index: 1-based to 0-based for BigQuery
27630                    if let Expression::Subscript(mut sub) = e {
27631                        if let Expression::Literal(ref lit) = sub.index {
27632                            if let Literal::Number(ref n) = lit.as_ref() {
27633                                if let Ok(val) = n.parse::<i64>() {
27634                                    sub.index = Expression::Literal(Box::new(Literal::Number(
27635                                        (val - 1).to_string(),
27636                                    )));
27637                                }
27638                            }
27639                        }
27640                        Ok(Expression::Subscript(sub))
27641                    } else {
27642                        Ok(e)
27643                    }
27644                }
27645
27646                Action::AnyValueIgnoreNulls => {
27647                    // ANY_VALUE(x) -> ANY_VALUE(x) IGNORE NULLS for Spark
27648                    if let Expression::AnyValue(mut av) = e {
27649                        if av.ignore_nulls.is_none() {
27650                            av.ignore_nulls = Some(true);
27651                        }
27652                        Ok(Expression::AnyValue(av))
27653                    } else {
27654                        Ok(e)
27655                    }
27656                }
27657
27658                Action::BigQueryNullsOrdering => {
27659                    // BigQuery doesn't support NULLS FIRST/LAST in window function ORDER BY
27660                    if let Expression::WindowFunction(mut wf) = e {
27661                        for o in &mut wf.over.order_by {
27662                            o.nulls_first = None;
27663                        }
27664                        Ok(Expression::WindowFunction(wf))
27665                    } else if let Expression::Ordered(mut o) = e {
27666                        o.nulls_first = None;
27667                        Ok(Expression::Ordered(o))
27668                    } else {
27669                        Ok(e)
27670                    }
27671                }
27672
27673                Action::SnowflakeFloatProtect => {
27674                    // Convert DataType::Float to DataType::Custom("FLOAT") to prevent
27675                    // Snowflake's target transform from converting it to DOUBLE.
27676                    // Non-Snowflake sources should keep their FLOAT spelling.
27677                    if let Expression::DataType(DataType::Float { .. }) = e {
27678                        Ok(Expression::DataType(DataType::Custom {
27679                            name: "FLOAT".to_string(),
27680                        }))
27681                    } else {
27682                        Ok(e)
27683                    }
27684                }
27685
27686                Action::MysqlNullsOrdering => {
27687                    // MySQL doesn't support NULLS FIRST/LAST - strip or rewrite
27688                    if let Expression::Ordered(mut o) = e {
27689                        let nulls_last = o.nulls_first == Some(false);
27690                        let desc = o.desc;
27691                        // MySQL default: ASC -> NULLS LAST, DESC -> NULLS FIRST
27692                        // If requested ordering matches default, just strip NULLS clause
27693                        let matches_default = if desc {
27694                            // DESC default is NULLS FIRST, so nulls_first=true matches
27695                            o.nulls_first == Some(true)
27696                        } else {
27697                            // ASC default is NULLS LAST, so nulls_first=false matches
27698                            nulls_last
27699                        };
27700                        if matches_default {
27701                            o.nulls_first = None;
27702                            Ok(Expression::Ordered(o))
27703                        } else {
27704                            // Need CASE WHEN x IS NULL THEN 0/1 ELSE 0/1 END, x
27705                            // For ASC NULLS FIRST: ORDER BY CASE WHEN x IS NULL THEN 0 ELSE 1 END, x ASC
27706                            // For DESC NULLS LAST: ORDER BY CASE WHEN x IS NULL THEN 1 ELSE 0 END, x DESC
27707                            let null_val = if desc { 1 } else { 0 };
27708                            let non_null_val = if desc { 0 } else { 1 };
27709                            let _case_expr = Expression::Case(Box::new(Case {
27710                                operand: None,
27711                                whens: vec![(
27712                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
27713                                        this: o.this.clone(),
27714                                        not: false,
27715                                        postfix_form: false,
27716                                    })),
27717                                    Expression::number(null_val),
27718                                )],
27719                                else_: Some(Expression::number(non_null_val)),
27720                                comments: Vec::new(),
27721                                inferred_type: None,
27722                            }));
27723                            o.nulls_first = None;
27724                            // Return a tuple of [case_expr, ordered_expr]
27725                            // We need to return both as part of the ORDER BY
27726                            // But since transform_recursive processes individual expressions,
27727                            // we can't easily add extra ORDER BY items here.
27728                            // Instead, strip the nulls_first
27729                            o.nulls_first = None;
27730                            Ok(Expression::Ordered(o))
27731                        }
27732                    } else {
27733                        Ok(e)
27734                    }
27735                }
27736
27737                Action::MysqlNullsLastRewrite => {
27738                    // DuckDB -> MySQL: Add CASE WHEN IS NULL THEN 1 ELSE 0 END to ORDER BY
27739                    // to simulate NULLS LAST for ASC ordering
27740                    if let Expression::WindowFunction(mut wf) = e {
27741                        let mut new_order_by = Vec::new();
27742                        for o in wf.over.order_by {
27743                            if !o.desc {
27744                                // ASC: DuckDB has NULLS LAST, MySQL has NULLS FIRST
27745                                // Add CASE WHEN expr IS NULL THEN 1 ELSE 0 END before expr
27746                                let case_expr = Expression::Case(Box::new(Case {
27747                                    operand: None,
27748                                    whens: vec![(
27749                                        Expression::IsNull(Box::new(crate::expressions::IsNull {
27750                                            this: o.this.clone(),
27751                                            not: false,
27752                                            postfix_form: false,
27753                                        })),
27754                                        Expression::Literal(Box::new(Literal::Number(
27755                                            "1".to_string(),
27756                                        ))),
27757                                    )],
27758                                    else_: Some(Expression::Literal(Box::new(Literal::Number(
27759                                        "0".to_string(),
27760                                    )))),
27761                                    comments: Vec::new(),
27762                                    inferred_type: None,
27763                                }));
27764                                new_order_by.push(crate::expressions::Ordered {
27765                                    this: case_expr,
27766                                    desc: false,
27767                                    nulls_first: None,
27768                                    explicit_asc: false,
27769                                    with_fill: None,
27770                                });
27771                                let mut ordered = o;
27772                                ordered.nulls_first = None;
27773                                new_order_by.push(ordered);
27774                            } else {
27775                                // DESC: DuckDB has NULLS LAST, MySQL also has NULLS LAST (NULLs smallest in DESC)
27776                                // No change needed
27777                                let mut ordered = o;
27778                                ordered.nulls_first = None;
27779                                new_order_by.push(ordered);
27780                            }
27781                        }
27782                        wf.over.order_by = new_order_by;
27783                        Ok(Expression::WindowFunction(wf))
27784                    } else {
27785                        Ok(e)
27786                    }
27787                }
27788
27789                Action::RespectNullsConvert => {
27790                    // RESPECT NULLS -> strip for SQLite (FIRST_VALUE(c) OVER (...))
27791                    if let Expression::WindowFunction(mut wf) = e {
27792                        match &mut wf.this {
27793                            Expression::FirstValue(ref mut vf) => {
27794                                if vf.ignore_nulls == Some(false) {
27795                                    vf.ignore_nulls = None;
27796                                    // For SQLite, we'd need to add NULLS LAST to ORDER BY in the OVER clause
27797                                    // but that's handled by the generator's NULLS ordering
27798                                }
27799                            }
27800                            Expression::LastValue(ref mut vf) => {
27801                                if vf.ignore_nulls == Some(false) {
27802                                    vf.ignore_nulls = None;
27803                                }
27804                            }
27805                            _ => {}
27806                        }
27807                        Ok(Expression::WindowFunction(wf))
27808                    } else {
27809                        Ok(e)
27810                    }
27811                }
27812
27813                Action::SnowflakeWindowFrameStrip => {
27814                    // Strip the default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
27815                    // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when targeting Snowflake
27816                    if let Expression::WindowFunction(mut wf) = e {
27817                        wf.over.frame = None;
27818                        Ok(Expression::WindowFunction(wf))
27819                    } else {
27820                        Ok(e)
27821                    }
27822                }
27823
27824                Action::SnowflakeWindowFrameAdd => {
27825                    // Add default ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
27826                    // for FIRST_VALUE/LAST_VALUE/NTH_VALUE when transpiling from Snowflake to non-Snowflake
27827                    if let Expression::WindowFunction(mut wf) = e {
27828                        wf.over.frame = Some(crate::expressions::WindowFrame {
27829                            kind: crate::expressions::WindowFrameKind::Rows,
27830                            start: crate::expressions::WindowFrameBound::UnboundedPreceding,
27831                            end: Some(crate::expressions::WindowFrameBound::UnboundedFollowing),
27832                            exclude: None,
27833                            kind_text: None,
27834                            start_side_text: None,
27835                            end_side_text: None,
27836                        });
27837                        Ok(Expression::WindowFunction(wf))
27838                    } else {
27839                        Ok(e)
27840                    }
27841                }
27842
27843                Action::CreateTableStripComment => {
27844                    // Strip COMMENT column constraint, USING, PARTITIONED BY for DuckDB
27845                    if let Expression::CreateTable(mut ct) = e {
27846                        for col in &mut ct.columns {
27847                            col.comment = None;
27848                            col.constraints.retain(|c| {
27849                                !matches!(c, crate::expressions::ColumnConstraint::Comment(_))
27850                            });
27851                            // Also remove Comment from constraint_order
27852                            col.constraint_order.retain(|c| {
27853                                !matches!(c, crate::expressions::ConstraintType::Comment)
27854                            });
27855                        }
27856                        // Strip properties (USING, PARTITIONED BY, etc.)
27857                        ct.properties.clear();
27858                        Ok(Expression::CreateTable(ct))
27859                    } else {
27860                        Ok(e)
27861                    }
27862                }
27863
27864                Action::AlterTableToSpRename => {
27865                    // ALTER TABLE db.t1 RENAME TO db.t2 -> EXEC sp_rename 'db.t1', 't2'
27866                    if let Expression::AlterTable(ref at) = e {
27867                        if let Some(crate::expressions::AlterTableAction::RenameTable(
27868                            ref new_tbl,
27869                        )) = at.actions.first()
27870                        {
27871                            // Build the old table name using TSQL bracket quoting
27872                            let old_name = if let Some(ref schema) = at.name.schema {
27873                                if at.name.name.quoted || schema.quoted {
27874                                    format!("[{}].[{}]", schema.name, at.name.name.name)
27875                                } else {
27876                                    format!("{}.{}", schema.name, at.name.name.name)
27877                                }
27878                            } else {
27879                                if at.name.name.quoted {
27880                                    format!("[{}]", at.name.name.name)
27881                                } else {
27882                                    at.name.name.name.clone()
27883                                }
27884                            };
27885                            let new_name = new_tbl.name.name.clone();
27886                            // EXEC sp_rename 'old_name', 'new_name'
27887                            let sql = format!("EXEC sp_rename '{}', '{}'", old_name, new_name);
27888                            Ok(Expression::Raw(crate::expressions::Raw { sql }))
27889                        } else {
27890                            Ok(e)
27891                        }
27892                    } else {
27893                        Ok(e)
27894                    }
27895                }
27896
27897                Action::SnowflakeIntervalFormat => {
27898                    // INTERVAL '2' HOUR -> INTERVAL '2 HOUR' for Snowflake
27899                    if let Expression::Interval(mut iv) = e {
27900                        if let (Some(Expression::Literal(lit)), Some(ref unit_spec)) =
27901                            (&iv.this, &iv.unit)
27902                        {
27903                            if let Literal::String(ref val) = lit.as_ref() {
27904                                let unit_str = match unit_spec {
27905                                    crate::expressions::IntervalUnitSpec::Simple {
27906                                        unit, ..
27907                                    } => match unit {
27908                                        crate::expressions::IntervalUnit::Year => "YEAR",
27909                                        crate::expressions::IntervalUnit::Quarter => "QUARTER",
27910                                        crate::expressions::IntervalUnit::Month => "MONTH",
27911                                        crate::expressions::IntervalUnit::Week => "WEEK",
27912                                        crate::expressions::IntervalUnit::Day => "DAY",
27913                                        crate::expressions::IntervalUnit::Hour => "HOUR",
27914                                        crate::expressions::IntervalUnit::Minute => "MINUTE",
27915                                        crate::expressions::IntervalUnit::Second => "SECOND",
27916                                        crate::expressions::IntervalUnit::Millisecond => {
27917                                            "MILLISECOND"
27918                                        }
27919                                        crate::expressions::IntervalUnit::Microsecond => {
27920                                            "MICROSECOND"
27921                                        }
27922                                        crate::expressions::IntervalUnit::Nanosecond => {
27923                                            "NANOSECOND"
27924                                        }
27925                                    },
27926                                    _ => "",
27927                                };
27928                                if !unit_str.is_empty() {
27929                                    let combined = format!("{} {}", val, unit_str);
27930                                    iv.this = Some(Expression::Literal(Box::new(Literal::String(
27931                                        combined,
27932                                    ))));
27933                                    iv.unit = None;
27934                                }
27935                            }
27936                        }
27937                        Ok(Expression::Interval(iv))
27938                    } else {
27939                        Ok(e)
27940                    }
27941                }
27942
27943                Action::ArrayConcatBracketConvert => {
27944                    // Expression::Array/ArrayFunc -> target-specific
27945                    // For PostgreSQL: Array -> ArrayFunc (bracket_notation: false)
27946                    // For Redshift: Array/ArrayFunc -> Function("ARRAY", args) to produce ARRAY(1, 2) with parens
27947                    match e {
27948                        Expression::Array(arr) => {
27949                            if matches!(target, DialectType::Redshift) {
27950                                Ok(Expression::Function(Box::new(Function::new(
27951                                    "ARRAY".to_string(),
27952                                    arr.expressions,
27953                                ))))
27954                            } else {
27955                                Ok(Expression::ArrayFunc(Box::new(
27956                                    crate::expressions::ArrayConstructor {
27957                                        expressions: arr.expressions,
27958                                        bracket_notation: false,
27959                                        use_list_keyword: false,
27960                                    },
27961                                )))
27962                            }
27963                        }
27964                        Expression::ArrayFunc(arr) => {
27965                            // Only for Redshift: convert bracket-notation ArrayFunc to Function("ARRAY")
27966                            if matches!(target, DialectType::Redshift) {
27967                                Ok(Expression::Function(Box::new(Function::new(
27968                                    "ARRAY".to_string(),
27969                                    arr.expressions,
27970                                ))))
27971                            } else {
27972                                Ok(Expression::ArrayFunc(arr))
27973                            }
27974                        }
27975                        _ => Ok(e),
27976                    }
27977                }
27978
27979                Action::BitAggFloatCast => {
27980                    // BIT_OR/BIT_AND/BIT_XOR with float/decimal cast arg -> wrap with ROUND+INT cast for DuckDB
27981                    // For FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
27982                    // For DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
27983                    let int_type = DataType::Int {
27984                        length: None,
27985                        integer_spelling: false,
27986                    };
27987                    let wrap_agg = |agg_this: Expression, int_dt: DataType| -> Expression {
27988                        if let Expression::Cast(c) = agg_this {
27989                            match &c.to {
27990                                DataType::Float { .. }
27991                                | DataType::Double { .. }
27992                                | DataType::Custom { .. } => {
27993                                    // FLOAT/DOUBLE/REAL: CAST(ROUND(CAST(val AS type)) AS INT)
27994                                    // Change FLOAT to REAL (Float with real_spelling=true) for DuckDB generator
27995                                    let inner_type = match &c.to {
27996                                        DataType::Float {
27997                                            precision, scale, ..
27998                                        } => DataType::Float {
27999                                            precision: *precision,
28000                                            scale: *scale,
28001                                            real_spelling: true,
28002                                        },
28003                                        other => other.clone(),
28004                                    };
28005                                    let inner_cast =
28006                                        Expression::Cast(Box::new(crate::expressions::Cast {
28007                                            this: c.this.clone(),
28008                                            to: inner_type,
28009                                            trailing_comments: Vec::new(),
28010                                            double_colon_syntax: false,
28011                                            format: None,
28012                                            default: None,
28013                                            inferred_type: None,
28014                                        }));
28015                                    let rounded = Expression::Function(Box::new(Function::new(
28016                                        "ROUND".to_string(),
28017                                        vec![inner_cast],
28018                                    )));
28019                                    Expression::Cast(Box::new(crate::expressions::Cast {
28020                                        this: rounded,
28021                                        to: int_dt,
28022                                        trailing_comments: Vec::new(),
28023                                        double_colon_syntax: false,
28024                                        format: None,
28025                                        default: None,
28026                                        inferred_type: None,
28027                                    }))
28028                                }
28029                                DataType::Decimal { .. } => {
28030                                    // DECIMAL: CAST(CAST(val AS DECIMAL(p,s)) AS INT)
28031                                    Expression::Cast(Box::new(crate::expressions::Cast {
28032                                        this: Expression::Cast(c),
28033                                        to: int_dt,
28034                                        trailing_comments: Vec::new(),
28035                                        double_colon_syntax: false,
28036                                        format: None,
28037                                        default: None,
28038                                        inferred_type: None,
28039                                    }))
28040                                }
28041                                _ => Expression::Cast(c),
28042                            }
28043                        } else {
28044                            agg_this
28045                        }
28046                    };
28047                    match e {
28048                        Expression::BitwiseOrAgg(mut f) => {
28049                            f.this = wrap_agg(f.this, int_type);
28050                            Ok(Expression::BitwiseOrAgg(f))
28051                        }
28052                        Expression::BitwiseAndAgg(mut f) => {
28053                            let int_type = DataType::Int {
28054                                length: None,
28055                                integer_spelling: false,
28056                            };
28057                            f.this = wrap_agg(f.this, int_type);
28058                            Ok(Expression::BitwiseAndAgg(f))
28059                        }
28060                        Expression::BitwiseXorAgg(mut f) => {
28061                            let int_type = DataType::Int {
28062                                length: None,
28063                                integer_spelling: false,
28064                            };
28065                            f.this = wrap_agg(f.this, int_type);
28066                            Ok(Expression::BitwiseXorAgg(f))
28067                        }
28068                        _ => Ok(e),
28069                    }
28070                }
28071
28072                Action::BitAggSnowflakeRename => {
28073                    // BIT_OR -> BITORAGG, BIT_AND -> BITANDAGG, BIT_XOR -> BITXORAGG for Snowflake
28074                    match e {
28075                        Expression::BitwiseOrAgg(f) => Ok(Expression::Function(Box::new(
28076                            Function::new("BITORAGG".to_string(), vec![f.this]),
28077                        ))),
28078                        Expression::BitwiseAndAgg(f) => Ok(Expression::Function(Box::new(
28079                            Function::new("BITANDAGG".to_string(), vec![f.this]),
28080                        ))),
28081                        Expression::BitwiseXorAgg(f) => Ok(Expression::Function(Box::new(
28082                            Function::new("BITXORAGG".to_string(), vec![f.this]),
28083                        ))),
28084                        _ => Ok(e),
28085                    }
28086                }
28087
28088                Action::StrftimeCastTimestamp => {
28089                    // CAST(x AS TIMESTAMP) -> CAST(x AS TIMESTAMP_NTZ) for Spark
28090                    if let Expression::Cast(mut c) = e {
28091                        if matches!(
28092                            c.to,
28093                            DataType::Timestamp {
28094                                timezone: false,
28095                                ..
28096                            }
28097                        ) {
28098                            c.to = DataType::Custom {
28099                                name: "TIMESTAMP_NTZ".to_string(),
28100                            };
28101                        }
28102                        Ok(Expression::Cast(c))
28103                    } else {
28104                        Ok(e)
28105                    }
28106                }
28107
28108                Action::DecimalDefaultPrecision => {
28109                    // DECIMAL without precision -> DECIMAL(18, 3) for Snowflake
28110                    if let Expression::Cast(mut c) = e {
28111                        if matches!(
28112                            c.to,
28113                            DataType::Decimal {
28114                                precision: None,
28115                                ..
28116                            }
28117                        ) {
28118                            c.to = DataType::Decimal {
28119                                precision: Some(18),
28120                                scale: Some(3),
28121                            };
28122                        }
28123                        Ok(Expression::Cast(c))
28124                    } else {
28125                        Ok(e)
28126                    }
28127                }
28128
28129                Action::FilterToIff => {
28130                    // FILTER(WHERE cond) -> rewrite aggregate: AGG(IFF(cond, val, NULL))
28131                    if let Expression::Filter(f) = e {
28132                        let condition = *f.expression;
28133                        let agg = *f.this;
28134                        // Strip WHERE from condition
28135                        let cond = match condition {
28136                            Expression::Where(w) => w.this,
28137                            other => other,
28138                        };
28139                        // Extract the aggregate function and its argument
28140                        // We want AVG(IFF(condition, x, NULL))
28141                        match agg {
28142                            Expression::Function(mut func) => {
28143                                if !func.args.is_empty() {
28144                                    let orig_arg = func.args[0].clone();
28145                                    let iff_call = Expression::Function(Box::new(Function::new(
28146                                        "IFF".to_string(),
28147                                        vec![cond, orig_arg, Expression::Null(Null)],
28148                                    )));
28149                                    func.args[0] = iff_call;
28150                                    Ok(Expression::Function(func))
28151                                } else {
28152                                    Ok(Expression::Filter(Box::new(crate::expressions::Filter {
28153                                        this: Box::new(Expression::Function(func)),
28154                                        expression: Box::new(cond),
28155                                    })))
28156                                }
28157                            }
28158                            Expression::Avg(mut avg) => {
28159                                let iff_call = Expression::Function(Box::new(Function::new(
28160                                    "IFF".to_string(),
28161                                    vec![cond, avg.this.clone(), Expression::Null(Null)],
28162                                )));
28163                                avg.this = iff_call;
28164                                Ok(Expression::Avg(avg))
28165                            }
28166                            Expression::Sum(mut s) => {
28167                                let iff_call = Expression::Function(Box::new(Function::new(
28168                                    "IFF".to_string(),
28169                                    vec![cond, s.this.clone(), Expression::Null(Null)],
28170                                )));
28171                                s.this = iff_call;
28172                                Ok(Expression::Sum(s))
28173                            }
28174                            Expression::Count(mut c) => {
28175                                if let Some(ref this_expr) = c.this {
28176                                    let iff_call = Expression::Function(Box::new(Function::new(
28177                                        "IFF".to_string(),
28178                                        vec![cond, this_expr.clone(), Expression::Null(Null)],
28179                                    )));
28180                                    c.this = Some(iff_call);
28181                                }
28182                                Ok(Expression::Count(c))
28183                            }
28184                            other => {
28185                                // Fallback: keep as Filter
28186                                Ok(Expression::Filter(Box::new(crate::expressions::Filter {
28187                                    this: Box::new(other),
28188                                    expression: Box::new(cond),
28189                                })))
28190                            }
28191                        }
28192                    } else {
28193                        Ok(e)
28194                    }
28195                }
28196
28197                Action::AggFilterToIff => {
28198                    // AggFunc.filter -> IFF wrapping: AVG(x) FILTER(WHERE cond) -> AVG(IFF(cond, x, NULL))
28199                    // Helper macro to handle the common AggFunc case
28200                    macro_rules! handle_agg_filter_to_iff {
28201                        ($variant:ident, $agg:expr) => {{
28202                            let mut agg = $agg;
28203                            if let Some(filter_cond) = agg.filter.take() {
28204                                let iff_call = Expression::Function(Box::new(Function::new(
28205                                    "IFF".to_string(),
28206                                    vec![filter_cond, agg.this.clone(), Expression::Null(Null)],
28207                                )));
28208                                agg.this = iff_call;
28209                            }
28210                            Ok(Expression::$variant(agg))
28211                        }};
28212                    }
28213
28214                    match e {
28215                        Expression::Avg(agg) => handle_agg_filter_to_iff!(Avg, agg),
28216                        Expression::Sum(agg) => handle_agg_filter_to_iff!(Sum, agg),
28217                        Expression::Min(agg) => handle_agg_filter_to_iff!(Min, agg),
28218                        Expression::Max(agg) => handle_agg_filter_to_iff!(Max, agg),
28219                        Expression::ArrayAgg(agg) => handle_agg_filter_to_iff!(ArrayAgg, agg),
28220                        Expression::CountIf(agg) => handle_agg_filter_to_iff!(CountIf, agg),
28221                        Expression::Stddev(agg) => handle_agg_filter_to_iff!(Stddev, agg),
28222                        Expression::StddevPop(agg) => handle_agg_filter_to_iff!(StddevPop, agg),
28223                        Expression::StddevSamp(agg) => handle_agg_filter_to_iff!(StddevSamp, agg),
28224                        Expression::Variance(agg) => handle_agg_filter_to_iff!(Variance, agg),
28225                        Expression::VarPop(agg) => handle_agg_filter_to_iff!(VarPop, agg),
28226                        Expression::VarSamp(agg) => handle_agg_filter_to_iff!(VarSamp, agg),
28227                        Expression::Median(agg) => handle_agg_filter_to_iff!(Median, agg),
28228                        Expression::Mode(agg) => handle_agg_filter_to_iff!(Mode, agg),
28229                        Expression::First(agg) => handle_agg_filter_to_iff!(First, agg),
28230                        Expression::Last(agg) => handle_agg_filter_to_iff!(Last, agg),
28231                        Expression::AnyValue(agg) => handle_agg_filter_to_iff!(AnyValue, agg),
28232                        Expression::ApproxDistinct(agg) => {
28233                            handle_agg_filter_to_iff!(ApproxDistinct, agg)
28234                        }
28235                        Expression::Count(mut c) => {
28236                            if let Some(filter_cond) = c.filter.take() {
28237                                if let Some(ref this_expr) = c.this {
28238                                    let iff_call = Expression::Function(Box::new(Function::new(
28239                                        "IFF".to_string(),
28240                                        vec![
28241                                            filter_cond,
28242                                            this_expr.clone(),
28243                                            Expression::Null(Null),
28244                                        ],
28245                                    )));
28246                                    c.this = Some(iff_call);
28247                                }
28248                            }
28249                            Ok(Expression::Count(c))
28250                        }
28251                        other => Ok(other),
28252                    }
28253                }
28254
28255                Action::JsonToGetPath => {
28256                    // JSON_EXTRACT(x, '$.key') -> GET_PATH(PARSE_JSON(x), 'key')
28257                    if let Expression::JsonExtract(je) = e {
28258                        // Convert to PARSE_JSON() wrapper:
28259                        // - JSON(x) -> PARSE_JSON(x)
28260                        // - PARSE_JSON(x) -> keep as-is
28261                        // - anything else -> wrap in PARSE_JSON()
28262                        let this = match &je.this {
28263                            Expression::Function(f)
28264                                if f.name.eq_ignore_ascii_case("JSON") && f.args.len() == 1 =>
28265                            {
28266                                Expression::Function(Box::new(Function::new(
28267                                    "PARSE_JSON".to_string(),
28268                                    f.args.clone(),
28269                                )))
28270                            }
28271                            Expression::Function(f)
28272                                if f.name.eq_ignore_ascii_case("PARSE_JSON") =>
28273                            {
28274                                je.this.clone()
28275                            }
28276                            // GET_PATH result is already JSON, don't wrap
28277                            Expression::Function(f) if f.name.eq_ignore_ascii_case("GET_PATH") => {
28278                                je.this.clone()
28279                            }
28280                            other => {
28281                                // Wrap non-JSON expressions in PARSE_JSON()
28282                                Expression::Function(Box::new(Function::new(
28283                                    "PARSE_JSON".to_string(),
28284                                    vec![other.clone()],
28285                                )))
28286                            }
28287                        };
28288                        // Convert path: extract key from JSONPath or strip $. prefix from string
28289                        let path = match &je.path {
28290                            Expression::JSONPath(jp) => {
28291                                // Extract the key from JSONPath: $root.key -> 'key'
28292                                let mut key_parts = Vec::new();
28293                                for expr in &jp.expressions {
28294                                    match expr {
28295                                        Expression::JSONPathRoot(_) => {} // skip root
28296                                        Expression::JSONPathKey(k) => {
28297                                            if let Expression::Literal(lit) = &*k.this {
28298                                                if let Literal::String(s) = lit.as_ref() {
28299                                                    key_parts.push(s.clone());
28300                                                }
28301                                            }
28302                                        }
28303                                        _ => {}
28304                                    }
28305                                }
28306                                if !key_parts.is_empty() {
28307                                    Expression::Literal(Box::new(Literal::String(
28308                                        key_parts.join("."),
28309                                    )))
28310                                } else {
28311                                    je.path.clone()
28312                                }
28313                            }
28314                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with("$.")) =>
28315                            {
28316                                let Literal::String(s) = lit.as_ref() else {
28317                                    unreachable!()
28318                                };
28319                                let stripped = Self::strip_json_wildcards(&s[2..].to_string());
28320                                Expression::Literal(Box::new(Literal::String(stripped)))
28321                            }
28322                            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(s) if s.starts_with('$')) =>
28323                            {
28324                                let Literal::String(s) = lit.as_ref() else {
28325                                    unreachable!()
28326                                };
28327                                let stripped = Self::strip_json_wildcards(&s[1..].to_string());
28328                                Expression::Literal(Box::new(Literal::String(stripped)))
28329                            }
28330                            _ => je.path.clone(),
28331                        };
28332                        Ok(Expression::Function(Box::new(Function::new(
28333                            "GET_PATH".to_string(),
28334                            vec![this, path],
28335                        ))))
28336                    } else {
28337                        Ok(e)
28338                    }
28339                }
28340
28341                Action::StructToRow => {
28342                    // DuckDB struct/dict -> BigQuery STRUCT(value AS key, ...) / Presto ROW
28343                    // Handles both Expression::Struct and Expression::MapFunc(curly_brace_syntax=true)
28344
28345                    // Extract key-value pairs from either Struct or MapFunc
28346                    let kv_pairs: Option<Vec<(String, Expression)>> = match &e {
28347                        Expression::Struct(s) => Some(
28348                            s.fields
28349                                .iter()
28350                                .map(|(opt_name, field_expr)| {
28351                                    if let Some(name) = opt_name {
28352                                        (name.clone(), field_expr.clone())
28353                                    } else if let Expression::NamedArgument(na) = field_expr {
28354                                        (na.name.name.clone(), na.value.clone())
28355                                    } else {
28356                                        (String::new(), field_expr.clone())
28357                                    }
28358                                })
28359                                .collect(),
28360                        ),
28361                        Expression::MapFunc(m) if m.curly_brace_syntax => Some(
28362                            m.keys
28363                                .iter()
28364                                .zip(m.values.iter())
28365                                .map(|(key, value)| {
28366                                    let key_name = match key {
28367                                        Expression::Literal(lit)
28368                                            if matches!(lit.as_ref(), Literal::String(_)) =>
28369                                        {
28370                                            let Literal::String(s) = lit.as_ref() else {
28371                                                unreachable!()
28372                                            };
28373                                            s.clone()
28374                                        }
28375                                        Expression::Identifier(id) => id.name.clone(),
28376                                        _ => String::new(),
28377                                    };
28378                                    (key_name, value.clone())
28379                                })
28380                                .collect(),
28381                        ),
28382                        _ => None,
28383                    };
28384
28385                    if let Some(pairs) = kv_pairs {
28386                        let mut named_args = Vec::new();
28387                        for (key_name, value) in pairs {
28388                            if matches!(target, DialectType::BigQuery) && !key_name.is_empty() {
28389                                named_args.push(Expression::Alias(Box::new(
28390                                    crate::expressions::Alias::new(
28391                                        value,
28392                                        Identifier::new(key_name),
28393                                    ),
28394                                )));
28395                            } else if matches!(target, DialectType::Presto | DialectType::Trino) {
28396                                named_args.push(value);
28397                            } else {
28398                                named_args.push(value);
28399                            }
28400                        }
28401
28402                        if matches!(target, DialectType::BigQuery) {
28403                            Ok(Expression::Function(Box::new(Function::new(
28404                                "STRUCT".to_string(),
28405                                named_args,
28406                            ))))
28407                        } else if matches!(target, DialectType::Presto | DialectType::Trino) {
28408                            // For Presto/Trino, infer types and wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
28409                            let row_func = Expression::Function(Box::new(Function::new(
28410                                "ROW".to_string(),
28411                                named_args,
28412                            )));
28413
28414                            // Try to infer types for each pair
28415                            let kv_pairs_again: Option<Vec<(String, Expression)>> = match &e {
28416                                Expression::Struct(s) => Some(
28417                                    s.fields
28418                                        .iter()
28419                                        .map(|(opt_name, field_expr)| {
28420                                            if let Some(name) = opt_name {
28421                                                (name.clone(), field_expr.clone())
28422                                            } else if let Expression::NamedArgument(na) = field_expr
28423                                            {
28424                                                (na.name.name.clone(), na.value.clone())
28425                                            } else {
28426                                                (String::new(), field_expr.clone())
28427                                            }
28428                                        })
28429                                        .collect(),
28430                                ),
28431                                Expression::MapFunc(m) if m.curly_brace_syntax => Some(
28432                                    m.keys
28433                                        .iter()
28434                                        .zip(m.values.iter())
28435                                        .map(|(key, value)| {
28436                                            let key_name = match key {
28437                                                Expression::Literal(lit)
28438                                                    if matches!(
28439                                                        lit.as_ref(),
28440                                                        Literal::String(_)
28441                                                    ) =>
28442                                                {
28443                                                    let Literal::String(s) = lit.as_ref() else {
28444                                                        unreachable!()
28445                                                    };
28446                                                    s.clone()
28447                                                }
28448                                                Expression::Identifier(id) => id.name.clone(),
28449                                                _ => String::new(),
28450                                            };
28451                                            (key_name, value.clone())
28452                                        })
28453                                        .collect(),
28454                                ),
28455                                _ => None,
28456                            };
28457
28458                            if let Some(pairs) = kv_pairs_again {
28459                                // Infer types for all values
28460                                let mut all_inferred = true;
28461                                let mut fields = Vec::new();
28462                                for (name, value) in &pairs {
28463                                    let inferred_type = match value {
28464                                        Expression::Literal(lit)
28465                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
28466                                        {
28467                                            let Literal::Number(n) = lit.as_ref() else {
28468                                                unreachable!()
28469                                            };
28470                                            if n.contains('.') {
28471                                                Some(DataType::Double {
28472                                                    precision: None,
28473                                                    scale: None,
28474                                                })
28475                                            } else {
28476                                                Some(DataType::Int {
28477                                                    length: None,
28478                                                    integer_spelling: true,
28479                                                })
28480                                            }
28481                                        }
28482                                        Expression::Literal(lit)
28483                                            if matches!(lit.as_ref(), Literal::String(_)) =>
28484                                        {
28485                                            Some(DataType::VarChar {
28486                                                length: None,
28487                                                parenthesized_length: false,
28488                                            })
28489                                        }
28490                                        Expression::Boolean(_) => Some(DataType::Boolean),
28491                                        _ => None,
28492                                    };
28493                                    if let Some(dt) = inferred_type {
28494                                        fields.push(crate::expressions::StructField::new(
28495                                            name.clone(),
28496                                            dt,
28497                                        ));
28498                                    } else {
28499                                        all_inferred = false;
28500                                        break;
28501                                    }
28502                                }
28503
28504                                if all_inferred && !fields.is_empty() {
28505                                    let row_type = DataType::Struct {
28506                                        fields,
28507                                        nested: true,
28508                                    };
28509                                    Ok(Expression::Cast(Box::new(Cast {
28510                                        this: row_func,
28511                                        to: row_type,
28512                                        trailing_comments: Vec::new(),
28513                                        double_colon_syntax: false,
28514                                        format: None,
28515                                        default: None,
28516                                        inferred_type: None,
28517                                    })))
28518                                } else {
28519                                    Ok(row_func)
28520                                }
28521                            } else {
28522                                Ok(row_func)
28523                            }
28524                        } else {
28525                            Ok(Expression::Function(Box::new(Function::new(
28526                                "ROW".to_string(),
28527                                named_args,
28528                            ))))
28529                        }
28530                    } else {
28531                        Ok(e)
28532                    }
28533                }
28534
28535                Action::SparkStructConvert => {
28536                    // Spark STRUCT(val AS name, ...) -> Presto CAST(ROW(...) AS ROW(name TYPE, ...))
28537                    // or DuckDB {'name': val, ...}
28538                    if let Expression::Function(f) = e {
28539                        // Extract name-value pairs from aliased args
28540                        let mut pairs: Vec<(String, Expression)> = Vec::new();
28541                        for arg in &f.args {
28542                            match arg {
28543                                Expression::Alias(a) => {
28544                                    pairs.push((a.alias.name.clone(), a.this.clone()));
28545                                }
28546                                _ => {
28547                                    pairs.push((String::new(), arg.clone()));
28548                                }
28549                            }
28550                        }
28551
28552                        match target {
28553                            DialectType::DuckDB => {
28554                                // Convert to DuckDB struct literal {'name': value, ...}
28555                                let mut keys = Vec::new();
28556                                let mut values = Vec::new();
28557                                for (name, value) in &pairs {
28558                                    keys.push(Expression::Literal(Box::new(Literal::String(
28559                                        name.clone(),
28560                                    ))));
28561                                    values.push(value.clone());
28562                                }
28563                                Ok(Expression::MapFunc(Box::new(
28564                                    crate::expressions::MapConstructor {
28565                                        keys,
28566                                        values,
28567                                        curly_brace_syntax: true,
28568                                        with_map_keyword: false,
28569                                    },
28570                                )))
28571                            }
28572                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
28573                                // Convert to CAST(ROW(val1, val2) AS ROW(name1 TYPE1, name2 TYPE2))
28574                                let row_args: Vec<Expression> =
28575                                    pairs.iter().map(|(_, v)| v.clone()).collect();
28576                                let row_func = Expression::Function(Box::new(Function::new(
28577                                    "ROW".to_string(),
28578                                    row_args,
28579                                )));
28580
28581                                // Infer types
28582                                let mut all_inferred = true;
28583                                let mut fields = Vec::new();
28584                                for (name, value) in &pairs {
28585                                    let inferred_type = match value {
28586                                        Expression::Literal(lit)
28587                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
28588                                        {
28589                                            let Literal::Number(n) = lit.as_ref() else {
28590                                                unreachable!()
28591                                            };
28592                                            if n.contains('.') {
28593                                                Some(DataType::Double {
28594                                                    precision: None,
28595                                                    scale: None,
28596                                                })
28597                                            } else {
28598                                                Some(DataType::Int {
28599                                                    length: None,
28600                                                    integer_spelling: true,
28601                                                })
28602                                            }
28603                                        }
28604                                        Expression::Literal(lit)
28605                                            if matches!(lit.as_ref(), Literal::String(_)) =>
28606                                        {
28607                                            Some(DataType::VarChar {
28608                                                length: None,
28609                                                parenthesized_length: false,
28610                                            })
28611                                        }
28612                                        Expression::Boolean(_) => Some(DataType::Boolean),
28613                                        _ => None,
28614                                    };
28615                                    if let Some(dt) = inferred_type {
28616                                        fields.push(crate::expressions::StructField::new(
28617                                            name.clone(),
28618                                            dt,
28619                                        ));
28620                                    } else {
28621                                        all_inferred = false;
28622                                        break;
28623                                    }
28624                                }
28625
28626                                if all_inferred && !fields.is_empty() {
28627                                    let row_type = DataType::Struct {
28628                                        fields,
28629                                        nested: true,
28630                                    };
28631                                    Ok(Expression::Cast(Box::new(Cast {
28632                                        this: row_func,
28633                                        to: row_type,
28634                                        trailing_comments: Vec::new(),
28635                                        double_colon_syntax: false,
28636                                        format: None,
28637                                        default: None,
28638                                        inferred_type: None,
28639                                    })))
28640                                } else {
28641                                    Ok(row_func)
28642                                }
28643                            }
28644                            _ => Ok(Expression::Function(f)),
28645                        }
28646                    } else {
28647                        Ok(e)
28648                    }
28649                }
28650
28651                Action::ApproxCountDistinctToApproxDistinct => {
28652                    // APPROX_COUNT_DISTINCT(x) -> APPROX_DISTINCT(x)
28653                    if let Expression::ApproxCountDistinct(f) = e {
28654                        Ok(Expression::ApproxDistinct(f))
28655                    } else {
28656                        Ok(e)
28657                    }
28658                }
28659
28660                Action::CollectListToArrayAgg => {
28661                    // COLLECT_LIST(x) -> ARRAY_AGG(x) FILTER(WHERE x IS NOT NULL)
28662                    if let Expression::AggregateFunction(f) = e {
28663                        let filter_expr = if !f.args.is_empty() {
28664                            let arg = f.args[0].clone();
28665                            Some(Expression::IsNull(Box::new(crate::expressions::IsNull {
28666                                this: arg,
28667                                not: true,
28668                                postfix_form: false,
28669                            })))
28670                        } else {
28671                            None
28672                        };
28673                        let agg = crate::expressions::AggFunc {
28674                            this: if f.args.is_empty() {
28675                                Expression::Null(crate::expressions::Null)
28676                            } else {
28677                                f.args[0].clone()
28678                            },
28679                            distinct: f.distinct,
28680                            order_by: f.order_by.clone(),
28681                            filter: filter_expr,
28682                            ignore_nulls: None,
28683                            name: None,
28684                            having_max: None,
28685                            limit: None,
28686                            inferred_type: None,
28687                        };
28688                        Ok(Expression::ArrayAgg(Box::new(agg)))
28689                    } else {
28690                        Ok(e)
28691                    }
28692                }
28693
28694                Action::CollectSetConvert => {
28695                    // COLLECT_SET(x) -> target-specific
28696                    if let Expression::AggregateFunction(f) = e {
28697                        match target {
28698                            DialectType::Presto => Ok(Expression::AggregateFunction(Box::new(
28699                                crate::expressions::AggregateFunction {
28700                                    name: "SET_AGG".to_string(),
28701                                    args: f.args,
28702                                    distinct: false,
28703                                    order_by: f.order_by,
28704                                    filter: f.filter,
28705                                    limit: f.limit,
28706                                    ignore_nulls: f.ignore_nulls,
28707                                    inferred_type: None,
28708                                },
28709                            ))),
28710                            DialectType::Snowflake => Ok(Expression::AggregateFunction(Box::new(
28711                                crate::expressions::AggregateFunction {
28712                                    name: "ARRAY_UNIQUE_AGG".to_string(),
28713                                    args: f.args,
28714                                    distinct: false,
28715                                    order_by: f.order_by,
28716                                    filter: f.filter,
28717                                    limit: f.limit,
28718                                    ignore_nulls: f.ignore_nulls,
28719                                    inferred_type: None,
28720                                },
28721                            ))),
28722                            DialectType::Trino | DialectType::DuckDB => {
28723                                let agg = crate::expressions::AggFunc {
28724                                    this: if f.args.is_empty() {
28725                                        Expression::Null(crate::expressions::Null)
28726                                    } else {
28727                                        f.args[0].clone()
28728                                    },
28729                                    distinct: true,
28730                                    order_by: Vec::new(),
28731                                    filter: None,
28732                                    ignore_nulls: None,
28733                                    name: None,
28734                                    having_max: None,
28735                                    limit: None,
28736                                    inferred_type: None,
28737                                };
28738                                Ok(Expression::ArrayAgg(Box::new(agg)))
28739                            }
28740                            _ => Ok(Expression::AggregateFunction(f)),
28741                        }
28742                    } else {
28743                        Ok(e)
28744                    }
28745                }
28746
28747                Action::PercentileConvert => {
28748                    // PERCENTILE(x, 0.5) -> QUANTILE(x, 0.5) / APPROX_PERCENTILE(x, 0.5)
28749                    if let Expression::AggregateFunction(f) = e {
28750                        let name = match target {
28751                            DialectType::DuckDB => "QUANTILE",
28752                            DialectType::Presto | DialectType::Trino => "APPROX_PERCENTILE",
28753                            _ => "PERCENTILE",
28754                        };
28755                        Ok(Expression::AggregateFunction(Box::new(
28756                            crate::expressions::AggregateFunction {
28757                                name: name.to_string(),
28758                                args: f.args,
28759                                distinct: f.distinct,
28760                                order_by: f.order_by,
28761                                filter: f.filter,
28762                                limit: f.limit,
28763                                ignore_nulls: f.ignore_nulls,
28764                                inferred_type: None,
28765                            },
28766                        )))
28767                    } else {
28768                        Ok(e)
28769                    }
28770                }
28771
28772                Action::CorrIsnanWrap => {
28773                    // CORR(a, b) -> CASE WHEN ISNAN(CORR(a, b)) THEN NULL ELSE CORR(a, b) END
28774                    // The CORR expression could be AggregateFunction, WindowFunction, or Filter-wrapped
28775                    let corr_clone = e.clone();
28776                    let isnan = Expression::Function(Box::new(Function::new(
28777                        "ISNAN".to_string(),
28778                        vec![corr_clone.clone()],
28779                    )));
28780                    let case_expr = Expression::Case(Box::new(Case {
28781                        operand: None,
28782                        whens: vec![(isnan, Expression::Null(crate::expressions::Null))],
28783                        else_: Some(corr_clone),
28784                        comments: Vec::new(),
28785                        inferred_type: None,
28786                    }));
28787                    Ok(case_expr)
28788                }
28789
28790                Action::TruncToDateTrunc => {
28791                    // TRUNC(timestamp, 'MONTH') -> DATE_TRUNC('MONTH', timestamp)
28792                    if let Expression::Function(f) = e {
28793                        if f.args.len() == 2 {
28794                            let timestamp = f.args[0].clone();
28795                            let unit_expr = f.args[1].clone();
28796
28797                            if matches!(target, DialectType::ClickHouse) {
28798                                // For ClickHouse, produce Expression::DateTrunc which the generator
28799                                // outputs as DATE_TRUNC(...) without going through the ClickHouse
28800                                // target transform that would convert it to dateTrunc
28801                                let unit_str = Self::get_unit_str_static(&unit_expr);
28802                                let dt_field = match unit_str.as_str() {
28803                                    "YEAR" => DateTimeField::Year,
28804                                    "MONTH" => DateTimeField::Month,
28805                                    "DAY" => DateTimeField::Day,
28806                                    "HOUR" => DateTimeField::Hour,
28807                                    "MINUTE" => DateTimeField::Minute,
28808                                    "SECOND" => DateTimeField::Second,
28809                                    "WEEK" => DateTimeField::Week,
28810                                    "QUARTER" => DateTimeField::Quarter,
28811                                    _ => DateTimeField::Custom(unit_str),
28812                                };
28813                                Ok(Expression::DateTrunc(Box::new(
28814                                    crate::expressions::DateTruncFunc {
28815                                        this: timestamp,
28816                                        unit: dt_field,
28817                                    },
28818                                )))
28819                            } else {
28820                                let new_args = vec![unit_expr, timestamp];
28821                                Ok(Expression::Function(Box::new(Function::new(
28822                                    "DATE_TRUNC".to_string(),
28823                                    new_args,
28824                                ))))
28825                            }
28826                        } else {
28827                            Ok(Expression::Function(f))
28828                        }
28829                    } else {
28830                        Ok(e)
28831                    }
28832                }
28833
28834                Action::ArrayContainsConvert => {
28835                    if let Expression::ArrayContains(f) = e {
28836                        match target {
28837                            DialectType::Presto | DialectType::Trino => {
28838                                // ARRAY_CONTAINS(arr, val) -> CONTAINS(arr, val)
28839                                Ok(Expression::Function(Box::new(Function::new(
28840                                    "CONTAINS".to_string(),
28841                                    vec![f.this, f.expression],
28842                                ))))
28843                            }
28844                            DialectType::Snowflake => {
28845                                // ARRAY_CONTAINS(arr, val) -> ARRAY_CONTAINS(CAST(val AS VARIANT), arr)
28846                                let cast_val =
28847                                    Expression::Cast(Box::new(crate::expressions::Cast {
28848                                        this: f.expression,
28849                                        to: crate::expressions::DataType::Custom {
28850                                            name: "VARIANT".to_string(),
28851                                        },
28852                                        trailing_comments: Vec::new(),
28853                                        double_colon_syntax: false,
28854                                        format: None,
28855                                        default: None,
28856                                        inferred_type: None,
28857                                    }));
28858                                Ok(Expression::Function(Box::new(Function::new(
28859                                    "ARRAY_CONTAINS".to_string(),
28860                                    vec![cast_val, f.this],
28861                                ))))
28862                            }
28863                            _ => Ok(Expression::ArrayContains(f)),
28864                        }
28865                    } else {
28866                        Ok(e)
28867                    }
28868                }
28869
28870                Action::ArrayExceptConvert => {
28871                    if let Expression::ArrayExcept(f) = e {
28872                        let source_arr = f.this;
28873                        let exclude_arr = f.expression;
28874                        match target {
28875                            DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
28876                                // Snowflake ARRAY_EXCEPT -> DuckDB bag semantics:
28877                                // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
28878                                // ELSE LIST_TRANSFORM(LIST_FILTER(
28879                                //   LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source))),
28880                                //   pair -> (LENGTH(LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1]))
28881                                //            > LENGTH(LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])))),
28882                                //   pair -> pair[1])
28883                                // END
28884
28885                                // Build null check
28886                                let source_is_null =
28887                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
28888                                        this: source_arr.clone(),
28889                                        not: false,
28890                                        postfix_form: false,
28891                                    }));
28892                                let exclude_is_null =
28893                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
28894                                        this: exclude_arr.clone(),
28895                                        not: false,
28896                                        postfix_form: false,
28897                                    }));
28898                                let null_check =
28899                                    Expression::Or(Box::new(crate::expressions::BinaryOp {
28900                                        left: source_is_null,
28901                                        right: exclude_is_null,
28902                                        left_comments: vec![],
28903                                        operator_comments: vec![],
28904                                        trailing_comments: vec![],
28905                                        inferred_type: None,
28906                                    }));
28907
28908                                // GENERATE_SERIES(1, LENGTH(source))
28909                                let gen_series = Expression::Function(Box::new(Function::new(
28910                                    "GENERATE_SERIES".to_string(),
28911                                    vec![
28912                                        Expression::number(1),
28913                                        Expression::Function(Box::new(Function::new(
28914                                            "LENGTH".to_string(),
28915                                            vec![source_arr.clone()],
28916                                        ))),
28917                                    ],
28918                                )));
28919
28920                                // LIST_ZIP(source, GENERATE_SERIES(1, LENGTH(source)))
28921                                let list_zip = Expression::Function(Box::new(Function::new(
28922                                    "LIST_ZIP".to_string(),
28923                                    vec![source_arr.clone(), gen_series],
28924                                )));
28925
28926                                // pair[1] and pair[2]
28927                                let pair_col = Expression::column("pair");
28928                                let pair_1 = Expression::Subscript(Box::new(
28929                                    crate::expressions::Subscript {
28930                                        this: pair_col.clone(),
28931                                        index: Expression::number(1),
28932                                    },
28933                                ));
28934                                let pair_2 = Expression::Subscript(Box::new(
28935                                    crate::expressions::Subscript {
28936                                        this: pair_col.clone(),
28937                                        index: Expression::number(2),
28938                                    },
28939                                ));
28940
28941                                // source[1:pair[2]]
28942                                let source_slice = Expression::ArraySlice(Box::new(
28943                                    crate::expressions::ArraySlice {
28944                                        this: source_arr.clone(),
28945                                        start: Some(Expression::number(1)),
28946                                        end: Some(pair_2),
28947                                    },
28948                                ));
28949
28950                                let e_col = Expression::column("e");
28951
28952                                // e -> e IS NOT DISTINCT FROM pair[1]
28953                                let inner_lambda1 =
28954                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
28955                                        parameters: vec![crate::expressions::Identifier::new("e")],
28956                                        body: Expression::NullSafeEq(Box::new(
28957                                            crate::expressions::BinaryOp {
28958                                                left: e_col.clone(),
28959                                                right: pair_1.clone(),
28960                                                left_comments: vec![],
28961                                                operator_comments: vec![],
28962                                                trailing_comments: vec![],
28963                                                inferred_type: None,
28964                                            },
28965                                        )),
28966                                        colon: false,
28967                                        parameter_types: vec![],
28968                                    }));
28969
28970                                // LIST_FILTER(source[1:pair[2]], e -> e IS NOT DISTINCT FROM pair[1])
28971                                let inner_filter1 = Expression::Function(Box::new(Function::new(
28972                                    "LIST_FILTER".to_string(),
28973                                    vec![source_slice, inner_lambda1],
28974                                )));
28975
28976                                // LENGTH(LIST_FILTER(source[1:pair[2]], ...))
28977                                let len1 = Expression::Function(Box::new(Function::new(
28978                                    "LENGTH".to_string(),
28979                                    vec![inner_filter1],
28980                                )));
28981
28982                                // e -> e IS NOT DISTINCT FROM pair[1]
28983                                let inner_lambda2 =
28984                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
28985                                        parameters: vec![crate::expressions::Identifier::new("e")],
28986                                        body: Expression::NullSafeEq(Box::new(
28987                                            crate::expressions::BinaryOp {
28988                                                left: e_col,
28989                                                right: pair_1.clone(),
28990                                                left_comments: vec![],
28991                                                operator_comments: vec![],
28992                                                trailing_comments: vec![],
28993                                                inferred_type: None,
28994                                            },
28995                                        )),
28996                                        colon: false,
28997                                        parameter_types: vec![],
28998                                    }));
28999
29000                                // LIST_FILTER(exclude, e -> e IS NOT DISTINCT FROM pair[1])
29001                                let inner_filter2 = Expression::Function(Box::new(Function::new(
29002                                    "LIST_FILTER".to_string(),
29003                                    vec![exclude_arr.clone(), inner_lambda2],
29004                                )));
29005
29006                                // LENGTH(LIST_FILTER(exclude, ...))
29007                                let len2 = Expression::Function(Box::new(Function::new(
29008                                    "LENGTH".to_string(),
29009                                    vec![inner_filter2],
29010                                )));
29011
29012                                // (LENGTH(...) > LENGTH(...))
29013                                let cond = Expression::Paren(Box::new(Paren {
29014                                    this: Expression::Gt(Box::new(crate::expressions::BinaryOp {
29015                                        left: len1,
29016                                        right: len2,
29017                                        left_comments: vec![],
29018                                        operator_comments: vec![],
29019                                        trailing_comments: vec![],
29020                                        inferred_type: None,
29021                                    })),
29022                                    trailing_comments: vec![],
29023                                }));
29024
29025                                // pair -> (condition)
29026                                let filter_lambda =
29027                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29028                                        parameters: vec![crate::expressions::Identifier::new(
29029                                            "pair",
29030                                        )],
29031                                        body: cond,
29032                                        colon: false,
29033                                        parameter_types: vec![],
29034                                    }));
29035
29036                                // LIST_FILTER(LIST_ZIP(...), pair -> ...)
29037                                let outer_filter = Expression::Function(Box::new(Function::new(
29038                                    "LIST_FILTER".to_string(),
29039                                    vec![list_zip, filter_lambda],
29040                                )));
29041
29042                                // pair -> pair[1]
29043                                let transform_lambda =
29044                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29045                                        parameters: vec![crate::expressions::Identifier::new(
29046                                            "pair",
29047                                        )],
29048                                        body: pair_1,
29049                                        colon: false,
29050                                        parameter_types: vec![],
29051                                    }));
29052
29053                                // LIST_TRANSFORM(LIST_FILTER(...), pair -> pair[1])
29054                                let list_transform = Expression::Function(Box::new(Function::new(
29055                                    "LIST_TRANSFORM".to_string(),
29056                                    vec![outer_filter, transform_lambda],
29057                                )));
29058
29059                                Ok(Expression::Case(Box::new(Case {
29060                                    operand: None,
29061                                    whens: vec![(null_check, Expression::Null(Null))],
29062                                    else_: Some(list_transform),
29063                                    comments: Vec::new(),
29064                                    inferred_type: None,
29065                                })))
29066                            }
29067                            DialectType::DuckDB => {
29068                                // ARRAY_EXCEPT(source, exclude) -> set semantics for DuckDB:
29069                                // CASE WHEN source IS NULL OR exclude IS NULL THEN NULL
29070                                // ELSE LIST_FILTER(LIST_DISTINCT(source),
29071                                //   e -> LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)) = 0)
29072                                // END
29073
29074                                // Build: source IS NULL
29075                                let source_is_null =
29076                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
29077                                        this: source_arr.clone(),
29078                                        not: false,
29079                                        postfix_form: false,
29080                                    }));
29081                                // Build: exclude IS NULL
29082                                let exclude_is_null =
29083                                    Expression::IsNull(Box::new(crate::expressions::IsNull {
29084                                        this: exclude_arr.clone(),
29085                                        not: false,
29086                                        postfix_form: false,
29087                                    }));
29088                                // source IS NULL OR exclude IS NULL
29089                                let null_check =
29090                                    Expression::Or(Box::new(crate::expressions::BinaryOp {
29091                                        left: source_is_null,
29092                                        right: exclude_is_null,
29093                                        left_comments: vec![],
29094                                        operator_comments: vec![],
29095                                        trailing_comments: vec![],
29096                                        inferred_type: None,
29097                                    }));
29098
29099                                // LIST_DISTINCT(source)
29100                                let list_distinct = Expression::Function(Box::new(Function::new(
29101                                    "LIST_DISTINCT".to_string(),
29102                                    vec![source_arr.clone()],
29103                                )));
29104
29105                                // x IS NOT DISTINCT FROM e
29106                                let x_col = Expression::column("x");
29107                                let e_col = Expression::column("e");
29108                                let is_not_distinct = Expression::NullSafeEq(Box::new(
29109                                    crate::expressions::BinaryOp {
29110                                        left: x_col,
29111                                        right: e_col.clone(),
29112                                        left_comments: vec![],
29113                                        operator_comments: vec![],
29114                                        trailing_comments: vec![],
29115                                        inferred_type: None,
29116                                    },
29117                                ));
29118
29119                                // x -> x IS NOT DISTINCT FROM e
29120                                let inner_lambda =
29121                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29122                                        parameters: vec![crate::expressions::Identifier::new("x")],
29123                                        body: is_not_distinct,
29124                                        colon: false,
29125                                        parameter_types: vec![],
29126                                    }));
29127
29128                                // LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e)
29129                                let inner_list_filter =
29130                                    Expression::Function(Box::new(Function::new(
29131                                        "LIST_FILTER".to_string(),
29132                                        vec![exclude_arr.clone(), inner_lambda],
29133                                    )));
29134
29135                                // LENGTH(LIST_FILTER(exclude, x -> x IS NOT DISTINCT FROM e))
29136                                let len_inner = Expression::Function(Box::new(Function::new(
29137                                    "LENGTH".to_string(),
29138                                    vec![inner_list_filter],
29139                                )));
29140
29141                                // LENGTH(...) = 0
29142                                let eq_zero =
29143                                    Expression::Eq(Box::new(crate::expressions::BinaryOp {
29144                                        left: len_inner,
29145                                        right: Expression::number(0),
29146                                        left_comments: vec![],
29147                                        operator_comments: vec![],
29148                                        trailing_comments: vec![],
29149                                        inferred_type: None,
29150                                    }));
29151
29152                                // e -> LENGTH(LIST_FILTER(...)) = 0
29153                                let outer_lambda =
29154                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29155                                        parameters: vec![crate::expressions::Identifier::new("e")],
29156                                        body: eq_zero,
29157                                        colon: false,
29158                                        parameter_types: vec![],
29159                                    }));
29160
29161                                // LIST_FILTER(LIST_DISTINCT(source), e -> ...)
29162                                let outer_list_filter =
29163                                    Expression::Function(Box::new(Function::new(
29164                                        "LIST_FILTER".to_string(),
29165                                        vec![list_distinct, outer_lambda],
29166                                    )));
29167
29168                                // CASE WHEN ... IS NULL ... THEN NULL ELSE LIST_FILTER(...) END
29169                                Ok(Expression::Case(Box::new(Case {
29170                                    operand: None,
29171                                    whens: vec![(null_check, Expression::Null(Null))],
29172                                    else_: Some(outer_list_filter),
29173                                    comments: Vec::new(),
29174                                    inferred_type: None,
29175                                })))
29176                            }
29177                            DialectType::Snowflake => {
29178                                // Snowflake: ARRAY_EXCEPT(source, exclude) - keep as-is
29179                                Ok(Expression::ArrayExcept(Box::new(
29180                                    crate::expressions::BinaryFunc {
29181                                        this: source_arr,
29182                                        expression: exclude_arr,
29183                                        original_name: None,
29184                                        inferred_type: None,
29185                                    },
29186                                )))
29187                            }
29188                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29189                                // Presto/Trino: ARRAY_EXCEPT(source, exclude) - keep function name, array syntax already converted
29190                                Ok(Expression::Function(Box::new(Function::new(
29191                                    "ARRAY_EXCEPT".to_string(),
29192                                    vec![source_arr, exclude_arr],
29193                                ))))
29194                            }
29195                            _ => Ok(Expression::ArrayExcept(Box::new(
29196                                crate::expressions::BinaryFunc {
29197                                    this: source_arr,
29198                                    expression: exclude_arr,
29199                                    original_name: None,
29200                                    inferred_type: None,
29201                                },
29202                            ))),
29203                        }
29204                    } else {
29205                        Ok(e)
29206                    }
29207                }
29208
29209                Action::RegexpLikeExasolAnchor => {
29210                    // RegexpLike -> Exasol: wrap pattern with .*...*
29211                    // Exasol REGEXP_LIKE does full-string match, but RLIKE/REGEXP from other
29212                    // dialects does partial match, so we need to anchor with .* on both sides
29213                    if let Expression::RegexpLike(mut f) = e {
29214                        match &f.pattern {
29215                            Expression::Literal(lit)
29216                                if matches!(lit.as_ref(), Literal::String(_)) =>
29217                            {
29218                                let Literal::String(s) = lit.as_ref() else {
29219                                    unreachable!()
29220                                };
29221                                // String literal: wrap with .*...*
29222                                f.pattern = Expression::Literal(Box::new(Literal::String(
29223                                    format!(".*{}.*", s),
29224                                )));
29225                            }
29226                            _ => {
29227                                // Non-literal: wrap with CONCAT('.*', pattern, '.*')
29228                                f.pattern =
29229                                    Expression::Paren(Box::new(crate::expressions::Paren {
29230                                        this: Expression::Concat(Box::new(
29231                                            crate::expressions::BinaryOp {
29232                                                left: Expression::Concat(Box::new(
29233                                                    crate::expressions::BinaryOp {
29234                                                        left: Expression::Literal(Box::new(
29235                                                            Literal::String(".*".to_string()),
29236                                                        )),
29237                                                        right: f.pattern,
29238                                                        left_comments: vec![],
29239                                                        operator_comments: vec![],
29240                                                        trailing_comments: vec![],
29241                                                        inferred_type: None,
29242                                                    },
29243                                                )),
29244                                                right: Expression::Literal(Box::new(
29245                                                    Literal::String(".*".to_string()),
29246                                                )),
29247                                                left_comments: vec![],
29248                                                operator_comments: vec![],
29249                                                trailing_comments: vec![],
29250                                                inferred_type: None,
29251                                            },
29252                                        )),
29253                                        trailing_comments: vec![],
29254                                    }));
29255                            }
29256                        }
29257                        Ok(Expression::RegexpLike(f))
29258                    } else {
29259                        Ok(e)
29260                    }
29261                }
29262
29263                Action::ArrayPositionSnowflakeSwap => {
29264                    // ARRAY_POSITION(arr, elem) -> ARRAY_POSITION(elem, arr) for Snowflake
29265                    if let Expression::ArrayPosition(f) = e {
29266                        Ok(Expression::ArrayPosition(Box::new(
29267                            crate::expressions::BinaryFunc {
29268                                this: f.expression,
29269                                expression: f.this,
29270                                original_name: f.original_name,
29271                                inferred_type: f.inferred_type,
29272                            },
29273                        )))
29274                    } else {
29275                        Ok(e)
29276                    }
29277                }
29278
29279                Action::SnowflakeArrayPositionToDuckDB => {
29280                    // Snowflake ARRAY_POSITION(value, array) -> DuckDB ARRAY_POSITION(array, value) - 1
29281                    // Snowflake uses 0-based indexing, DuckDB uses 1-based
29282                    // The parser has this=value, expression=array (Snowflake order)
29283                    if let Expression::ArrayPosition(f) = e {
29284                        // Create ARRAY_POSITION(array, value) in standard order
29285                        let standard_pos =
29286                            Expression::ArrayPosition(Box::new(crate::expressions::BinaryFunc {
29287                                this: f.expression, // array
29288                                expression: f.this, // value
29289                                original_name: f.original_name,
29290                                inferred_type: f.inferred_type,
29291                            }));
29292                        // Subtract 1 for zero-based indexing
29293                        Ok(Expression::Sub(Box::new(BinaryOp {
29294                            left: standard_pos,
29295                            right: Expression::number(1),
29296                            left_comments: vec![],
29297                            operator_comments: vec![],
29298                            trailing_comments: vec![],
29299                            inferred_type: None,
29300                        })))
29301                    } else {
29302                        Ok(e)
29303                    }
29304                }
29305
29306                Action::ArrayDistinctConvert => {
29307                    // ARRAY_DISTINCT(arr) -> DuckDB NULL-aware CASE:
29308                    // CASE WHEN ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
29309                    //   THEN LIST_APPEND(LIST_DISTINCT(LIST_FILTER(arr, _u -> NOT _u IS NULL)), NULL)
29310                    //   ELSE LIST_DISTINCT(arr)
29311                    // END
29312                    if let Expression::ArrayDistinct(f) = e {
29313                        let arr = f.this;
29314
29315                        // ARRAY_LENGTH(arr)
29316                        let array_length = Expression::Function(Box::new(Function::new(
29317                            "ARRAY_LENGTH".to_string(),
29318                            vec![arr.clone()],
29319                        )));
29320                        // LIST_COUNT(arr)
29321                        let list_count = Expression::Function(Box::new(Function::new(
29322                            "LIST_COUNT".to_string(),
29323                            vec![arr.clone()],
29324                        )));
29325                        // ARRAY_LENGTH(arr) <> LIST_COUNT(arr)
29326                        let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
29327                            left: array_length,
29328                            right: list_count,
29329                            left_comments: vec![],
29330                            operator_comments: vec![],
29331                            trailing_comments: vec![],
29332                            inferred_type: None,
29333                        }));
29334
29335                        // _u column
29336                        let u_col = Expression::column("_u");
29337                        // NOT _u IS NULL
29338                        let u_is_null = Expression::IsNull(Box::new(crate::expressions::IsNull {
29339                            this: u_col.clone(),
29340                            not: false,
29341                            postfix_form: false,
29342                        }));
29343                        let not_u_is_null =
29344                            Expression::Not(Box::new(crate::expressions::UnaryOp {
29345                                this: u_is_null,
29346                                inferred_type: None,
29347                            }));
29348                        // _u -> NOT _u IS NULL
29349                        let filter_lambda =
29350                            Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
29351                                parameters: vec![crate::expressions::Identifier::new("_u")],
29352                                body: not_u_is_null,
29353                                colon: false,
29354                                parameter_types: vec![],
29355                            }));
29356                        // LIST_FILTER(arr, _u -> NOT _u IS NULL)
29357                        let list_filter = Expression::Function(Box::new(Function::new(
29358                            "LIST_FILTER".to_string(),
29359                            vec![arr.clone(), filter_lambda],
29360                        )));
29361                        // LIST_DISTINCT(LIST_FILTER(arr, ...))
29362                        let list_distinct_filtered = Expression::Function(Box::new(Function::new(
29363                            "LIST_DISTINCT".to_string(),
29364                            vec![list_filter],
29365                        )));
29366                        // LIST_APPEND(LIST_DISTINCT(LIST_FILTER(...)), NULL)
29367                        let list_append = Expression::Function(Box::new(Function::new(
29368                            "LIST_APPEND".to_string(),
29369                            vec![list_distinct_filtered, Expression::Null(Null)],
29370                        )));
29371
29372                        // LIST_DISTINCT(arr)
29373                        let list_distinct = Expression::Function(Box::new(Function::new(
29374                            "LIST_DISTINCT".to_string(),
29375                            vec![arr],
29376                        )));
29377
29378                        // CASE WHEN neq THEN list_append ELSE list_distinct END
29379                        Ok(Expression::Case(Box::new(Case {
29380                            operand: None,
29381                            whens: vec![(neq, list_append)],
29382                            else_: Some(list_distinct),
29383                            comments: Vec::new(),
29384                            inferred_type: None,
29385                        })))
29386                    } else {
29387                        Ok(e)
29388                    }
29389                }
29390
29391                Action::ArrayDistinctClickHouse => {
29392                    // ARRAY_DISTINCT(arr) -> arrayDistinct(arr) for ClickHouse
29393                    if let Expression::ArrayDistinct(f) = e {
29394                        Ok(Expression::Function(Box::new(Function::new(
29395                            "arrayDistinct".to_string(),
29396                            vec![f.this],
29397                        ))))
29398                    } else {
29399                        Ok(e)
29400                    }
29401                }
29402
29403                Action::ArrayContainsDuckDBConvert => {
29404                    // Snowflake ARRAY_CONTAINS(value, array) -> DuckDB NULL-aware:
29405                    // CASE WHEN value IS NULL
29406                    //   THEN NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
29407                    //   ELSE ARRAY_CONTAINS(array, value)
29408                    // END
29409                    // Note: In Rust AST from Snowflake parse, this=value (first arg), expression=array (second arg)
29410                    if let Expression::ArrayContains(f) = e {
29411                        let value = f.this;
29412                        let array = f.expression;
29413
29414                        // value IS NULL
29415                        let value_is_null =
29416                            Expression::IsNull(Box::new(crate::expressions::IsNull {
29417                                this: value.clone(),
29418                                not: false,
29419                                postfix_form: false,
29420                            }));
29421
29422                        // ARRAY_LENGTH(array)
29423                        let array_length = Expression::Function(Box::new(Function::new(
29424                            "ARRAY_LENGTH".to_string(),
29425                            vec![array.clone()],
29426                        )));
29427                        // LIST_COUNT(array)
29428                        let list_count = Expression::Function(Box::new(Function::new(
29429                            "LIST_COUNT".to_string(),
29430                            vec![array.clone()],
29431                        )));
29432                        // ARRAY_LENGTH(array) <> LIST_COUNT(array)
29433                        let neq = Expression::Neq(Box::new(crate::expressions::BinaryOp {
29434                            left: array_length,
29435                            right: list_count,
29436                            left_comments: vec![],
29437                            operator_comments: vec![],
29438                            trailing_comments: vec![],
29439                            inferred_type: None,
29440                        }));
29441                        // NULLIF(ARRAY_LENGTH(array) <> LIST_COUNT(array), FALSE)
29442                        let nullif = Expression::Nullif(Box::new(crate::expressions::Nullif {
29443                            this: Box::new(neq),
29444                            expression: Box::new(Expression::Boolean(
29445                                crate::expressions::BooleanLiteral { value: false },
29446                            )),
29447                        }));
29448
29449                        // ARRAY_CONTAINS(array, value) - DuckDB syntax: array first, value second
29450                        let array_contains = Expression::Function(Box::new(Function::new(
29451                            "ARRAY_CONTAINS".to_string(),
29452                            vec![array, value],
29453                        )));
29454
29455                        // CASE WHEN value IS NULL THEN NULLIF(...) ELSE ARRAY_CONTAINS(array, value) END
29456                        Ok(Expression::Case(Box::new(Case {
29457                            operand: None,
29458                            whens: vec![(value_is_null, nullif)],
29459                            else_: Some(array_contains),
29460                            comments: Vec::new(),
29461                            inferred_type: None,
29462                        })))
29463                    } else {
29464                        Ok(e)
29465                    }
29466                }
29467
29468                Action::StrPositionExpand => {
29469                    // StrPosition with position arg -> complex STRPOS expansion for Presto/DuckDB
29470                    // For Presto: IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
29471                    // For DuckDB: CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
29472                    if let Expression::StrPosition(sp) = e {
29473                        let crate::expressions::StrPosition {
29474                            this,
29475                            substr,
29476                            position,
29477                            occurrence,
29478                        } = *sp;
29479                        let string = *this;
29480                        let substr_expr = match substr {
29481                            Some(s) => *s,
29482                            None => Expression::Null(Null),
29483                        };
29484                        let pos = match position {
29485                            Some(p) => *p,
29486                            None => Expression::number(1),
29487                        };
29488
29489                        // SUBSTRING(string, pos)
29490                        let substring_call = Expression::Function(Box::new(Function::new(
29491                            "SUBSTRING".to_string(),
29492                            vec![string.clone(), pos.clone()],
29493                        )));
29494                        // STRPOS(SUBSTRING(string, pos), substr)
29495                        let strpos_call = Expression::Function(Box::new(Function::new(
29496                            "STRPOS".to_string(),
29497                            vec![substring_call, substr_expr.clone()],
29498                        )));
29499                        // STRPOS(...) + pos - 1
29500                        let pos_adjusted =
29501                            Expression::Sub(Box::new(crate::expressions::BinaryOp::new(
29502                                Expression::Add(Box::new(crate::expressions::BinaryOp::new(
29503                                    strpos_call.clone(),
29504                                    pos.clone(),
29505                                ))),
29506                                Expression::number(1),
29507                            )));
29508                        // STRPOS(...) = 0
29509                        let is_zero = Expression::Eq(Box::new(crate::expressions::BinaryOp::new(
29510                            strpos_call.clone(),
29511                            Expression::number(0),
29512                        )));
29513
29514                        match target {
29515                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29516                                // IF(STRPOS(SUBSTRING(str, pos), substr) = 0, 0, STRPOS(SUBSTRING(str, pos), substr) + pos - 1)
29517                                Ok(Expression::Function(Box::new(Function::new(
29518                                    "IF".to_string(),
29519                                    vec![is_zero, Expression::number(0), pos_adjusted],
29520                                ))))
29521                            }
29522                            DialectType::DuckDB => {
29523                                // CASE WHEN STRPOS(SUBSTRING(str, pos), substr) = 0 THEN 0 ELSE STRPOS(SUBSTRING(str, pos), substr) + pos - 1 END
29524                                Ok(Expression::Case(Box::new(Case {
29525                                    operand: None,
29526                                    whens: vec![(is_zero, Expression::number(0))],
29527                                    else_: Some(pos_adjusted),
29528                                    comments: Vec::new(),
29529                                    inferred_type: None,
29530                                })))
29531                            }
29532                            _ => {
29533                                // Reconstruct StrPosition
29534                                Ok(Expression::StrPosition(Box::new(
29535                                    crate::expressions::StrPosition {
29536                                        this: Box::new(string),
29537                                        substr: Some(Box::new(substr_expr)),
29538                                        position: Some(Box::new(pos)),
29539                                        occurrence,
29540                                    },
29541                                )))
29542                            }
29543                        }
29544                    } else {
29545                        Ok(e)
29546                    }
29547                }
29548
29549                Action::MonthsBetweenConvert => {
29550                    if let Expression::MonthsBetween(mb) = e {
29551                        let crate::expressions::BinaryFunc {
29552                            this: end_date,
29553                            expression: start_date,
29554                            ..
29555                        } = *mb;
29556                        match target {
29557                            DialectType::DuckDB => {
29558                                let cast_end = Self::ensure_cast_date(end_date);
29559                                let cast_start = Self::ensure_cast_date(start_date);
29560                                let dd = Expression::Function(Box::new(Function::new(
29561                                    "DATE_DIFF".to_string(),
29562                                    vec![
29563                                        Expression::string("MONTH"),
29564                                        cast_start.clone(),
29565                                        cast_end.clone(),
29566                                    ],
29567                                )));
29568                                let day_end = Expression::Function(Box::new(Function::new(
29569                                    "DAY".to_string(),
29570                                    vec![cast_end.clone()],
29571                                )));
29572                                let day_start = Expression::Function(Box::new(Function::new(
29573                                    "DAY".to_string(),
29574                                    vec![cast_start.clone()],
29575                                )));
29576                                let last_day_end = Expression::Function(Box::new(Function::new(
29577                                    "LAST_DAY".to_string(),
29578                                    vec![cast_end.clone()],
29579                                )));
29580                                let last_day_start = Expression::Function(Box::new(Function::new(
29581                                    "LAST_DAY".to_string(),
29582                                    vec![cast_start.clone()],
29583                                )));
29584                                let day_last_end = Expression::Function(Box::new(Function::new(
29585                                    "DAY".to_string(),
29586                                    vec![last_day_end],
29587                                )));
29588                                let day_last_start = Expression::Function(Box::new(Function::new(
29589                                    "DAY".to_string(),
29590                                    vec![last_day_start],
29591                                )));
29592                                let cond1 = Expression::Eq(Box::new(BinaryOp::new(
29593                                    day_end.clone(),
29594                                    day_last_end,
29595                                )));
29596                                let cond2 = Expression::Eq(Box::new(BinaryOp::new(
29597                                    day_start.clone(),
29598                                    day_last_start,
29599                                )));
29600                                let both_cond =
29601                                    Expression::And(Box::new(BinaryOp::new(cond1, cond2)));
29602                                let day_diff =
29603                                    Expression::Sub(Box::new(BinaryOp::new(day_end, day_start)));
29604                                let day_diff_paren =
29605                                    Expression::Paren(Box::new(crate::expressions::Paren {
29606                                        this: day_diff,
29607                                        trailing_comments: Vec::new(),
29608                                    }));
29609                                let frac = Expression::Div(Box::new(BinaryOp::new(
29610                                    day_diff_paren,
29611                                    Expression::Literal(Box::new(Literal::Number(
29612                                        "31.0".to_string(),
29613                                    ))),
29614                                )));
29615                                let case_expr = Expression::Case(Box::new(Case {
29616                                    operand: None,
29617                                    whens: vec![(both_cond, Expression::number(0))],
29618                                    else_: Some(frac),
29619                                    comments: Vec::new(),
29620                                    inferred_type: None,
29621                                }));
29622                                Ok(Expression::Add(Box::new(BinaryOp::new(dd, case_expr))))
29623                            }
29624                            DialectType::Snowflake | DialectType::Redshift => {
29625                                let unit = Expression::Identifier(Identifier::new("MONTH"));
29626                                Ok(Expression::Function(Box::new(Function::new(
29627                                    "DATEDIFF".to_string(),
29628                                    vec![unit, start_date, end_date],
29629                                ))))
29630                            }
29631                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29632                                Ok(Expression::Function(Box::new(Function::new(
29633                                    "DATE_DIFF".to_string(),
29634                                    vec![Expression::string("MONTH"), start_date, end_date],
29635                                ))))
29636                            }
29637                            _ => Ok(Expression::MonthsBetween(Box::new(
29638                                crate::expressions::BinaryFunc {
29639                                    this: end_date,
29640                                    expression: start_date,
29641                                    original_name: None,
29642                                    inferred_type: None,
29643                                },
29644                            ))),
29645                        }
29646                    } else {
29647                        Ok(e)
29648                    }
29649                }
29650
29651                Action::AddMonthsConvert => {
29652                    if let Expression::AddMonths(am) = e {
29653                        let date = am.this;
29654                        let val = am.expression;
29655                        match target {
29656                            DialectType::TSQL | DialectType::Fabric => {
29657                                let cast_date = Self::ensure_cast_datetime2(date);
29658                                Ok(Expression::Function(Box::new(Function::new(
29659                                    "DATEADD".to_string(),
29660                                    vec![
29661                                        Expression::Identifier(Identifier::new("MONTH")),
29662                                        val,
29663                                        cast_date,
29664                                    ],
29665                                ))))
29666                            }
29667                            DialectType::DuckDB if matches!(source, DialectType::Snowflake) => {
29668                                // DuckDB ADD_MONTHS from Snowflake: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
29669                                // Optionally wrapped in CAST(... AS type) if the input had a specific type
29670
29671                                // Determine the cast type from the date expression
29672                                let (cast_date, return_type) = match &date {
29673                                    Expression::Literal(lit)
29674                                        if matches!(lit.as_ref(), Literal::String(_)) =>
29675                                    {
29676                                        // String literal: CAST(str AS TIMESTAMP), no outer CAST
29677                                        (
29678                                            Expression::Cast(Box::new(Cast {
29679                                                this: date.clone(),
29680                                                to: DataType::Timestamp {
29681                                                    precision: None,
29682                                                    timezone: false,
29683                                                },
29684                                                trailing_comments: Vec::new(),
29685                                                double_colon_syntax: false,
29686                                                format: None,
29687                                                default: None,
29688                                                inferred_type: None,
29689                                            })),
29690                                            None,
29691                                        )
29692                                    }
29693                                    Expression::Cast(c) => {
29694                                        // Already cast (e.g., '2023-01-31'::DATE) - keep the cast, wrap result in CAST(... AS type)
29695                                        (date.clone(), Some(c.to.clone()))
29696                                    }
29697                                    _ => {
29698                                        // Expression or NULL::TYPE - keep as-is, check for cast type
29699                                        if let Expression::Cast(c) = &date {
29700                                            (date.clone(), Some(c.to.clone()))
29701                                        } else {
29702                                            (date.clone(), None)
29703                                        }
29704                                    }
29705                                };
29706
29707                                // Build the interval expression
29708                                // For non-integer values (float, decimal, cast), use TO_MONTHS(CAST(ROUND(val) AS INT))
29709                                // For integer values, use INTERVAL val MONTH
29710                                let is_non_integer_val = match &val {
29711                                    Expression::Literal(lit)
29712                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
29713                                    {
29714                                        let Literal::Number(n) = lit.as_ref() else {
29715                                            unreachable!()
29716                                        };
29717                                        n.contains('.')
29718                                    }
29719                                    Expression::Cast(_) => true, // e.g., 3.2::DECIMAL(10,2)
29720                                    Expression::Neg(n) => {
29721                                        if let Expression::Literal(lit) = &n.this {
29722                                            if let Literal::Number(s) = lit.as_ref() {
29723                                                s.contains('.')
29724                                            } else {
29725                                                false
29726                                            }
29727                                        } else {
29728                                            false
29729                                        }
29730                                    }
29731                                    _ => false,
29732                                };
29733
29734                                let add_interval = if is_non_integer_val {
29735                                    // TO_MONTHS(CAST(ROUND(val) AS INT))
29736                                    let round_val = Expression::Function(Box::new(Function::new(
29737                                        "ROUND".to_string(),
29738                                        vec![val.clone()],
29739                                    )));
29740                                    let cast_int = Expression::Cast(Box::new(Cast {
29741                                        this: round_val,
29742                                        to: DataType::Int {
29743                                            length: None,
29744                                            integer_spelling: false,
29745                                        },
29746                                        trailing_comments: Vec::new(),
29747                                        double_colon_syntax: false,
29748                                        format: None,
29749                                        default: None,
29750                                        inferred_type: None,
29751                                    }));
29752                                    Expression::Function(Box::new(Function::new(
29753                                        "TO_MONTHS".to_string(),
29754                                        vec![cast_int],
29755                                    )))
29756                                } else {
29757                                    // INTERVAL val MONTH
29758                                    // For negative numbers, wrap in parens
29759                                    let interval_val = match &val {
29760                                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n.starts_with('-')) =>
29761                                        {
29762                                            let Literal::Number(_) = lit.as_ref() else {
29763                                                unreachable!()
29764                                            };
29765                                            Expression::Paren(Box::new(Paren {
29766                                                this: val.clone(),
29767                                                trailing_comments: Vec::new(),
29768                                            }))
29769                                        }
29770                                        Expression::Neg(_) => Expression::Paren(Box::new(Paren {
29771                                            this: val.clone(),
29772                                            trailing_comments: Vec::new(),
29773                                        })),
29774                                        Expression::Null(_) => Expression::Paren(Box::new(Paren {
29775                                            this: val.clone(),
29776                                            trailing_comments: Vec::new(),
29777                                        })),
29778                                        _ => val.clone(),
29779                                    };
29780                                    Expression::Interval(Box::new(crate::expressions::Interval {
29781                                        this: Some(interval_val),
29782                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29783                                            unit: crate::expressions::IntervalUnit::Month,
29784                                            use_plural: false,
29785                                        }),
29786                                    }))
29787                                };
29788
29789                                // Build: date + interval
29790                                let date_plus_interval = Expression::Add(Box::new(BinaryOp::new(
29791                                    cast_date.clone(),
29792                                    add_interval.clone(),
29793                                )));
29794
29795                                // Build LAST_DAY(date)
29796                                let last_day_date = Expression::Function(Box::new(Function::new(
29797                                    "LAST_DAY".to_string(),
29798                                    vec![cast_date.clone()],
29799                                )));
29800
29801                                // Build LAST_DAY(date + interval)
29802                                let last_day_date_plus =
29803                                    Expression::Function(Box::new(Function::new(
29804                                        "LAST_DAY".to_string(),
29805                                        vec![date_plus_interval.clone()],
29806                                    )));
29807
29808                                // Build: CASE WHEN LAST_DAY(date) = date THEN LAST_DAY(date + interval) ELSE date + interval END
29809                                let case_expr = Expression::Case(Box::new(Case {
29810                                    operand: None,
29811                                    whens: vec![(
29812                                        Expression::Eq(Box::new(BinaryOp::new(
29813                                            last_day_date,
29814                                            cast_date.clone(),
29815                                        ))),
29816                                        last_day_date_plus,
29817                                    )],
29818                                    else_: Some(date_plus_interval),
29819                                    comments: Vec::new(),
29820                                    inferred_type: None,
29821                                }));
29822
29823                                // Wrap in CAST(... AS type) if needed
29824                                if let Some(dt) = return_type {
29825                                    Ok(Expression::Cast(Box::new(Cast {
29826                                        this: case_expr,
29827                                        to: dt,
29828                                        trailing_comments: Vec::new(),
29829                                        double_colon_syntax: false,
29830                                        format: None,
29831                                        default: None,
29832                                        inferred_type: None,
29833                                    })))
29834                                } else {
29835                                    Ok(case_expr)
29836                                }
29837                            }
29838                            DialectType::DuckDB => {
29839                                // Non-Snowflake source: simple date + INTERVAL
29840                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29841                                {
29842                                    Expression::Cast(Box::new(Cast {
29843                                        this: date,
29844                                        to: DataType::Timestamp {
29845                                            precision: None,
29846                                            timezone: false,
29847                                        },
29848                                        trailing_comments: Vec::new(),
29849                                        double_colon_syntax: false,
29850                                        format: None,
29851                                        default: None,
29852                                        inferred_type: None,
29853                                    }))
29854                                } else {
29855                                    date
29856                                };
29857                                let interval =
29858                                    Expression::Interval(Box::new(crate::expressions::Interval {
29859                                        this: Some(val),
29860                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29861                                            unit: crate::expressions::IntervalUnit::Month,
29862                                            use_plural: false,
29863                                        }),
29864                                    }));
29865                                Ok(Expression::Add(Box::new(BinaryOp::new(
29866                                    cast_date, interval,
29867                                ))))
29868                            }
29869                            DialectType::Snowflake => {
29870                                // Keep ADD_MONTHS when source is also Snowflake
29871                                if matches!(source, DialectType::Snowflake) {
29872                                    Ok(Expression::Function(Box::new(Function::new(
29873                                        "ADD_MONTHS".to_string(),
29874                                        vec![date, val],
29875                                    ))))
29876                                } else {
29877                                    Ok(Expression::Function(Box::new(Function::new(
29878                                        "DATEADD".to_string(),
29879                                        vec![
29880                                            Expression::Identifier(Identifier::new("MONTH")),
29881                                            val,
29882                                            date,
29883                                        ],
29884                                    ))))
29885                                }
29886                            }
29887                            DialectType::Redshift => {
29888                                Ok(Expression::Function(Box::new(Function::new(
29889                                    "DATEADD".to_string(),
29890                                    vec![
29891                                        Expression::Identifier(Identifier::new("MONTH")),
29892                                        val,
29893                                        date,
29894                                    ],
29895                                ))))
29896                            }
29897                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
29898                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29899                                {
29900                                    Expression::Cast(Box::new(Cast {
29901                                        this: date,
29902                                        to: DataType::Timestamp {
29903                                            precision: None,
29904                                            timezone: false,
29905                                        },
29906                                        trailing_comments: Vec::new(),
29907                                        double_colon_syntax: false,
29908                                        format: None,
29909                                        default: None,
29910                                        inferred_type: None,
29911                                    }))
29912                                } else {
29913                                    date
29914                                };
29915                                Ok(Expression::Function(Box::new(Function::new(
29916                                    "DATE_ADD".to_string(),
29917                                    vec![Expression::string("MONTH"), val, cast_date],
29918                                ))))
29919                            }
29920                            DialectType::BigQuery => {
29921                                let interval =
29922                                    Expression::Interval(Box::new(crate::expressions::Interval {
29923                                        this: Some(val),
29924                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
29925                                            unit: crate::expressions::IntervalUnit::Month,
29926                                            use_plural: false,
29927                                        }),
29928                                    }));
29929                                let cast_date = if matches!(&date, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
29930                                {
29931                                    Expression::Cast(Box::new(Cast {
29932                                        this: date,
29933                                        to: DataType::Custom {
29934                                            name: "DATETIME".to_string(),
29935                                        },
29936                                        trailing_comments: Vec::new(),
29937                                        double_colon_syntax: false,
29938                                        format: None,
29939                                        default: None,
29940                                        inferred_type: None,
29941                                    }))
29942                                } else {
29943                                    date
29944                                };
29945                                Ok(Expression::Function(Box::new(Function::new(
29946                                    "DATE_ADD".to_string(),
29947                                    vec![cast_date, interval],
29948                                ))))
29949                            }
29950                            DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
29951                                Ok(Expression::Function(Box::new(Function::new(
29952                                    "ADD_MONTHS".to_string(),
29953                                    vec![date, val],
29954                                ))))
29955                            }
29956                            _ => {
29957                                // Default: keep as AddMonths expression
29958                                Ok(Expression::AddMonths(Box::new(
29959                                    crate::expressions::BinaryFunc {
29960                                        this: date,
29961                                        expression: val,
29962                                        original_name: None,
29963                                        inferred_type: None,
29964                                    },
29965                                )))
29966                            }
29967                        }
29968                    } else {
29969                        Ok(e)
29970                    }
29971                }
29972
29973                Action::PercentileContConvert => {
29974                    // PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY col) ->
29975                    // Presto/Trino: APPROX_PERCENTILE(col, p)
29976                    // Spark/Databricks: PERCENTILE_APPROX(col, p)
29977                    if let Expression::WithinGroup(wg) = e {
29978                        // Extract percentile value and order by column
29979                        let (percentile, _is_disc) = match &wg.this {
29980                            Expression::Function(f) => {
29981                                let is_disc = f.name.eq_ignore_ascii_case("PERCENTILE_DISC");
29982                                let pct = f.args.first().cloned().unwrap_or(Expression::Literal(
29983                                    Box::new(Literal::Number("0.5".to_string())),
29984                                ));
29985                                (pct, is_disc)
29986                            }
29987                            Expression::AggregateFunction(af) => {
29988                                let is_disc = af.name.eq_ignore_ascii_case("PERCENTILE_DISC");
29989                                let pct = af.args.first().cloned().unwrap_or(Expression::Literal(
29990                                    Box::new(Literal::Number("0.5".to_string())),
29991                                ));
29992                                (pct, is_disc)
29993                            }
29994                            Expression::PercentileCont(pc) => (pc.percentile.clone(), false),
29995                            _ => return Ok(Expression::WithinGroup(wg)),
29996                        };
29997                        let col = wg.order_by.first().map(|o| o.this.clone()).unwrap_or(
29998                            Expression::Literal(Box::new(Literal::Number("1".to_string()))),
29999                        );
30000
30001                        let func_name = match target {
30002                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30003                                "APPROX_PERCENTILE"
30004                            }
30005                            _ => "PERCENTILE_APPROX", // Spark, Databricks
30006                        };
30007                        Ok(Expression::Function(Box::new(Function::new(
30008                            func_name.to_string(),
30009                            vec![col, percentile],
30010                        ))))
30011                    } else {
30012                        Ok(e)
30013                    }
30014                }
30015
30016                Action::CurrentUserSparkParens => {
30017                    // CURRENT_USER -> CURRENT_USER() for Spark
30018                    if let Expression::CurrentUser(_) = e {
30019                        Ok(Expression::Function(Box::new(Function::new(
30020                            "CURRENT_USER".to_string(),
30021                            vec![],
30022                        ))))
30023                    } else {
30024                        Ok(e)
30025                    }
30026                }
30027
30028                Action::SparkDateFuncCast => {
30029                    // MONTH/YEAR/DAY('string') from Spark -> wrap arg in CAST to DATE
30030                    let cast_arg = |arg: Expression| -> Expression {
30031                        match target {
30032                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30033                                Self::double_cast_timestamp_date(arg)
30034                            }
30035                            _ => {
30036                                // DuckDB, PostgreSQL, etc: CAST(arg AS DATE)
30037                                Self::ensure_cast_date(arg)
30038                            }
30039                        }
30040                    };
30041                    match e {
30042                        Expression::Month(f) => Ok(Expression::Month(Box::new(
30043                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30044                        ))),
30045                        Expression::Year(f) => Ok(Expression::Year(Box::new(
30046                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30047                        ))),
30048                        Expression::Day(f) => Ok(Expression::Day(Box::new(
30049                            crate::expressions::UnaryFunc::new(cast_arg(f.this)),
30050                        ))),
30051                        other => Ok(other),
30052                    }
30053                }
30054
30055                Action::MapFromArraysConvert => {
30056                    // Expression::MapFromArrays -> target-specific
30057                    if let Expression::MapFromArrays(mfa) = e {
30058                        let keys = mfa.this;
30059                        let values = mfa.expression;
30060                        match target {
30061                            DialectType::Snowflake => Ok(Expression::Function(Box::new(
30062                                Function::new("OBJECT_CONSTRUCT".to_string(), vec![keys, values]),
30063                            ))),
30064                            _ => {
30065                                // Hive, Presto, DuckDB, etc.: MAP(keys, values)
30066                                Ok(Expression::Function(Box::new(Function::new(
30067                                    "MAP".to_string(),
30068                                    vec![keys, values],
30069                                ))))
30070                            }
30071                        }
30072                    } else {
30073                        Ok(e)
30074                    }
30075                }
30076
30077                Action::AnyToExists => {
30078                    if let Expression::Any(q) = e {
30079                        if let Some(op) = q.op.clone() {
30080                            let lambda_param = crate::expressions::Identifier::new("x");
30081                            let rhs = Expression::Identifier(lambda_param.clone());
30082                            let body = match op {
30083                                crate::expressions::QuantifiedOp::Eq => {
30084                                    Expression::Eq(Box::new(BinaryOp::new(q.this, rhs)))
30085                                }
30086                                crate::expressions::QuantifiedOp::Neq => {
30087                                    Expression::Neq(Box::new(BinaryOp::new(q.this, rhs)))
30088                                }
30089                                crate::expressions::QuantifiedOp::Lt => {
30090                                    Expression::Lt(Box::new(BinaryOp::new(q.this, rhs)))
30091                                }
30092                                crate::expressions::QuantifiedOp::Lte => {
30093                                    Expression::Lte(Box::new(BinaryOp::new(q.this, rhs)))
30094                                }
30095                                crate::expressions::QuantifiedOp::Gt => {
30096                                    Expression::Gt(Box::new(BinaryOp::new(q.this, rhs)))
30097                                }
30098                                crate::expressions::QuantifiedOp::Gte => {
30099                                    Expression::Gte(Box::new(BinaryOp::new(q.this, rhs)))
30100                                }
30101                            };
30102                            let lambda =
30103                                Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
30104                                    parameters: vec![lambda_param],
30105                                    body,
30106                                    colon: false,
30107                                    parameter_types: Vec::new(),
30108                                }));
30109                            Ok(Expression::Function(Box::new(Function::new(
30110                                "EXISTS".to_string(),
30111                                vec![q.subquery, lambda],
30112                            ))))
30113                        } else {
30114                            Ok(Expression::Any(q))
30115                        }
30116                    } else {
30117                        Ok(e)
30118                    }
30119                }
30120
30121                Action::GenerateSeriesConvert => {
30122                    // GENERATE_SERIES(start, end[, step]) -> SEQUENCE for Spark/Databricks/Hive, wrapped in UNNEST/EXPLODE
30123                    // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
30124                    // For PG/Redshift target: keep as GENERATE_SERIES but normalize interval string step
30125                    if let Expression::Function(f) = e {
30126                        if f.name.eq_ignore_ascii_case("GENERATE_SERIES") && f.args.len() >= 2 {
30127                            let start = f.args[0].clone();
30128                            let end = f.args[1].clone();
30129                            let step = f.args.get(2).cloned();
30130
30131                            // Normalize step: convert string interval like '1day' or '  2   days  ' to INTERVAL expression
30132                            let step = step.map(|s| Self::normalize_interval_string(s, target));
30133
30134                            // Helper: wrap CURRENT_TIMESTAMP in CAST(... AS TIMESTAMP) for Presto/Trino/Spark
30135                            let maybe_cast_timestamp = |arg: Expression| -> Expression {
30136                                if matches!(
30137                                    target,
30138                                    DialectType::Presto
30139                                        | DialectType::Trino
30140                                        | DialectType::Athena
30141                                        | DialectType::Spark
30142                                        | DialectType::Databricks
30143                                        | DialectType::Hive
30144                                ) {
30145                                    match &arg {
30146                                        Expression::CurrentTimestamp(_) => {
30147                                            Expression::Cast(Box::new(Cast {
30148                                                this: arg,
30149                                                to: DataType::Timestamp {
30150                                                    precision: None,
30151                                                    timezone: false,
30152                                                },
30153                                                trailing_comments: Vec::new(),
30154                                                double_colon_syntax: false,
30155                                                format: None,
30156                                                default: None,
30157                                                inferred_type: None,
30158                                            }))
30159                                        }
30160                                        _ => arg,
30161                                    }
30162                                } else {
30163                                    arg
30164                                }
30165                            };
30166
30167                            let start = maybe_cast_timestamp(start);
30168                            let end = maybe_cast_timestamp(end);
30169
30170                            // For PostgreSQL/Redshift target, keep as GENERATE_SERIES
30171                            if matches!(target, DialectType::PostgreSQL | DialectType::Redshift) {
30172                                let mut gs_args = vec![start, end];
30173                                if let Some(step) = step {
30174                                    gs_args.push(step);
30175                                }
30176                                return Ok(Expression::Function(Box::new(Function::new(
30177                                    "GENERATE_SERIES".to_string(),
30178                                    gs_args,
30179                                ))));
30180                            }
30181
30182                            // For DuckDB target: wrap in UNNEST(GENERATE_SERIES(...))
30183                            if matches!(target, DialectType::DuckDB) {
30184                                let mut gs_args = vec![start, end];
30185                                if let Some(step) = step {
30186                                    gs_args.push(step);
30187                                }
30188                                let gs = Expression::Function(Box::new(Function::new(
30189                                    "GENERATE_SERIES".to_string(),
30190                                    gs_args,
30191                                )));
30192                                return Ok(Expression::Function(Box::new(Function::new(
30193                                    "UNNEST".to_string(),
30194                                    vec![gs],
30195                                ))));
30196                            }
30197
30198                            let mut seq_args = vec![start, end];
30199                            if let Some(step) = step {
30200                                seq_args.push(step);
30201                            }
30202
30203                            let seq = Expression::Function(Box::new(Function::new(
30204                                "SEQUENCE".to_string(),
30205                                seq_args,
30206                            )));
30207
30208                            match target {
30209                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
30210                                    // Wrap in UNNEST
30211                                    Ok(Expression::Function(Box::new(Function::new(
30212                                        "UNNEST".to_string(),
30213                                        vec![seq],
30214                                    ))))
30215                                }
30216                                DialectType::Spark
30217                                | DialectType::Databricks
30218                                | DialectType::Hive => {
30219                                    // Wrap in EXPLODE
30220                                    Ok(Expression::Function(Box::new(Function::new(
30221                                        "EXPLODE".to_string(),
30222                                        vec![seq],
30223                                    ))))
30224                                }
30225                                _ => {
30226                                    // Just SEQUENCE for others
30227                                    Ok(seq)
30228                                }
30229                            }
30230                        } else {
30231                            Ok(Expression::Function(f))
30232                        }
30233                    } else {
30234                        Ok(e)
30235                    }
30236                }
30237
30238                Action::ConcatCoalesceWrap => {
30239                    // CONCAT(a, b) function -> CONCAT(COALESCE(CAST(a AS VARCHAR), ''), ...) for Presto
30240                    // CONCAT(a, b) function -> CONCAT(COALESCE(a, ''), ...) for ClickHouse
30241                    if let Expression::Function(f) = e {
30242                        if f.name.eq_ignore_ascii_case("CONCAT") {
30243                            let new_args: Vec<Expression> = f
30244                                .args
30245                                .into_iter()
30246                                .map(|arg| {
30247                                    let cast_arg = if matches!(
30248                                        target,
30249                                        DialectType::Presto
30250                                            | DialectType::Trino
30251                                            | DialectType::Athena
30252                                    ) {
30253                                        Expression::Cast(Box::new(Cast {
30254                                            this: arg,
30255                                            to: DataType::VarChar {
30256                                                length: None,
30257                                                parenthesized_length: false,
30258                                            },
30259                                            trailing_comments: Vec::new(),
30260                                            double_colon_syntax: false,
30261                                            format: None,
30262                                            default: None,
30263                                            inferred_type: None,
30264                                        }))
30265                                    } else {
30266                                        arg
30267                                    };
30268                                    Expression::Function(Box::new(Function::new(
30269                                        "COALESCE".to_string(),
30270                                        vec![cast_arg, Expression::string("")],
30271                                    )))
30272                                })
30273                                .collect();
30274                            Ok(Expression::Function(Box::new(Function::new(
30275                                "CONCAT".to_string(),
30276                                new_args,
30277                            ))))
30278                        } else {
30279                            Ok(Expression::Function(f))
30280                        }
30281                    } else {
30282                        Ok(e)
30283                    }
30284                }
30285
30286                Action::PipeConcatToConcat => {
30287                    // a || b (Concat operator) -> CONCAT(CAST(a AS VARCHAR), CAST(b AS VARCHAR)) for Presto/Trino
30288                    if let Expression::Concat(op) = e {
30289                        let cast_left = Expression::Cast(Box::new(Cast {
30290                            this: op.left,
30291                            to: DataType::VarChar {
30292                                length: None,
30293                                parenthesized_length: false,
30294                            },
30295                            trailing_comments: Vec::new(),
30296                            double_colon_syntax: false,
30297                            format: None,
30298                            default: None,
30299                            inferred_type: None,
30300                        }));
30301                        let cast_right = Expression::Cast(Box::new(Cast {
30302                            this: op.right,
30303                            to: DataType::VarChar {
30304                                length: None,
30305                                parenthesized_length: false,
30306                            },
30307                            trailing_comments: Vec::new(),
30308                            double_colon_syntax: false,
30309                            format: None,
30310                            default: None,
30311                            inferred_type: None,
30312                        }));
30313                        Ok(Expression::Function(Box::new(Function::new(
30314                            "CONCAT".to_string(),
30315                            vec![cast_left, cast_right],
30316                        ))))
30317                    } else {
30318                        Ok(e)
30319                    }
30320                }
30321
30322                Action::DivFuncConvert => {
30323                    // DIV(a, b) -> target-specific integer division
30324                    if let Expression::Function(f) = e {
30325                        if f.name.eq_ignore_ascii_case("DIV") && f.args.len() == 2 {
30326                            let a = f.args[0].clone();
30327                            let b = f.args[1].clone();
30328                            match target {
30329                                DialectType::DuckDB => {
30330                                    // DIV(a, b) -> CAST(a // b AS DECIMAL)
30331                                    let int_div = Expression::IntDiv(Box::new(
30332                                        crate::expressions::BinaryFunc {
30333                                            this: a,
30334                                            expression: b,
30335                                            original_name: None,
30336                                            inferred_type: None,
30337                                        },
30338                                    ));
30339                                    Ok(Expression::Cast(Box::new(Cast {
30340                                        this: int_div,
30341                                        to: DataType::Decimal {
30342                                            precision: None,
30343                                            scale: None,
30344                                        },
30345                                        trailing_comments: Vec::new(),
30346                                        double_colon_syntax: false,
30347                                        format: None,
30348                                        default: None,
30349                                        inferred_type: None,
30350                                    })))
30351                                }
30352                                DialectType::BigQuery => {
30353                                    // DIV(a, b) -> CAST(DIV(a, b) AS NUMERIC)
30354                                    let div_func = Expression::Function(Box::new(Function::new(
30355                                        "DIV".to_string(),
30356                                        vec![a, b],
30357                                    )));
30358                                    Ok(Expression::Cast(Box::new(Cast {
30359                                        this: div_func,
30360                                        to: DataType::Custom {
30361                                            name: "NUMERIC".to_string(),
30362                                        },
30363                                        trailing_comments: Vec::new(),
30364                                        double_colon_syntax: false,
30365                                        format: None,
30366                                        default: None,
30367                                        inferred_type: None,
30368                                    })))
30369                                }
30370                                DialectType::SQLite => {
30371                                    // DIV(a, b) -> CAST(CAST(CAST(a AS REAL) / b AS INTEGER) AS REAL)
30372                                    let cast_a = Expression::Cast(Box::new(Cast {
30373                                        this: a,
30374                                        to: DataType::Custom {
30375                                            name: "REAL".to_string(),
30376                                        },
30377                                        trailing_comments: Vec::new(),
30378                                        double_colon_syntax: false,
30379                                        format: None,
30380                                        default: None,
30381                                        inferred_type: None,
30382                                    }));
30383                                    let div = Expression::Div(Box::new(BinaryOp::new(cast_a, b)));
30384                                    let cast_int = Expression::Cast(Box::new(Cast {
30385                                        this: div,
30386                                        to: DataType::Int {
30387                                            length: None,
30388                                            integer_spelling: true,
30389                                        },
30390                                        trailing_comments: Vec::new(),
30391                                        double_colon_syntax: false,
30392                                        format: None,
30393                                        default: None,
30394                                        inferred_type: None,
30395                                    }));
30396                                    Ok(Expression::Cast(Box::new(Cast {
30397                                        this: cast_int,
30398                                        to: DataType::Custom {
30399                                            name: "REAL".to_string(),
30400                                        },
30401                                        trailing_comments: Vec::new(),
30402                                        double_colon_syntax: false,
30403                                        format: None,
30404                                        default: None,
30405                                        inferred_type: None,
30406                                    })))
30407                                }
30408                                DialectType::TSQL | DialectType::Fabric => {
30409                                    Ok(Self::build_tsql_div_func(a, b, target))
30410                                }
30411                                _ => Ok(Expression::Function(f)),
30412                            }
30413                        } else {
30414                            Ok(Expression::Function(f))
30415                        }
30416                    } else {
30417                        Ok(e)
30418                    }
30419                }
30420
30421                Action::CbrtToPower => match e {
30422                    Expression::Cbrt(f) => Ok(Self::build_tsql_cbrt_power(f.this)),
30423                    Expression::Function(f) if f.args.len() == 1 => {
30424                        let mut args = f.args;
30425                        Ok(Self::build_tsql_cbrt_power(args.remove(0)))
30426                    }
30427                    _ => Ok(e),
30428                },
30429
30430                Action::JsonObjectAggConvert => {
30431                    // JSON_OBJECT_AGG/JSONB_OBJECT_AGG -> JSON_GROUP_OBJECT for DuckDB
30432                    match e {
30433                        Expression::Function(f) => Ok(Expression::Function(Box::new(
30434                            Function::new("JSON_GROUP_OBJECT".to_string(), f.args),
30435                        ))),
30436                        Expression::AggregateFunction(af) => {
30437                            // AggregateFunction stores all args in the `args` vec
30438                            Ok(Expression::Function(Box::new(Function::new(
30439                                "JSON_GROUP_OBJECT".to_string(),
30440                                af.args,
30441                            ))))
30442                        }
30443                        other => Ok(other),
30444                    }
30445                }
30446
30447                Action::JsonbExistsConvert => {
30448                    // JSONB_EXISTS('json', 'key') -> JSON_EXISTS('json', '$.key') for DuckDB
30449                    if let Expression::Function(f) = e {
30450                        if f.args.len() == 2 {
30451                            let json_expr = f.args[0].clone();
30452                            let key = match &f.args[1] {
30453                                Expression::Literal(lit)
30454                                    if matches!(
30455                                        lit.as_ref(),
30456                                        crate::expressions::Literal::String(_)
30457                                    ) =>
30458                                {
30459                                    let crate::expressions::Literal::String(s) = lit.as_ref()
30460                                    else {
30461                                        unreachable!()
30462                                    };
30463                                    format!("$.{}", s)
30464                                }
30465                                _ => return Ok(Expression::Function(f)),
30466                            };
30467                            Ok(Expression::Function(Box::new(Function::new(
30468                                "JSON_EXISTS".to_string(),
30469                                vec![json_expr, Expression::string(&key)],
30470                            ))))
30471                        } else {
30472                            Ok(Expression::Function(f))
30473                        }
30474                    } else {
30475                        Ok(e)
30476                    }
30477                }
30478
30479                Action::DateBinConvert => {
30480                    // DATE_BIN('interval', ts, origin) -> TIME_BUCKET('interval', ts, origin) for DuckDB
30481                    if let Expression::Function(f) = e {
30482                        Ok(Expression::Function(Box::new(Function::new(
30483                            "TIME_BUCKET".to_string(),
30484                            f.args,
30485                        ))))
30486                    } else {
30487                        Ok(e)
30488                    }
30489                }
30490
30491                Action::MysqlCastCharToText => {
30492                    // MySQL CAST(x AS CHAR) was originally TEXT -> convert to target text type
30493                    if let Expression::Cast(mut c) = e {
30494                        c.to = DataType::Text;
30495                        Ok(Expression::Cast(c))
30496                    } else {
30497                        Ok(e)
30498                    }
30499                }
30500
30501                Action::SparkCastVarcharToString => {
30502                    // Spark parses VARCHAR(n)/CHAR(n) as TEXT -> normalize to STRING
30503                    match e {
30504                        Expression::Cast(mut c) => {
30505                            c.to = Self::normalize_varchar_to_string(c.to);
30506                            Ok(Expression::Cast(c))
30507                        }
30508                        Expression::TryCast(mut c) => {
30509                            c.to = Self::normalize_varchar_to_string(c.to);
30510                            Ok(Expression::TryCast(c))
30511                        }
30512                        _ => Ok(e),
30513                    }
30514                }
30515
30516                Action::MinMaxToLeastGreatest => {
30517                    // Multi-arg MIN(a,b,c) -> LEAST(a,b,c), MAX(a,b,c) -> GREATEST(a,b,c)
30518                    if let Expression::Function(f) = e {
30519                        let new_name = if f.name.eq_ignore_ascii_case("MIN") {
30520                            "LEAST"
30521                        } else if f.name.eq_ignore_ascii_case("MAX") {
30522                            "GREATEST"
30523                        } else {
30524                            return Ok(Expression::Function(f));
30525                        };
30526                        Ok(Expression::Function(Box::new(Function::new(
30527                            new_name.to_string(),
30528                            f.args,
30529                        ))))
30530                    } else {
30531                        Ok(e)
30532                    }
30533                }
30534
30535                Action::ClickHouseUniqToApproxCountDistinct => {
30536                    // ClickHouse uniq(x) -> APPROX_COUNT_DISTINCT(x) for non-ClickHouse targets
30537                    if let Expression::Function(f) = e {
30538                        Ok(Expression::Function(Box::new(Function::new(
30539                            "APPROX_COUNT_DISTINCT".to_string(),
30540                            f.args,
30541                        ))))
30542                    } else {
30543                        Ok(e)
30544                    }
30545                }
30546
30547                Action::ClickHouseAnyToAnyValue => {
30548                    // ClickHouse any(x) -> ANY_VALUE(x) for non-ClickHouse targets
30549                    if let Expression::Function(f) = e {
30550                        Ok(Expression::Function(Box::new(Function::new(
30551                            "ANY_VALUE".to_string(),
30552                            f.args,
30553                        ))))
30554                    } else {
30555                        Ok(e)
30556                    }
30557                }
30558
30559                Action::OracleVarchar2ToVarchar => {
30560                    // Oracle VARCHAR2(N CHAR/BYTE) / NVARCHAR2(N) -> VarChar(N) for non-Oracle targets
30561                    if let Expression::DataType(DataType::Custom { ref name }) = e {
30562                        // Extract length from VARCHAR2(N ...) or NVARCHAR2(N ...)
30563                        let starts_varchar2 =
30564                            name.len() >= 9 && name[..9].eq_ignore_ascii_case("VARCHAR2(");
30565                        let starts_nvarchar2 =
30566                            name.len() >= 10 && name[..10].eq_ignore_ascii_case("NVARCHAR2(");
30567                        let inner = if starts_varchar2 || starts_nvarchar2 {
30568                            let start = if starts_nvarchar2 { 10 } else { 9 }; // skip "NVARCHAR2(" or "VARCHAR2("
30569                            let end = name.len() - 1; // skip trailing ")"
30570                            Some(&name[start..end])
30571                        } else {
30572                            Option::None
30573                        };
30574                        if let Some(inner_str) = inner {
30575                            // Parse the number part, ignoring BYTE/CHAR qualifier
30576                            let num_str = inner_str.split_whitespace().next().unwrap_or("");
30577                            if let Ok(n) = num_str.parse::<u32>() {
30578                                Ok(Expression::DataType(DataType::VarChar {
30579                                    length: Some(n),
30580                                    parenthesized_length: false,
30581                                }))
30582                            } else {
30583                                Ok(e)
30584                            }
30585                        } else {
30586                            // Plain VARCHAR2 / NVARCHAR2 without parens
30587                            Ok(Expression::DataType(DataType::VarChar {
30588                                length: Option::None,
30589                                parenthesized_length: false,
30590                            }))
30591                        }
30592                    } else {
30593                        Ok(e)
30594                    }
30595                }
30596
30597                Action::Nvl2Expand => {
30598                    // NVL2(a, b[, c]) -> CASE WHEN NOT a IS NULL THEN b [ELSE c] END
30599                    // But keep as NVL2 for dialects that support it natively
30600                    let nvl2_native = matches!(
30601                        target,
30602                        DialectType::Oracle
30603                            | DialectType::Snowflake
30604                            | DialectType::Redshift
30605                            | DialectType::Teradata
30606                            | DialectType::Spark
30607                            | DialectType::Databricks
30608                    );
30609                    let (a, b, c) = if let Expression::Nvl2(nvl2) = e {
30610                        if nvl2_native {
30611                            return Ok(Expression::Nvl2(nvl2));
30612                        }
30613                        (nvl2.this, nvl2.true_value, Some(nvl2.false_value))
30614                    } else if let Expression::Function(f) = e {
30615                        if nvl2_native {
30616                            return Ok(Expression::Function(Box::new(Function::new(
30617                                "NVL2".to_string(),
30618                                f.args,
30619                            ))));
30620                        }
30621                        if f.args.len() < 2 {
30622                            return Ok(Expression::Function(f));
30623                        }
30624                        let mut args = f.args;
30625                        let a = args.remove(0);
30626                        let b = args.remove(0);
30627                        let c = if !args.is_empty() {
30628                            Some(args.remove(0))
30629                        } else {
30630                            Option::None
30631                        };
30632                        (a, b, c)
30633                    } else {
30634                        return Ok(e);
30635                    };
30636                    // Build: NOT (a IS NULL)
30637                    let is_null = Expression::IsNull(Box::new(IsNull {
30638                        this: a,
30639                        not: false,
30640                        postfix_form: false,
30641                    }));
30642                    let not_null = Expression::Not(Box::new(crate::expressions::UnaryOp {
30643                        this: is_null,
30644                        inferred_type: None,
30645                    }));
30646                    Ok(Expression::Case(Box::new(Case {
30647                        operand: Option::None,
30648                        whens: vec![(not_null, b)],
30649                        else_: c,
30650                        comments: Vec::new(),
30651                        inferred_type: None,
30652                    })))
30653                }
30654
30655                Action::IfnullToCoalesce => {
30656                    // IFNULL(a, b) -> COALESCE(a, b): clear original_name to output COALESCE
30657                    if let Expression::Coalesce(mut cf) = e {
30658                        cf.original_name = Option::None;
30659                        Ok(Expression::Coalesce(cf))
30660                    } else if let Expression::Function(f) = e {
30661                        Ok(Expression::Function(Box::new(Function::new(
30662                            "COALESCE".to_string(),
30663                            f.args,
30664                        ))))
30665                    } else {
30666                        Ok(e)
30667                    }
30668                }
30669
30670                Action::IsAsciiConvert => {
30671                    // IS_ASCII(x) -> dialect-specific ASCII check
30672                    if let Expression::Function(f) = e {
30673                        let arg = f.args.into_iter().next().unwrap();
30674                        match target {
30675                            DialectType::MySQL | DialectType::SingleStore | DialectType::TiDB => {
30676                                // REGEXP_LIKE(x, '^[[:ascii:]]*$')
30677                                Ok(Expression::Function(Box::new(Function::new(
30678                                    "REGEXP_LIKE".to_string(),
30679                                    vec![
30680                                        arg,
30681                                        Expression::Literal(Box::new(Literal::String(
30682                                            "^[[:ascii:]]*$".to_string(),
30683                                        ))),
30684                                    ],
30685                                ))))
30686                            }
30687                            DialectType::PostgreSQL
30688                            | DialectType::Redshift
30689                            | DialectType::Materialize
30690                            | DialectType::RisingWave => {
30691                                // (x ~ '^[[:ascii:]]*$')
30692                                Ok(Expression::Paren(Box::new(Paren {
30693                                    this: Expression::RegexpLike(Box::new(
30694                                        crate::expressions::RegexpFunc {
30695                                            this: arg,
30696                                            pattern: Expression::Literal(Box::new(
30697                                                Literal::String("^[[:ascii:]]*$".to_string()),
30698                                            )),
30699                                            flags: Option::None,
30700                                        },
30701                                    )),
30702                                    trailing_comments: Vec::new(),
30703                                })))
30704                            }
30705                            DialectType::SQLite => {
30706                                // (NOT x GLOB CAST(x'2a5b5e012d7f5d2a' AS TEXT))
30707                                let hex_lit = Expression::Literal(Box::new(Literal::HexString(
30708                                    "2a5b5e012d7f5d2a".to_string(),
30709                                )));
30710                                let cast_expr = Expression::Cast(Box::new(Cast {
30711                                    this: hex_lit,
30712                                    to: DataType::Text,
30713                                    trailing_comments: Vec::new(),
30714                                    double_colon_syntax: false,
30715                                    format: Option::None,
30716                                    default: Option::None,
30717                                    inferred_type: None,
30718                                }));
30719                                let glob = Expression::Glob(Box::new(BinaryOp {
30720                                    left: arg,
30721                                    right: cast_expr,
30722                                    left_comments: Vec::new(),
30723                                    operator_comments: Vec::new(),
30724                                    trailing_comments: Vec::new(),
30725                                    inferred_type: None,
30726                                }));
30727                                Ok(Expression::Paren(Box::new(Paren {
30728                                    this: Expression::Not(Box::new(crate::expressions::UnaryOp {
30729                                        this: glob,
30730                                        inferred_type: None,
30731                                    })),
30732                                    trailing_comments: Vec::new(),
30733                                })))
30734                            }
30735                            DialectType::TSQL | DialectType::Fabric => {
30736                                // (PATINDEX(CONVERT(VARCHAR(MAX), 0x255b5e002d7f5d25) COLLATE Latin1_General_BIN, x) = 0)
30737                                let hex_lit = Expression::Literal(Box::new(Literal::HexNumber(
30738                                    "255b5e002d7f5d25".to_string(),
30739                                )));
30740                                let convert_expr = Expression::Convert(Box::new(
30741                                    crate::expressions::ConvertFunc {
30742                                        this: hex_lit,
30743                                        to: DataType::Text, // Text generates as VARCHAR(MAX) for TSQL
30744                                        style: None,
30745                                    },
30746                                ));
30747                                let collated = Expression::Collation(Box::new(
30748                                    crate::expressions::CollationExpr {
30749                                        this: convert_expr,
30750                                        collation: "Latin1_General_BIN".to_string(),
30751                                        quoted: false,
30752                                        double_quoted: false,
30753                                    },
30754                                ));
30755                                let patindex = Expression::Function(Box::new(Function::new(
30756                                    "PATINDEX".to_string(),
30757                                    vec![collated, arg],
30758                                )));
30759                                let zero =
30760                                    Expression::Literal(Box::new(Literal::Number("0".to_string())));
30761                                let eq_zero = Expression::Eq(Box::new(BinaryOp {
30762                                    left: patindex,
30763                                    right: zero,
30764                                    left_comments: Vec::new(),
30765                                    operator_comments: Vec::new(),
30766                                    trailing_comments: Vec::new(),
30767                                    inferred_type: None,
30768                                }));
30769                                Ok(Expression::Paren(Box::new(Paren {
30770                                    this: eq_zero,
30771                                    trailing_comments: Vec::new(),
30772                                })))
30773                            }
30774                            DialectType::Oracle => {
30775                                // NVL(REGEXP_LIKE(x, '^[' || CHR(1) || '-' || CHR(127) || ']*$'), TRUE)
30776                                // Build the pattern: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
30777                                let s1 = Expression::Literal(Box::new(Literal::String(
30778                                    "^[".to_string(),
30779                                )));
30780                                let chr1 = Expression::Function(Box::new(Function::new(
30781                                    "CHR".to_string(),
30782                                    vec![Expression::Literal(Box::new(Literal::Number(
30783                                        "1".to_string(),
30784                                    )))],
30785                                )));
30786                                let dash =
30787                                    Expression::Literal(Box::new(Literal::String("-".to_string())));
30788                                let chr127 = Expression::Function(Box::new(Function::new(
30789                                    "CHR".to_string(),
30790                                    vec![Expression::Literal(Box::new(Literal::Number(
30791                                        "127".to_string(),
30792                                    )))],
30793                                )));
30794                                let s2 = Expression::Literal(Box::new(Literal::String(
30795                                    "]*$".to_string(),
30796                                )));
30797                                // Build: '^[' || CHR(1) || '-' || CHR(127) || ']*$'
30798                                let concat1 =
30799                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
30800                                        this: Box::new(s1),
30801                                        expression: Box::new(chr1),
30802                                        safe: None,
30803                                    }));
30804                                let concat2 =
30805                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
30806                                        this: Box::new(concat1),
30807                                        expression: Box::new(dash),
30808                                        safe: None,
30809                                    }));
30810                                let concat3 =
30811                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
30812                                        this: Box::new(concat2),
30813                                        expression: Box::new(chr127),
30814                                        safe: None,
30815                                    }));
30816                                let concat4 =
30817                                    Expression::DPipe(Box::new(crate::expressions::DPipe {
30818                                        this: Box::new(concat3),
30819                                        expression: Box::new(s2),
30820                                        safe: None,
30821                                    }));
30822                                let regexp_like = Expression::Function(Box::new(Function::new(
30823                                    "REGEXP_LIKE".to_string(),
30824                                    vec![arg, concat4],
30825                                )));
30826                                // Use Column("TRUE") to output literal TRUE keyword (not boolean 1/0)
30827                                let true_expr =
30828                                    Expression::Column(Box::new(crate::expressions::Column {
30829                                        name: Identifier {
30830                                            name: "TRUE".to_string(),
30831                                            quoted: false,
30832                                            trailing_comments: Vec::new(),
30833                                            span: None,
30834                                        },
30835                                        table: None,
30836                                        join_mark: false,
30837                                        trailing_comments: Vec::new(),
30838                                        span: None,
30839                                        inferred_type: None,
30840                                    }));
30841                                let nvl = Expression::Function(Box::new(Function::new(
30842                                    "NVL".to_string(),
30843                                    vec![regexp_like, true_expr],
30844                                )));
30845                                Ok(nvl)
30846                            }
30847                            _ => Ok(Expression::Function(Box::new(Function::new(
30848                                "IS_ASCII".to_string(),
30849                                vec![arg],
30850                            )))),
30851                        }
30852                    } else {
30853                        Ok(e)
30854                    }
30855                }
30856
30857                Action::StrPositionConvert => {
30858                    // STR_POSITION(haystack, needle[, position[, occurrence]]) -> dialect-specific
30859                    if let Expression::Function(f) = e {
30860                        if f.args.len() < 2 {
30861                            return Ok(Expression::Function(f));
30862                        }
30863                        let mut args = f.args;
30864
30865                        let haystack = args.remove(0);
30866                        let needle = args.remove(0);
30867                        let position = if !args.is_empty() {
30868                            Some(args.remove(0))
30869                        } else {
30870                            Option::None
30871                        };
30872                        let occurrence = if !args.is_empty() {
30873                            Some(args.remove(0))
30874                        } else {
30875                            Option::None
30876                        };
30877
30878                        // Helper to build: STRPOS/INSTR(SUBSTRING(haystack, pos), needle) expansion
30879                        // Returns: CASE/IF WHEN func(SUBSTRING(haystack, pos), needle[, occ]) = 0 THEN 0 ELSE ... + pos - 1 END
30880                        fn build_position_expansion(
30881                            haystack: Expression,
30882                            needle: Expression,
30883                            pos: Expression,
30884                            occurrence: Option<Expression>,
30885                            inner_func: &str,
30886                            wrapper: &str, // "CASE", "IF", "IIF"
30887                        ) -> Expression {
30888                            let substr = Expression::Function(Box::new(Function::new(
30889                                "SUBSTRING".to_string(),
30890                                vec![haystack, pos.clone()],
30891                            )));
30892                            let mut inner_args = vec![substr, needle];
30893                            if let Some(occ) = occurrence {
30894                                inner_args.push(occ);
30895                            }
30896                            let inner_call = Expression::Function(Box::new(Function::new(
30897                                inner_func.to_string(),
30898                                inner_args,
30899                            )));
30900                            let zero =
30901                                Expression::Literal(Box::new(Literal::Number("0".to_string())));
30902                            let one =
30903                                Expression::Literal(Box::new(Literal::Number("1".to_string())));
30904                            let eq_zero = Expression::Eq(Box::new(BinaryOp {
30905                                left: inner_call.clone(),
30906                                right: zero.clone(),
30907                                left_comments: Vec::new(),
30908                                operator_comments: Vec::new(),
30909                                trailing_comments: Vec::new(),
30910                                inferred_type: None,
30911                            }));
30912                            let add_pos = Expression::Add(Box::new(BinaryOp {
30913                                left: inner_call,
30914                                right: pos,
30915                                left_comments: Vec::new(),
30916                                operator_comments: Vec::new(),
30917                                trailing_comments: Vec::new(),
30918                                inferred_type: None,
30919                            }));
30920                            let sub_one = Expression::Sub(Box::new(BinaryOp {
30921                                left: add_pos,
30922                                right: one,
30923                                left_comments: Vec::new(),
30924                                operator_comments: Vec::new(),
30925                                trailing_comments: Vec::new(),
30926                                inferred_type: None,
30927                            }));
30928
30929                            match wrapper {
30930                                "CASE" => Expression::Case(Box::new(Case {
30931                                    operand: Option::None,
30932                                    whens: vec![(eq_zero, zero)],
30933                                    else_: Some(sub_one),
30934                                    comments: Vec::new(),
30935                                    inferred_type: None,
30936                                })),
30937                                "IIF" => Expression::Function(Box::new(Function::new(
30938                                    "IIF".to_string(),
30939                                    vec![eq_zero, zero, sub_one],
30940                                ))),
30941                                _ => Expression::Function(Box::new(Function::new(
30942                                    "IF".to_string(),
30943                                    vec![eq_zero, zero, sub_one],
30944                                ))),
30945                            }
30946                        }
30947
30948                        match target {
30949                            // STRPOS group: Athena, DuckDB, Presto, Trino, Drill
30950                            DialectType::Athena
30951                            | DialectType::DuckDB
30952                            | DialectType::Presto
30953                            | DialectType::Trino
30954                            | DialectType::Drill => {
30955                                if let Some(pos) = position {
30956                                    let wrapper = if matches!(target, DialectType::DuckDB) {
30957                                        "CASE"
30958                                    } else {
30959                                        "IF"
30960                                    };
30961                                    let result = build_position_expansion(
30962                                        haystack, needle, pos, occurrence, "STRPOS", wrapper,
30963                                    );
30964                                    if matches!(target, DialectType::Drill) {
30965                                        // Drill uses backtick-quoted `IF`
30966                                        if let Expression::Function(mut f) = result {
30967                                            f.name = "`IF`".to_string();
30968                                            Ok(Expression::Function(f))
30969                                        } else {
30970                                            Ok(result)
30971                                        }
30972                                    } else {
30973                                        Ok(result)
30974                                    }
30975                                } else {
30976                                    Ok(Expression::Function(Box::new(Function::new(
30977                                        "STRPOS".to_string(),
30978                                        vec![haystack, needle],
30979                                    ))))
30980                                }
30981                            }
30982                            // SQLite: IIF wrapper
30983                            DialectType::SQLite => {
30984                                if let Some(pos) = position {
30985                                    Ok(build_position_expansion(
30986                                        haystack, needle, pos, occurrence, "INSTR", "IIF",
30987                                    ))
30988                                } else {
30989                                    Ok(Expression::Function(Box::new(Function::new(
30990                                        "INSTR".to_string(),
30991                                        vec![haystack, needle],
30992                                    ))))
30993                                }
30994                            }
30995                            // INSTR group: Teradata, BigQuery, Oracle
30996                            DialectType::Teradata | DialectType::BigQuery | DialectType::Oracle => {
30997                                let mut a = vec![haystack, needle];
30998                                if let Some(pos) = position {
30999                                    a.push(pos);
31000                                }
31001                                if let Some(occ) = occurrence {
31002                                    a.push(occ);
31003                                }
31004                                Ok(Expression::Function(Box::new(Function::new(
31005                                    "INSTR".to_string(),
31006                                    a,
31007                                ))))
31008                            }
31009                            // CHARINDEX group: Snowflake, TSQL
31010                            DialectType::Snowflake | DialectType::TSQL | DialectType::Fabric => {
31011                                let mut a = vec![needle, haystack];
31012                                if let Some(pos) = position {
31013                                    a.push(pos);
31014                                }
31015                                Ok(Expression::Function(Box::new(Function::new(
31016                                    "CHARINDEX".to_string(),
31017                                    a,
31018                                ))))
31019                            }
31020                            // POSITION(needle IN haystack): PostgreSQL, Materialize, RisingWave, Redshift
31021                            DialectType::PostgreSQL
31022                            | DialectType::Materialize
31023                            | DialectType::RisingWave
31024                            | DialectType::Redshift => {
31025                                if let Some(pos) = position {
31026                                    // Build: CASE WHEN POSITION(needle IN SUBSTRING(haystack FROM pos)) = 0 THEN 0
31027                                    //   ELSE POSITION(...) + pos - 1 END
31028                                    let substr = Expression::Substring(Box::new(
31029                                        crate::expressions::SubstringFunc {
31030                                            this: haystack,
31031                                            start: pos.clone(),
31032                                            length: Option::None,
31033                                            from_for_syntax: true,
31034                                        },
31035                                    ));
31036                                    let pos_in = Expression::StrPosition(Box::new(
31037                                        crate::expressions::StrPosition {
31038                                            this: Box::new(substr),
31039                                            substr: Some(Box::new(needle)),
31040                                            position: Option::None,
31041                                            occurrence: Option::None,
31042                                        },
31043                                    ));
31044                                    let zero = Expression::Literal(Box::new(Literal::Number(
31045                                        "0".to_string(),
31046                                    )));
31047                                    let one = Expression::Literal(Box::new(Literal::Number(
31048                                        "1".to_string(),
31049                                    )));
31050                                    let eq_zero = Expression::Eq(Box::new(BinaryOp {
31051                                        left: pos_in.clone(),
31052                                        right: zero.clone(),
31053                                        left_comments: Vec::new(),
31054                                        operator_comments: Vec::new(),
31055                                        trailing_comments: Vec::new(),
31056                                        inferred_type: None,
31057                                    }));
31058                                    let add_pos = Expression::Add(Box::new(BinaryOp {
31059                                        left: pos_in,
31060                                        right: pos,
31061                                        left_comments: Vec::new(),
31062                                        operator_comments: Vec::new(),
31063                                        trailing_comments: Vec::new(),
31064                                        inferred_type: None,
31065                                    }));
31066                                    let sub_one = Expression::Sub(Box::new(BinaryOp {
31067                                        left: add_pos,
31068                                        right: one,
31069                                        left_comments: Vec::new(),
31070                                        operator_comments: Vec::new(),
31071                                        trailing_comments: Vec::new(),
31072                                        inferred_type: None,
31073                                    }));
31074                                    Ok(Expression::Case(Box::new(Case {
31075                                        operand: Option::None,
31076                                        whens: vec![(eq_zero, zero)],
31077                                        else_: Some(sub_one),
31078                                        comments: Vec::new(),
31079                                        inferred_type: None,
31080                                    })))
31081                                } else {
31082                                    Ok(Expression::StrPosition(Box::new(
31083                                        crate::expressions::StrPosition {
31084                                            this: Box::new(haystack),
31085                                            substr: Some(Box::new(needle)),
31086                                            position: Option::None,
31087                                            occurrence: Option::None,
31088                                        },
31089                                    )))
31090                                }
31091                            }
31092                            // LOCATE group: MySQL, Hive, Spark, Databricks, Doris
31093                            DialectType::MySQL
31094                            | DialectType::SingleStore
31095                            | DialectType::TiDB
31096                            | DialectType::Hive
31097                            | DialectType::Spark
31098                            | DialectType::Databricks
31099                            | DialectType::Doris
31100                            | DialectType::StarRocks => {
31101                                let mut a = vec![needle, haystack];
31102                                if let Some(pos) = position {
31103                                    a.push(pos);
31104                                }
31105                                Ok(Expression::Function(Box::new(Function::new(
31106                                    "LOCATE".to_string(),
31107                                    a,
31108                                ))))
31109                            }
31110                            // ClickHouse: POSITION(haystack, needle[, position])
31111                            DialectType::ClickHouse => {
31112                                let mut a = vec![haystack, needle];
31113                                if let Some(pos) = position {
31114                                    a.push(pos);
31115                                }
31116                                Ok(Expression::Function(Box::new(Function::new(
31117                                    "POSITION".to_string(),
31118                                    a,
31119                                ))))
31120                            }
31121                            _ => {
31122                                let mut a = vec![haystack, needle];
31123                                if let Some(pos) = position {
31124                                    a.push(pos);
31125                                }
31126                                if let Some(occ) = occurrence {
31127                                    a.push(occ);
31128                                }
31129                                Ok(Expression::Function(Box::new(Function::new(
31130                                    "STR_POSITION".to_string(),
31131                                    a,
31132                                ))))
31133                            }
31134                        }
31135                    } else {
31136                        Ok(e)
31137                    }
31138                }
31139
31140                Action::ArraySumConvert => {
31141                    // ARRAY_SUM(arr) -> dialect-specific
31142                    if let Expression::Function(f) = e {
31143                        let args = f.args;
31144                        match target {
31145                            DialectType::DuckDB => Ok(Expression::Function(Box::new(
31146                                Function::new("LIST_SUM".to_string(), args),
31147                            ))),
31148                            DialectType::Spark | DialectType::Databricks => {
31149                                // AGGREGATE(arr, 0, (acc, x) -> acc + x, acc -> acc)
31150                                let arr = args.into_iter().next().unwrap();
31151                                let zero =
31152                                    Expression::Literal(Box::new(Literal::Number("0".to_string())));
31153                                let acc_id = Identifier::new("acc");
31154                                let x_id = Identifier::new("x");
31155                                let acc = Expression::Identifier(acc_id.clone());
31156                                let x = Expression::Identifier(x_id.clone());
31157                                let add = Expression::Add(Box::new(BinaryOp {
31158                                    left: acc.clone(),
31159                                    right: x,
31160                                    left_comments: Vec::new(),
31161                                    operator_comments: Vec::new(),
31162                                    trailing_comments: Vec::new(),
31163                                    inferred_type: None,
31164                                }));
31165                                let lambda1 =
31166                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
31167                                        parameters: vec![acc_id.clone(), x_id],
31168                                        body: add,
31169                                        colon: false,
31170                                        parameter_types: Vec::new(),
31171                                    }));
31172                                let lambda2 =
31173                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
31174                                        parameters: vec![acc_id],
31175                                        body: acc,
31176                                        colon: false,
31177                                        parameter_types: Vec::new(),
31178                                    }));
31179                                Ok(Expression::Function(Box::new(Function::new(
31180                                    "AGGREGATE".to_string(),
31181                                    vec![arr, zero, lambda1, lambda2],
31182                                ))))
31183                            }
31184                            DialectType::Presto | DialectType::Athena => {
31185                                // Presto/Athena keep ARRAY_SUM natively
31186                                Ok(Expression::Function(Box::new(Function::new(
31187                                    "ARRAY_SUM".to_string(),
31188                                    args,
31189                                ))))
31190                            }
31191                            DialectType::Trino => {
31192                                // REDUCE(arr, 0, (acc, x) -> acc + x, acc -> acc)
31193                                if args.len() == 1 {
31194                                    let arr = args.into_iter().next().unwrap();
31195                                    let zero = Expression::Literal(Box::new(Literal::Number(
31196                                        "0".to_string(),
31197                                    )));
31198                                    let acc_id = Identifier::new("acc");
31199                                    let x_id = Identifier::new("x");
31200                                    let acc = Expression::Identifier(acc_id.clone());
31201                                    let x = Expression::Identifier(x_id.clone());
31202                                    let add = Expression::Add(Box::new(BinaryOp {
31203                                        left: acc.clone(),
31204                                        right: x,
31205                                        left_comments: Vec::new(),
31206                                        operator_comments: Vec::new(),
31207                                        trailing_comments: Vec::new(),
31208                                        inferred_type: None,
31209                                    }));
31210                                    let lambda1 = Expression::Lambda(Box::new(
31211                                        crate::expressions::LambdaExpr {
31212                                            parameters: vec![acc_id.clone(), x_id],
31213                                            body: add,
31214                                            colon: false,
31215                                            parameter_types: Vec::new(),
31216                                        },
31217                                    ));
31218                                    let lambda2 = Expression::Lambda(Box::new(
31219                                        crate::expressions::LambdaExpr {
31220                                            parameters: vec![acc_id],
31221                                            body: acc,
31222                                            colon: false,
31223                                            parameter_types: Vec::new(),
31224                                        },
31225                                    ));
31226                                    Ok(Expression::Function(Box::new(Function::new(
31227                                        "REDUCE".to_string(),
31228                                        vec![arr, zero, lambda1, lambda2],
31229                                    ))))
31230                                } else {
31231                                    Ok(Expression::Function(Box::new(Function::new(
31232                                        "ARRAY_SUM".to_string(),
31233                                        args,
31234                                    ))))
31235                                }
31236                            }
31237                            DialectType::ClickHouse => {
31238                                // arraySum(lambda, arr) or arraySum(arr)
31239                                Ok(Expression::Function(Box::new(Function::new(
31240                                    "arraySum".to_string(),
31241                                    args,
31242                                ))))
31243                            }
31244                            _ => Ok(Expression::Function(Box::new(Function::new(
31245                                "ARRAY_SUM".to_string(),
31246                                args,
31247                            )))),
31248                        }
31249                    } else {
31250                        Ok(e)
31251                    }
31252                }
31253
31254                Action::ArraySizeConvert => {
31255                    if let Expression::Function(f) = e {
31256                        Ok(Expression::Function(Box::new(Function::new(
31257                            "REPEATED_COUNT".to_string(),
31258                            f.args,
31259                        ))))
31260                    } else {
31261                        Ok(e)
31262                    }
31263                }
31264
31265                Action::ArrayAnyConvert => {
31266                    if let Expression::Function(f) = e {
31267                        let mut args = f.args;
31268                        if args.len() == 2 {
31269                            let arr = args.remove(0);
31270                            let lambda = args.remove(0);
31271
31272                            // Extract lambda parameter name and body
31273                            let (param_name, pred_body) =
31274                                if let Expression::Lambda(ref lam) = lambda {
31275                                    let name = if let Some(p) = lam.parameters.first() {
31276                                        p.name.clone()
31277                                    } else {
31278                                        "x".to_string()
31279                                    };
31280                                    (name, lam.body.clone())
31281                                } else {
31282                                    ("x".to_string(), lambda.clone())
31283                                };
31284
31285                            // Helper: build a function call Expression
31286                            let make_func = |name: &str, args: Vec<Expression>| -> Expression {
31287                                Expression::Function(Box::new(Function::new(
31288                                    name.to_string(),
31289                                    args,
31290                                )))
31291                            };
31292
31293                            // Helper: build (len_func(arr) = 0 OR len_func(filter_expr) <> 0) wrapped in Paren
31294                            let build_filter_pattern = |len_func: &str,
31295                                                        len_args_extra: Vec<Expression>,
31296                                                        filter_expr: Expression|
31297                             -> Expression {
31298                                // len_func(arr, ...extra) = 0
31299                                let mut len_arr_args = vec![arr.clone()];
31300                                len_arr_args.extend(len_args_extra.clone());
31301                                let len_arr = make_func(len_func, len_arr_args);
31302                                let eq_zero = Expression::Eq(Box::new(BinaryOp::new(
31303                                    len_arr,
31304                                    Expression::number(0),
31305                                )));
31306
31307                                // len_func(filter_expr, ...extra) <> 0
31308                                let mut len_filter_args = vec![filter_expr];
31309                                len_filter_args.extend(len_args_extra);
31310                                let len_filter = make_func(len_func, len_filter_args);
31311                                let neq_zero = Expression::Neq(Box::new(BinaryOp::new(
31312                                    len_filter,
31313                                    Expression::number(0),
31314                                )));
31315
31316                                // (eq_zero OR neq_zero)
31317                                let or_expr =
31318                                    Expression::Or(Box::new(BinaryOp::new(eq_zero, neq_zero)));
31319                                Expression::Paren(Box::new(Paren {
31320                                    this: or_expr,
31321                                    trailing_comments: Vec::new(),
31322                                }))
31323                            };
31324
31325                            match target {
31326                                DialectType::Trino | DialectType::Presto | DialectType::Athena => {
31327                                    Ok(make_func("ANY_MATCH", vec![arr, lambda]))
31328                                }
31329                                DialectType::ClickHouse => {
31330                                    // (LENGTH(arr) = 0 OR LENGTH(arrayFilter(x -> pred, arr)) <> 0)
31331                                    // ClickHouse arrayFilter takes lambda first, then array
31332                                    let filter_expr =
31333                                        make_func("arrayFilter", vec![lambda, arr.clone()]);
31334                                    Ok(build_filter_pattern("LENGTH", vec![], filter_expr))
31335                                }
31336                                DialectType::Databricks | DialectType::Spark => {
31337                                    // (SIZE(arr) = 0 OR SIZE(FILTER(arr, x -> pred)) <> 0)
31338                                    let filter_expr =
31339                                        make_func("FILTER", vec![arr.clone(), lambda]);
31340                                    Ok(build_filter_pattern("SIZE", vec![], filter_expr))
31341                                }
31342                                DialectType::DuckDB => {
31343                                    // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(LIST_FILTER(arr, x -> pred)) <> 0)
31344                                    let filter_expr =
31345                                        make_func("LIST_FILTER", vec![arr.clone(), lambda]);
31346                                    Ok(build_filter_pattern("ARRAY_LENGTH", vec![], filter_expr))
31347                                }
31348                                DialectType::Teradata => {
31349                                    // (CARDINALITY(arr) = 0 OR CARDINALITY(FILTER(arr, x -> pred)) <> 0)
31350                                    let filter_expr =
31351                                        make_func("FILTER", vec![arr.clone(), lambda]);
31352                                    Ok(build_filter_pattern("CARDINALITY", vec![], filter_expr))
31353                                }
31354                                DialectType::BigQuery => {
31355                                    // (ARRAY_LENGTH(arr) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS x WHERE pred)) <> 0)
31356                                    // Build: SELECT x FROM UNNEST(arr) AS x WHERE pred
31357                                    let param_col = Expression::column(&param_name);
31358                                    let unnest_expr = Expression::Unnest(Box::new(
31359                                        crate::expressions::UnnestFunc {
31360                                            this: arr.clone(),
31361                                            expressions: vec![],
31362                                            with_ordinality: false,
31363                                            alias: Some(Identifier::new(&param_name)),
31364                                            offset_alias: None,
31365                                        },
31366                                    ));
31367                                    let mut sel = crate::expressions::Select::default();
31368                                    sel.expressions = vec![param_col];
31369                                    sel.from = Some(crate::expressions::From {
31370                                        expressions: vec![unnest_expr],
31371                                    });
31372                                    sel.where_clause =
31373                                        Some(crate::expressions::Where { this: pred_body });
31374                                    let array_subquery =
31375                                        make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
31376                                    Ok(build_filter_pattern("ARRAY_LENGTH", vec![], array_subquery))
31377                                }
31378                                DialectType::PostgreSQL => {
31379                                    // (ARRAY_LENGTH(arr, 1) = 0 OR ARRAY_LENGTH(ARRAY(SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred), 1) <> 0)
31380                                    // Build: SELECT x FROM UNNEST(arr) AS _t0(x) WHERE pred
31381                                    let param_col = Expression::column(&param_name);
31382                                    // For PostgreSQL, UNNEST uses AS _t0(x) syntax - use TableAlias
31383                                    let unnest_with_alias =
31384                                        Expression::Alias(Box::new(crate::expressions::Alias {
31385                                            this: Expression::Unnest(Box::new(
31386                                                crate::expressions::UnnestFunc {
31387                                                    this: arr.clone(),
31388                                                    expressions: vec![],
31389                                                    with_ordinality: false,
31390                                                    alias: None,
31391                                                    offset_alias: None,
31392                                                },
31393                                            )),
31394                                            alias: Identifier::new("_t0"),
31395                                            column_aliases: vec![Identifier::new(&param_name)],
31396                                            alias_explicit_as: false,
31397                                            alias_keyword: None,
31398                                            pre_alias_comments: Vec::new(),
31399                                            trailing_comments: Vec::new(),
31400                                            inferred_type: None,
31401                                        }));
31402                                    let mut sel = crate::expressions::Select::default();
31403                                    sel.expressions = vec![param_col];
31404                                    sel.from = Some(crate::expressions::From {
31405                                        expressions: vec![unnest_with_alias],
31406                                    });
31407                                    sel.where_clause =
31408                                        Some(crate::expressions::Where { this: pred_body });
31409                                    let array_subquery =
31410                                        make_func("ARRAY", vec![Expression::Select(Box::new(sel))]);
31411                                    Ok(build_filter_pattern(
31412                                        "ARRAY_LENGTH",
31413                                        vec![Expression::number(1)],
31414                                        array_subquery,
31415                                    ))
31416                                }
31417                                _ => Ok(Expression::Function(Box::new(Function::new(
31418                                    "ARRAY_ANY".to_string(),
31419                                    vec![arr, lambda],
31420                                )))),
31421                            }
31422                        } else {
31423                            Ok(Expression::Function(Box::new(Function::new(
31424                                "ARRAY_ANY".to_string(),
31425                                args,
31426                            ))))
31427                        }
31428                    } else {
31429                        Ok(e)
31430                    }
31431                }
31432
31433                Action::DecodeSimplify => {
31434                    // DECODE(x, search1, result1, ..., default) -> CASE WHEN ... THEN result1 ... [ELSE default] END
31435                    // For literal search values: CASE WHEN x = search THEN result
31436                    // For NULL search: CASE WHEN x IS NULL THEN result
31437                    // For non-literal (column, expr): CASE WHEN x = search OR (x IS NULL AND search IS NULL) THEN result
31438                    fn is_decode_literal(e: &Expression) -> bool {
31439                        matches!(
31440                            e,
31441                            Expression::Literal(_) | Expression::Boolean(_) | Expression::Neg(_)
31442                        )
31443                    }
31444
31445                    let build_decode_case =
31446                        |this_expr: Expression,
31447                         pairs: Vec<(Expression, Expression)>,
31448                         default: Option<Expression>| {
31449                            let whens: Vec<(Expression, Expression)> = pairs
31450                                .into_iter()
31451                                .map(|(search, result)| {
31452                                    if matches!(&search, Expression::Null(_)) {
31453                                        // NULL search -> IS NULL
31454                                        let condition = Expression::Is(Box::new(BinaryOp {
31455                                            left: this_expr.clone(),
31456                                            right: Expression::Null(crate::expressions::Null),
31457                                            left_comments: Vec::new(),
31458                                            operator_comments: Vec::new(),
31459                                            trailing_comments: Vec::new(),
31460                                            inferred_type: None,
31461                                        }));
31462                                        (condition, result)
31463                                    } else if is_decode_literal(&search)
31464                                        || is_decode_literal(&this_expr)
31465                                    {
31466                                        // At least one side is a literal -> simple equality (no NULL check needed)
31467                                        let eq = Expression::Eq(Box::new(BinaryOp {
31468                                            left: this_expr.clone(),
31469                                            right: search,
31470                                            left_comments: Vec::new(),
31471                                            operator_comments: Vec::new(),
31472                                            trailing_comments: Vec::new(),
31473                                            inferred_type: None,
31474                                        }));
31475                                        (eq, result)
31476                                    } else {
31477                                        // Non-literal -> null-safe comparison
31478                                        let needs_paren = matches!(
31479                                            &search,
31480                                            Expression::Eq(_)
31481                                                | Expression::Neq(_)
31482                                                | Expression::Gt(_)
31483                                                | Expression::Gte(_)
31484                                                | Expression::Lt(_)
31485                                                | Expression::Lte(_)
31486                                        );
31487                                        let search_ref = if needs_paren {
31488                                            Expression::Paren(Box::new(crate::expressions::Paren {
31489                                                this: search.clone(),
31490                                                trailing_comments: Vec::new(),
31491                                            }))
31492                                        } else {
31493                                            search.clone()
31494                                        };
31495                                        // Build: x = search OR (x IS NULL AND search IS NULL)
31496                                        let eq = Expression::Eq(Box::new(BinaryOp {
31497                                            left: this_expr.clone(),
31498                                            right: search_ref,
31499                                            left_comments: Vec::new(),
31500                                            operator_comments: Vec::new(),
31501                                            trailing_comments: Vec::new(),
31502                                            inferred_type: None,
31503                                        }));
31504                                        let search_in_null = if needs_paren {
31505                                            Expression::Paren(Box::new(crate::expressions::Paren {
31506                                                this: search.clone(),
31507                                                trailing_comments: Vec::new(),
31508                                            }))
31509                                        } else {
31510                                            search.clone()
31511                                        };
31512                                        let x_is_null = Expression::Is(Box::new(BinaryOp {
31513                                            left: this_expr.clone(),
31514                                            right: Expression::Null(crate::expressions::Null),
31515                                            left_comments: Vec::new(),
31516                                            operator_comments: Vec::new(),
31517                                            trailing_comments: Vec::new(),
31518                                            inferred_type: None,
31519                                        }));
31520                                        let search_is_null = Expression::Is(Box::new(BinaryOp {
31521                                            left: search_in_null,
31522                                            right: Expression::Null(crate::expressions::Null),
31523                                            left_comments: Vec::new(),
31524                                            operator_comments: Vec::new(),
31525                                            trailing_comments: Vec::new(),
31526                                            inferred_type: None,
31527                                        }));
31528                                        let both_null = Expression::And(Box::new(BinaryOp {
31529                                            left: x_is_null,
31530                                            right: search_is_null,
31531                                            left_comments: Vec::new(),
31532                                            operator_comments: Vec::new(),
31533                                            trailing_comments: Vec::new(),
31534                                            inferred_type: None,
31535                                        }));
31536                                        let condition = Expression::Or(Box::new(BinaryOp {
31537                                            left: eq,
31538                                            right: Expression::Paren(Box::new(
31539                                                crate::expressions::Paren {
31540                                                    this: both_null,
31541                                                    trailing_comments: Vec::new(),
31542                                                },
31543                                            )),
31544                                            left_comments: Vec::new(),
31545                                            operator_comments: Vec::new(),
31546                                            trailing_comments: Vec::new(),
31547                                            inferred_type: None,
31548                                        }));
31549                                        (condition, result)
31550                                    }
31551                                })
31552                                .collect();
31553                            Expression::Case(Box::new(Case {
31554                                operand: None,
31555                                whens,
31556                                else_: default,
31557                                comments: Vec::new(),
31558                                inferred_type: None,
31559                            }))
31560                        };
31561
31562                    if let Expression::Decode(decode) = e {
31563                        Ok(build_decode_case(
31564                            decode.this,
31565                            decode.search_results,
31566                            decode.default,
31567                        ))
31568                    } else if let Expression::DecodeCase(dc) = e {
31569                        // DecodeCase has flat expressions: [x, s1, r1, s2, r2, ..., default?]
31570                        let mut exprs = dc.expressions;
31571                        if exprs.len() < 3 {
31572                            return Ok(Expression::DecodeCase(Box::new(
31573                                crate::expressions::DecodeCase { expressions: exprs },
31574                            )));
31575                        }
31576                        let this_expr = exprs.remove(0);
31577                        let mut pairs = Vec::new();
31578                        let mut default = None;
31579                        let mut i = 0;
31580                        while i + 1 < exprs.len() {
31581                            pairs.push((exprs[i].clone(), exprs[i + 1].clone()));
31582                            i += 2;
31583                        }
31584                        if i < exprs.len() {
31585                            // Odd remaining element is the default
31586                            default = Some(exprs[i].clone());
31587                        }
31588                        Ok(build_decode_case(this_expr, pairs, default))
31589                    } else {
31590                        Ok(e)
31591                    }
31592                }
31593
31594                Action::CreateTableLikeToCtas => {
31595                    // CREATE TABLE a LIKE b -> CREATE TABLE a AS SELECT * FROM b LIMIT 0
31596                    if let Expression::CreateTable(ct) = e {
31597                        let like_source = ct.constraints.iter().find_map(|c| {
31598                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
31599                                Some(source.clone())
31600                            } else {
31601                                None
31602                            }
31603                        });
31604                        if let Some(source_table) = like_source {
31605                            let mut new_ct = *ct;
31606                            new_ct.constraints.clear();
31607                            // Build: SELECT * FROM b LIMIT 0
31608                            let select = Expression::Select(Box::new(crate::expressions::Select {
31609                                expressions: vec![Expression::Star(crate::expressions::Star {
31610                                    table: None,
31611                                    except: None,
31612                                    replace: None,
31613                                    rename: None,
31614                                    trailing_comments: Vec::new(),
31615                                    span: None,
31616                                })],
31617                                from: Some(crate::expressions::From {
31618                                    expressions: vec![Expression::Table(Box::new(source_table))],
31619                                }),
31620                                limit: Some(crate::expressions::Limit {
31621                                    this: Expression::Literal(Box::new(Literal::Number(
31622                                        "0".to_string(),
31623                                    ))),
31624                                    percent: false,
31625                                    comments: Vec::new(),
31626                                }),
31627                                ..Default::default()
31628                            }));
31629                            new_ct.as_select = Some(select);
31630                            Ok(Expression::CreateTable(Box::new(new_ct)))
31631                        } else {
31632                            Ok(Expression::CreateTable(ct))
31633                        }
31634                    } else {
31635                        Ok(e)
31636                    }
31637                }
31638
31639                Action::CreateTableLikeToSelectInto => {
31640                    // CREATE TABLE a LIKE b -> SELECT TOP 0 * INTO a FROM b AS temp
31641                    if let Expression::CreateTable(ct) = e {
31642                        let like_source = ct.constraints.iter().find_map(|c| {
31643                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
31644                                Some(source.clone())
31645                            } else {
31646                                None
31647                            }
31648                        });
31649                        if let Some(source_table) = like_source {
31650                            let mut aliased_source = source_table;
31651                            aliased_source.alias = Some(Identifier::new("temp"));
31652                            // Build: SELECT TOP 0 * INTO a FROM b AS temp
31653                            let select = Expression::Select(Box::new(crate::expressions::Select {
31654                                expressions: vec![Expression::Star(crate::expressions::Star {
31655                                    table: None,
31656                                    except: None,
31657                                    replace: None,
31658                                    rename: None,
31659                                    trailing_comments: Vec::new(),
31660                                    span: None,
31661                                })],
31662                                from: Some(crate::expressions::From {
31663                                    expressions: vec![Expression::Table(Box::new(aliased_source))],
31664                                }),
31665                                into: Some(crate::expressions::SelectInto {
31666                                    this: Expression::Table(Box::new(ct.name.clone())),
31667                                    temporary: false,
31668                                    unlogged: false,
31669                                    bulk_collect: false,
31670                                    expressions: Vec::new(),
31671                                }),
31672                                top: Some(crate::expressions::Top {
31673                                    this: Expression::Literal(Box::new(Literal::Number(
31674                                        "0".to_string(),
31675                                    ))),
31676                                    percent: false,
31677                                    with_ties: false,
31678                                    parenthesized: false,
31679                                }),
31680                                ..Default::default()
31681                            }));
31682                            Ok(select)
31683                        } else {
31684                            Ok(Expression::CreateTable(ct))
31685                        }
31686                    } else {
31687                        Ok(e)
31688                    }
31689                }
31690
31691                Action::CreateTableLikeToAs => {
31692                    // CREATE TABLE a LIKE b -> CREATE TABLE a AS b (ClickHouse)
31693                    if let Expression::CreateTable(ct) = e {
31694                        let like_source = ct.constraints.iter().find_map(|c| {
31695                            if let crate::expressions::TableConstraint::Like { source, .. } = c {
31696                                Some(source.clone())
31697                            } else {
31698                                None
31699                            }
31700                        });
31701                        if let Some(source_table) = like_source {
31702                            let mut new_ct = *ct;
31703                            new_ct.constraints.clear();
31704                            // AS b (just a table reference, not a SELECT)
31705                            new_ct.as_select = Some(Expression::Table(Box::new(source_table)));
31706                            Ok(Expression::CreateTable(Box::new(new_ct)))
31707                        } else {
31708                            Ok(Expression::CreateTable(ct))
31709                        }
31710                    } else {
31711                        Ok(e)
31712                    }
31713                }
31714
31715                Action::TsOrDsToDateConvert => {
31716                    // TS_OR_DS_TO_DATE(x[, fmt]) -> dialect-specific date conversion
31717                    if let Expression::Function(f) = e {
31718                        let mut args = f.args;
31719                        let this = args.remove(0);
31720                        let fmt = if !args.is_empty() {
31721                            match &args[0] {
31722                                Expression::Literal(lit)
31723                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31724                                {
31725                                    let Literal::String(s) = lit.as_ref() else {
31726                                        unreachable!()
31727                                    };
31728                                    Some(s.clone())
31729                                }
31730                                _ => None,
31731                            }
31732                        } else {
31733                            None
31734                        };
31735                        Ok(Expression::TsOrDsToDate(Box::new(
31736                            crate::expressions::TsOrDsToDate {
31737                                this: Box::new(this),
31738                                format: fmt,
31739                                safe: None,
31740                            },
31741                        )))
31742                    } else {
31743                        Ok(e)
31744                    }
31745                }
31746
31747                Action::TsOrDsToDateStrConvert => {
31748                    // TS_OR_DS_TO_DATE_STR(x) -> SUBSTRING(CAST(x AS type), 1, 10)
31749                    if let Expression::Function(f) = e {
31750                        let arg = f.args.into_iter().next().unwrap();
31751                        let str_type = match target {
31752                            DialectType::DuckDB
31753                            | DialectType::PostgreSQL
31754                            | DialectType::Materialize => DataType::Text,
31755                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
31756                                DataType::Custom {
31757                                    name: "STRING".to_string(),
31758                                }
31759                            }
31760                            DialectType::Presto
31761                            | DialectType::Trino
31762                            | DialectType::Athena
31763                            | DialectType::Drill => DataType::VarChar {
31764                                length: None,
31765                                parenthesized_length: false,
31766                            },
31767                            DialectType::MySQL | DialectType::Doris | DialectType::StarRocks => {
31768                                DataType::Custom {
31769                                    name: "STRING".to_string(),
31770                                }
31771                            }
31772                            _ => DataType::VarChar {
31773                                length: None,
31774                                parenthesized_length: false,
31775                            },
31776                        };
31777                        let cast_expr = Expression::Cast(Box::new(Cast {
31778                            this: arg,
31779                            to: str_type,
31780                            double_colon_syntax: false,
31781                            trailing_comments: Vec::new(),
31782                            format: None,
31783                            default: None,
31784                            inferred_type: None,
31785                        }));
31786                        Ok(Expression::Substring(Box::new(
31787                            crate::expressions::SubstringFunc {
31788                                this: cast_expr,
31789                                start: Expression::number(1),
31790                                length: Some(Expression::number(10)),
31791                                from_for_syntax: false,
31792                            },
31793                        )))
31794                    } else {
31795                        Ok(e)
31796                    }
31797                }
31798
31799                Action::DateStrToDateConvert => {
31800                    // DATE_STR_TO_DATE(x) -> dialect-specific
31801                    if let Expression::Function(f) = e {
31802                        let arg = f.args.into_iter().next().unwrap();
31803                        match target {
31804                            DialectType::SQLite => {
31805                                // SQLite: just the bare expression (dates are strings)
31806                                Ok(arg)
31807                            }
31808                            _ => Ok(Expression::Cast(Box::new(Cast {
31809                                this: arg,
31810                                to: DataType::Date,
31811                                double_colon_syntax: false,
31812                                trailing_comments: Vec::new(),
31813                                format: None,
31814                                default: None,
31815                                inferred_type: None,
31816                            }))),
31817                        }
31818                    } else {
31819                        Ok(e)
31820                    }
31821                }
31822
31823                Action::TimeStrToDateConvert => {
31824                    // TIME_STR_TO_DATE(x) -> dialect-specific
31825                    if let Expression::Function(f) = e {
31826                        let arg = f.args.into_iter().next().unwrap();
31827                        match target {
31828                            DialectType::Hive
31829                            | DialectType::Doris
31830                            | DialectType::StarRocks
31831                            | DialectType::Snowflake => Ok(Expression::Function(Box::new(
31832                                Function::new("TO_DATE".to_string(), vec![arg]),
31833                            ))),
31834                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
31835                                // Presto: CAST(x AS TIMESTAMP)
31836                                Ok(Expression::Cast(Box::new(Cast {
31837                                    this: arg,
31838                                    to: DataType::Timestamp {
31839                                        timezone: false,
31840                                        precision: None,
31841                                    },
31842                                    double_colon_syntax: false,
31843                                    trailing_comments: Vec::new(),
31844                                    format: None,
31845                                    default: None,
31846                                    inferred_type: None,
31847                                })))
31848                            }
31849                            _ => {
31850                                // Default: CAST(x AS DATE)
31851                                Ok(Expression::Cast(Box::new(Cast {
31852                                    this: arg,
31853                                    to: DataType::Date,
31854                                    double_colon_syntax: false,
31855                                    trailing_comments: Vec::new(),
31856                                    format: None,
31857                                    default: None,
31858                                    inferred_type: None,
31859                                })))
31860                            }
31861                        }
31862                    } else {
31863                        Ok(e)
31864                    }
31865                }
31866
31867                Action::TimeStrToTimeConvert => {
31868                    // TIME_STR_TO_TIME(x[, zone]) -> dialect-specific CAST to timestamp type
31869                    if let Expression::Function(f) = e {
31870                        let mut args = f.args;
31871                        let this = args.remove(0);
31872                        let zone = if !args.is_empty() {
31873                            match &args[0] {
31874                                Expression::Literal(lit)
31875                                    if matches!(lit.as_ref(), Literal::String(_)) =>
31876                                {
31877                                    let Literal::String(s) = lit.as_ref() else {
31878                                        unreachable!()
31879                                    };
31880                                    Some(s.clone())
31881                                }
31882                                _ => None,
31883                            }
31884                        } else {
31885                            None
31886                        };
31887                        let has_zone = zone.is_some();
31888
31889                        match target {
31890                            DialectType::SQLite => {
31891                                // SQLite: just the bare expression
31892                                Ok(this)
31893                            }
31894                            DialectType::MySQL => {
31895                                if has_zone {
31896                                    // MySQL with zone: TIMESTAMP(x)
31897                                    Ok(Expression::Function(Box::new(Function::new(
31898                                        "TIMESTAMP".to_string(),
31899                                        vec![this],
31900                                    ))))
31901                                } else {
31902                                    // MySQL: CAST(x AS DATETIME) or with precision
31903                                    // Use DataType::Custom to avoid MySQL's transform_cast converting
31904                                    // CAST(x AS TIMESTAMP) -> TIMESTAMP(x)
31905                                    let precision = if let Expression::Literal(ref lit) = this {
31906                                        if let Literal::String(ref s) = lit.as_ref() {
31907                                            if let Some(dot_pos) = s.rfind('.') {
31908                                                let frac = &s[dot_pos + 1..];
31909                                                let digit_count = frac
31910                                                    .chars()
31911                                                    .take_while(|c| c.is_ascii_digit())
31912                                                    .count();
31913                                                if digit_count > 0 {
31914                                                    Some(digit_count)
31915                                                } else {
31916                                                    None
31917                                                }
31918                                            } else {
31919                                                None
31920                                            }
31921                                        } else {
31922                                            None
31923                                        }
31924                                    } else {
31925                                        None
31926                                    };
31927                                    let type_name = match precision {
31928                                        Some(p) => format!("DATETIME({})", p),
31929                                        None => "DATETIME".to_string(),
31930                                    };
31931                                    Ok(Expression::Cast(Box::new(Cast {
31932                                        this,
31933                                        to: DataType::Custom { name: type_name },
31934                                        double_colon_syntax: false,
31935                                        trailing_comments: Vec::new(),
31936                                        format: None,
31937                                        default: None,
31938                                        inferred_type: None,
31939                                    })))
31940                                }
31941                            }
31942                            DialectType::ClickHouse => {
31943                                if has_zone {
31944                                    // ClickHouse with zone: CAST(x AS DateTime64(6, 'zone'))
31945                                    // We need to strip the timezone offset from the literal if present
31946                                    let clean_this = if let Expression::Literal(ref lit) = this {
31947                                        if let Literal::String(ref s) = lit.as_ref() {
31948                                            // Strip timezone offset like "-08:00" or "+00:00"
31949                                            let re_offset = s.rfind(|c: char| c == '+' || c == '-');
31950                                            if let Some(offset_pos) = re_offset {
31951                                                if offset_pos > 10 {
31952                                                    // After the date part
31953                                                    let trimmed = s[..offset_pos].to_string();
31954                                                    Expression::Literal(Box::new(Literal::String(
31955                                                        trimmed,
31956                                                    )))
31957                                                } else {
31958                                                    this.clone()
31959                                                }
31960                                            } else {
31961                                                this.clone()
31962                                            }
31963                                        } else {
31964                                            this.clone()
31965                                        }
31966                                    } else {
31967                                        this.clone()
31968                                    };
31969                                    let zone_str = zone.unwrap();
31970                                    // Build: CAST(x AS DateTime64(6, 'zone'))
31971                                    let type_name = format!("DateTime64(6, '{}')", zone_str);
31972                                    Ok(Expression::Cast(Box::new(Cast {
31973                                        this: clean_this,
31974                                        to: DataType::Custom { name: type_name },
31975                                        double_colon_syntax: false,
31976                                        trailing_comments: Vec::new(),
31977                                        format: None,
31978                                        default: None,
31979                                        inferred_type: None,
31980                                    })))
31981                                } else {
31982                                    Ok(Expression::Cast(Box::new(Cast {
31983                                        this,
31984                                        to: DataType::Custom {
31985                                            name: "DateTime64(6)".to_string(),
31986                                        },
31987                                        double_colon_syntax: false,
31988                                        trailing_comments: Vec::new(),
31989                                        format: None,
31990                                        default: None,
31991                                        inferred_type: None,
31992                                    })))
31993                                }
31994                            }
31995                            DialectType::BigQuery => {
31996                                if has_zone {
31997                                    // BigQuery with zone: CAST(x AS TIMESTAMP)
31998                                    Ok(Expression::Cast(Box::new(Cast {
31999                                        this,
32000                                        to: DataType::Timestamp {
32001                                            timezone: false,
32002                                            precision: None,
32003                                        },
32004                                        double_colon_syntax: false,
32005                                        trailing_comments: Vec::new(),
32006                                        format: None,
32007                                        default: None,
32008                                        inferred_type: None,
32009                                    })))
32010                                } else {
32011                                    // BigQuery: CAST(x AS DATETIME) - Timestamp{tz:false} renders as DATETIME for BigQuery
32012                                    Ok(Expression::Cast(Box::new(Cast {
32013                                        this,
32014                                        to: DataType::Custom {
32015                                            name: "DATETIME".to_string(),
32016                                        },
32017                                        double_colon_syntax: false,
32018                                        trailing_comments: Vec::new(),
32019                                        format: None,
32020                                        default: None,
32021                                        inferred_type: None,
32022                                    })))
32023                                }
32024                            }
32025                            DialectType::Doris => {
32026                                // Doris: CAST(x AS DATETIME)
32027                                Ok(Expression::Cast(Box::new(Cast {
32028                                    this,
32029                                    to: DataType::Custom {
32030                                        name: "DATETIME".to_string(),
32031                                    },
32032                                    double_colon_syntax: false,
32033                                    trailing_comments: Vec::new(),
32034                                    format: None,
32035                                    default: None,
32036                                    inferred_type: None,
32037                                })))
32038                            }
32039                            DialectType::TSQL | DialectType::Fabric => {
32040                                if has_zone {
32041                                    // TSQL with zone: CAST(x AS DATETIMEOFFSET) AT TIME ZONE 'UTC'
32042                                    let cast_expr = Expression::Cast(Box::new(Cast {
32043                                        this,
32044                                        to: DataType::Custom {
32045                                            name: "DATETIMEOFFSET".to_string(),
32046                                        },
32047                                        double_colon_syntax: false,
32048                                        trailing_comments: Vec::new(),
32049                                        format: None,
32050                                        default: None,
32051                                        inferred_type: None,
32052                                    }));
32053                                    Ok(Expression::AtTimeZone(Box::new(
32054                                        crate::expressions::AtTimeZone {
32055                                            this: cast_expr,
32056                                            zone: Expression::Literal(Box::new(Literal::String(
32057                                                "UTC".to_string(),
32058                                            ))),
32059                                        },
32060                                    )))
32061                                } else {
32062                                    // TSQL: CAST(x AS DATETIME2)
32063                                    Ok(Expression::Cast(Box::new(Cast {
32064                                        this,
32065                                        to: DataType::Custom {
32066                                            name: "DATETIME2".to_string(),
32067                                        },
32068                                        double_colon_syntax: false,
32069                                        trailing_comments: Vec::new(),
32070                                        format: None,
32071                                        default: None,
32072                                        inferred_type: None,
32073                                    })))
32074                                }
32075                            }
32076                            DialectType::DuckDB => {
32077                                if has_zone {
32078                                    // DuckDB with zone: CAST(x AS TIMESTAMPTZ)
32079                                    Ok(Expression::Cast(Box::new(Cast {
32080                                        this,
32081                                        to: DataType::Timestamp {
32082                                            timezone: true,
32083                                            precision: None,
32084                                        },
32085                                        double_colon_syntax: false,
32086                                        trailing_comments: Vec::new(),
32087                                        format: None,
32088                                        default: None,
32089                                        inferred_type: None,
32090                                    })))
32091                                } else {
32092                                    // DuckDB: CAST(x AS TIMESTAMP)
32093                                    Ok(Expression::Cast(Box::new(Cast {
32094                                        this,
32095                                        to: DataType::Timestamp {
32096                                            timezone: false,
32097                                            precision: None,
32098                                        },
32099                                        double_colon_syntax: false,
32100                                        trailing_comments: Vec::new(),
32101                                        format: None,
32102                                        default: None,
32103                                        inferred_type: None,
32104                                    })))
32105                                }
32106                            }
32107                            DialectType::PostgreSQL
32108                            | DialectType::Materialize
32109                            | DialectType::RisingWave => {
32110                                if has_zone {
32111                                    // PostgreSQL with zone: CAST(x AS TIMESTAMPTZ)
32112                                    Ok(Expression::Cast(Box::new(Cast {
32113                                        this,
32114                                        to: DataType::Timestamp {
32115                                            timezone: true,
32116                                            precision: None,
32117                                        },
32118                                        double_colon_syntax: false,
32119                                        trailing_comments: Vec::new(),
32120                                        format: None,
32121                                        default: None,
32122                                        inferred_type: None,
32123                                    })))
32124                                } else {
32125                                    // PostgreSQL: CAST(x AS TIMESTAMP)
32126                                    Ok(Expression::Cast(Box::new(Cast {
32127                                        this,
32128                                        to: DataType::Timestamp {
32129                                            timezone: false,
32130                                            precision: None,
32131                                        },
32132                                        double_colon_syntax: false,
32133                                        trailing_comments: Vec::new(),
32134                                        format: None,
32135                                        default: None,
32136                                        inferred_type: None,
32137                                    })))
32138                                }
32139                            }
32140                            DialectType::Snowflake => {
32141                                if has_zone {
32142                                    // Snowflake with zone: CAST(x AS TIMESTAMPTZ)
32143                                    Ok(Expression::Cast(Box::new(Cast {
32144                                        this,
32145                                        to: DataType::Timestamp {
32146                                            timezone: true,
32147                                            precision: None,
32148                                        },
32149                                        double_colon_syntax: false,
32150                                        trailing_comments: Vec::new(),
32151                                        format: None,
32152                                        default: None,
32153                                        inferred_type: None,
32154                                    })))
32155                                } else {
32156                                    // Snowflake: CAST(x AS TIMESTAMP)
32157                                    Ok(Expression::Cast(Box::new(Cast {
32158                                        this,
32159                                        to: DataType::Timestamp {
32160                                            timezone: false,
32161                                            precision: None,
32162                                        },
32163                                        double_colon_syntax: false,
32164                                        trailing_comments: Vec::new(),
32165                                        format: None,
32166                                        default: None,
32167                                        inferred_type: None,
32168                                    })))
32169                                }
32170                            }
32171                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32172                                if has_zone {
32173                                    // Presto/Trino with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
32174                                    // Check for precision from sub-second digits
32175                                    let precision = if let Expression::Literal(ref lit) = this {
32176                                        if let Literal::String(ref s) = lit.as_ref() {
32177                                            if let Some(dot_pos) = s.rfind('.') {
32178                                                let frac = &s[dot_pos + 1..];
32179                                                let digit_count = frac
32180                                                    .chars()
32181                                                    .take_while(|c| c.is_ascii_digit())
32182                                                    .count();
32183                                                if digit_count > 0
32184                                                    && matches!(target, DialectType::Trino)
32185                                                {
32186                                                    Some(digit_count as u32)
32187                                                } else {
32188                                                    None
32189                                                }
32190                                            } else {
32191                                                None
32192                                            }
32193                                        } else {
32194                                            None
32195                                        }
32196                                    } else {
32197                                        None
32198                                    };
32199                                    let dt = if let Some(prec) = precision {
32200                                        DataType::Timestamp {
32201                                            timezone: true,
32202                                            precision: Some(prec),
32203                                        }
32204                                    } else {
32205                                        DataType::Timestamp {
32206                                            timezone: true,
32207                                            precision: None,
32208                                        }
32209                                    };
32210                                    Ok(Expression::Cast(Box::new(Cast {
32211                                        this,
32212                                        to: dt,
32213                                        double_colon_syntax: false,
32214                                        trailing_comments: Vec::new(),
32215                                        format: None,
32216                                        default: None,
32217                                        inferred_type: None,
32218                                    })))
32219                                } else {
32220                                    // Check for sub-second precision for Trino
32221                                    let precision = if let Expression::Literal(ref lit) = this {
32222                                        if let Literal::String(ref s) = lit.as_ref() {
32223                                            if let Some(dot_pos) = s.rfind('.') {
32224                                                let frac = &s[dot_pos + 1..];
32225                                                let digit_count = frac
32226                                                    .chars()
32227                                                    .take_while(|c| c.is_ascii_digit())
32228                                                    .count();
32229                                                if digit_count > 0
32230                                                    && matches!(target, DialectType::Trino)
32231                                                {
32232                                                    Some(digit_count as u32)
32233                                                } else {
32234                                                    None
32235                                                }
32236                                            } else {
32237                                                None
32238                                            }
32239                                        } else {
32240                                            None
32241                                        }
32242                                    } else {
32243                                        None
32244                                    };
32245                                    let dt = DataType::Timestamp {
32246                                        timezone: false,
32247                                        precision,
32248                                    };
32249                                    Ok(Expression::Cast(Box::new(Cast {
32250                                        this,
32251                                        to: dt,
32252                                        double_colon_syntax: false,
32253                                        trailing_comments: Vec::new(),
32254                                        format: None,
32255                                        default: None,
32256                                        inferred_type: None,
32257                                    })))
32258                                }
32259                            }
32260                            DialectType::Redshift => {
32261                                if has_zone {
32262                                    // Redshift with zone: CAST(x AS TIMESTAMP WITH TIME ZONE)
32263                                    Ok(Expression::Cast(Box::new(Cast {
32264                                        this,
32265                                        to: DataType::Timestamp {
32266                                            timezone: true,
32267                                            precision: None,
32268                                        },
32269                                        double_colon_syntax: false,
32270                                        trailing_comments: Vec::new(),
32271                                        format: None,
32272                                        default: None,
32273                                        inferred_type: None,
32274                                    })))
32275                                } else {
32276                                    // Redshift: CAST(x AS TIMESTAMP)
32277                                    Ok(Expression::Cast(Box::new(Cast {
32278                                        this,
32279                                        to: DataType::Timestamp {
32280                                            timezone: false,
32281                                            precision: None,
32282                                        },
32283                                        double_colon_syntax: false,
32284                                        trailing_comments: Vec::new(),
32285                                        format: None,
32286                                        default: None,
32287                                        inferred_type: None,
32288                                    })))
32289                                }
32290                            }
32291                            _ => {
32292                                // Default: CAST(x AS TIMESTAMP)
32293                                Ok(Expression::Cast(Box::new(Cast {
32294                                    this,
32295                                    to: DataType::Timestamp {
32296                                        timezone: false,
32297                                        precision: None,
32298                                    },
32299                                    double_colon_syntax: false,
32300                                    trailing_comments: Vec::new(),
32301                                    format: None,
32302                                    default: None,
32303                                    inferred_type: None,
32304                                })))
32305                            }
32306                        }
32307                    } else {
32308                        Ok(e)
32309                    }
32310                }
32311
32312                Action::DateToDateStrConvert => {
32313                    // DATE_TO_DATE_STR(x) -> CAST(x AS text_type) per dialect
32314                    if let Expression::Function(f) = e {
32315                        let arg = f.args.into_iter().next().unwrap();
32316                        let str_type = match target {
32317                            DialectType::DuckDB => DataType::Text,
32318                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32319                                DataType::Custom {
32320                                    name: "STRING".to_string(),
32321                                }
32322                            }
32323                            DialectType::Presto
32324                            | DialectType::Trino
32325                            | DialectType::Athena
32326                            | DialectType::Drill => DataType::VarChar {
32327                                length: None,
32328                                parenthesized_length: false,
32329                            },
32330                            _ => DataType::VarChar {
32331                                length: None,
32332                                parenthesized_length: false,
32333                            },
32334                        };
32335                        Ok(Expression::Cast(Box::new(Cast {
32336                            this: arg,
32337                            to: str_type,
32338                            double_colon_syntax: false,
32339                            trailing_comments: Vec::new(),
32340                            format: None,
32341                            default: None,
32342                            inferred_type: None,
32343                        })))
32344                    } else {
32345                        Ok(e)
32346                    }
32347                }
32348
32349                Action::DateToDiConvert => {
32350                    // DATE_TO_DI(x) -> CAST(format_func(x, fmt) AS INT)
32351                    if let Expression::Function(f) = e {
32352                        let arg = f.args.into_iter().next().unwrap();
32353                        let inner = match target {
32354                            DialectType::DuckDB => {
32355                                // STRFTIME(x, '%Y%m%d')
32356                                Expression::Function(Box::new(Function::new(
32357                                    "STRFTIME".to_string(),
32358                                    vec![arg, Expression::string("%Y%m%d")],
32359                                )))
32360                            }
32361                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32362                                // DATE_FORMAT(x, 'yyyyMMdd')
32363                                Expression::Function(Box::new(Function::new(
32364                                    "DATE_FORMAT".to_string(),
32365                                    vec![arg, Expression::string("yyyyMMdd")],
32366                                )))
32367                            }
32368                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32369                                // DATE_FORMAT(x, '%Y%m%d')
32370                                Expression::Function(Box::new(Function::new(
32371                                    "DATE_FORMAT".to_string(),
32372                                    vec![arg, Expression::string("%Y%m%d")],
32373                                )))
32374                            }
32375                            DialectType::Drill => {
32376                                // TO_DATE(x, 'yyyyMMdd')
32377                                Expression::Function(Box::new(Function::new(
32378                                    "TO_DATE".to_string(),
32379                                    vec![arg, Expression::string("yyyyMMdd")],
32380                                )))
32381                            }
32382                            _ => {
32383                                // Default: STRFTIME(x, '%Y%m%d')
32384                                Expression::Function(Box::new(Function::new(
32385                                    "STRFTIME".to_string(),
32386                                    vec![arg, Expression::string("%Y%m%d")],
32387                                )))
32388                            }
32389                        };
32390                        // Use INT (not INTEGER) for Presto/Trino
32391                        let int_type = match target {
32392                            DialectType::Presto
32393                            | DialectType::Trino
32394                            | DialectType::Athena
32395                            | DialectType::TSQL
32396                            | DialectType::Fabric
32397                            | DialectType::SQLite
32398                            | DialectType::Redshift => DataType::Custom {
32399                                name: "INT".to_string(),
32400                            },
32401                            _ => DataType::Int {
32402                                length: None,
32403                                integer_spelling: false,
32404                            },
32405                        };
32406                        Ok(Expression::Cast(Box::new(Cast {
32407                            this: inner,
32408                            to: int_type,
32409                            double_colon_syntax: false,
32410                            trailing_comments: Vec::new(),
32411                            format: None,
32412                            default: None,
32413                            inferred_type: None,
32414                        })))
32415                    } else {
32416                        Ok(e)
32417                    }
32418                }
32419
32420                Action::DiToDateConvert => {
32421                    // DI_TO_DATE(x) -> dialect-specific integer-to-date conversion
32422                    if let Expression::Function(f) = e {
32423                        let arg = f.args.into_iter().next().unwrap();
32424                        match target {
32425                            DialectType::DuckDB => {
32426                                // CAST(STRPTIME(CAST(x AS TEXT), '%Y%m%d') AS DATE)
32427                                let cast_text = Expression::Cast(Box::new(Cast {
32428                                    this: arg,
32429                                    to: DataType::Text,
32430                                    double_colon_syntax: false,
32431                                    trailing_comments: Vec::new(),
32432                                    format: None,
32433                                    default: None,
32434                                    inferred_type: None,
32435                                }));
32436                                let strptime = Expression::Function(Box::new(Function::new(
32437                                    "STRPTIME".to_string(),
32438                                    vec![cast_text, Expression::string("%Y%m%d")],
32439                                )));
32440                                Ok(Expression::Cast(Box::new(Cast {
32441                                    this: strptime,
32442                                    to: DataType::Date,
32443                                    double_colon_syntax: false,
32444                                    trailing_comments: Vec::new(),
32445                                    format: None,
32446                                    default: None,
32447                                    inferred_type: None,
32448                                })))
32449                            }
32450                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32451                                // TO_DATE(CAST(x AS STRING), 'yyyyMMdd')
32452                                let cast_str = Expression::Cast(Box::new(Cast {
32453                                    this: arg,
32454                                    to: DataType::Custom {
32455                                        name: "STRING".to_string(),
32456                                    },
32457                                    double_colon_syntax: false,
32458                                    trailing_comments: Vec::new(),
32459                                    format: None,
32460                                    default: None,
32461                                    inferred_type: None,
32462                                }));
32463                                Ok(Expression::Function(Box::new(Function::new(
32464                                    "TO_DATE".to_string(),
32465                                    vec![cast_str, Expression::string("yyyyMMdd")],
32466                                ))))
32467                            }
32468                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32469                                // CAST(DATE_PARSE(CAST(x AS VARCHAR), '%Y%m%d') AS DATE)
32470                                let cast_varchar = Expression::Cast(Box::new(Cast {
32471                                    this: arg,
32472                                    to: DataType::VarChar {
32473                                        length: None,
32474                                        parenthesized_length: false,
32475                                    },
32476                                    double_colon_syntax: false,
32477                                    trailing_comments: Vec::new(),
32478                                    format: None,
32479                                    default: None,
32480                                    inferred_type: None,
32481                                }));
32482                                let date_parse = Expression::Function(Box::new(Function::new(
32483                                    "DATE_PARSE".to_string(),
32484                                    vec![cast_varchar, Expression::string("%Y%m%d")],
32485                                )));
32486                                Ok(Expression::Cast(Box::new(Cast {
32487                                    this: date_parse,
32488                                    to: DataType::Date,
32489                                    double_colon_syntax: false,
32490                                    trailing_comments: Vec::new(),
32491                                    format: None,
32492                                    default: None,
32493                                    inferred_type: None,
32494                                })))
32495                            }
32496                            DialectType::Drill => {
32497                                // TO_DATE(CAST(x AS VARCHAR), 'yyyyMMdd')
32498                                let cast_varchar = Expression::Cast(Box::new(Cast {
32499                                    this: arg,
32500                                    to: DataType::VarChar {
32501                                        length: None,
32502                                        parenthesized_length: false,
32503                                    },
32504                                    double_colon_syntax: false,
32505                                    trailing_comments: Vec::new(),
32506                                    format: None,
32507                                    default: None,
32508                                    inferred_type: None,
32509                                }));
32510                                Ok(Expression::Function(Box::new(Function::new(
32511                                    "TO_DATE".to_string(),
32512                                    vec![cast_varchar, Expression::string("yyyyMMdd")],
32513                                ))))
32514                            }
32515                            _ => Ok(Expression::Function(Box::new(Function::new(
32516                                "DI_TO_DATE".to_string(),
32517                                vec![arg],
32518                            )))),
32519                        }
32520                    } else {
32521                        Ok(e)
32522                    }
32523                }
32524
32525                Action::TsOrDiToDiConvert => {
32526                    // TS_OR_DI_TO_DI(x) -> CAST(SUBSTR(REPLACE(CAST(x AS type), '-', ''), 1, 8) AS INT)
32527                    if let Expression::Function(f) = e {
32528                        let arg = f.args.into_iter().next().unwrap();
32529                        let str_type = match target {
32530                            DialectType::DuckDB => DataType::Text,
32531                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32532                                DataType::Custom {
32533                                    name: "STRING".to_string(),
32534                                }
32535                            }
32536                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32537                                DataType::VarChar {
32538                                    length: None,
32539                                    parenthesized_length: false,
32540                                }
32541                            }
32542                            _ => DataType::VarChar {
32543                                length: None,
32544                                parenthesized_length: false,
32545                            },
32546                        };
32547                        let cast_str = Expression::Cast(Box::new(Cast {
32548                            this: arg,
32549                            to: str_type,
32550                            double_colon_syntax: false,
32551                            trailing_comments: Vec::new(),
32552                            format: None,
32553                            default: None,
32554                            inferred_type: None,
32555                        }));
32556                        let replace_expr = Expression::Function(Box::new(Function::new(
32557                            "REPLACE".to_string(),
32558                            vec![cast_str, Expression::string("-"), Expression::string("")],
32559                        )));
32560                        let substr_name = match target {
32561                            DialectType::DuckDB
32562                            | DialectType::Hive
32563                            | DialectType::Spark
32564                            | DialectType::Databricks => "SUBSTR",
32565                            _ => "SUBSTR",
32566                        };
32567                        let substr = Expression::Function(Box::new(Function::new(
32568                            substr_name.to_string(),
32569                            vec![replace_expr, Expression::number(1), Expression::number(8)],
32570                        )));
32571                        // Use INT (not INTEGER) for Presto/Trino etc.
32572                        let int_type = match target {
32573                            DialectType::Presto
32574                            | DialectType::Trino
32575                            | DialectType::Athena
32576                            | DialectType::TSQL
32577                            | DialectType::Fabric
32578                            | DialectType::SQLite
32579                            | DialectType::Redshift => DataType::Custom {
32580                                name: "INT".to_string(),
32581                            },
32582                            _ => DataType::Int {
32583                                length: None,
32584                                integer_spelling: false,
32585                            },
32586                        };
32587                        Ok(Expression::Cast(Box::new(Cast {
32588                            this: substr,
32589                            to: int_type,
32590                            double_colon_syntax: false,
32591                            trailing_comments: Vec::new(),
32592                            format: None,
32593                            default: None,
32594                            inferred_type: None,
32595                        })))
32596                    } else {
32597                        Ok(e)
32598                    }
32599                }
32600
32601                Action::UnixToStrConvert => {
32602                    // UNIX_TO_STR(x, fmt) -> convert to Expression::UnixToStr for generator
32603                    if let Expression::Function(f) = e {
32604                        let mut args = f.args;
32605                        let this = args.remove(0);
32606                        let fmt_expr = if !args.is_empty() {
32607                            Some(args.remove(0))
32608                        } else {
32609                            None
32610                        };
32611
32612                        // Check if format is a string literal
32613                        let fmt_str = fmt_expr.as_ref().and_then(|f| {
32614                            if let Expression::Literal(lit) = f {
32615                                if let Literal::String(s) = lit.as_ref() {
32616                                    Some(s.clone())
32617                                } else {
32618                                    None
32619                                }
32620                            } else {
32621                                None
32622                            }
32623                        });
32624
32625                        if let Some(fmt_string) = fmt_str {
32626                            // String literal format -> use UnixToStr expression (generator handles it)
32627                            Ok(Expression::UnixToStr(Box::new(
32628                                crate::expressions::UnixToStr {
32629                                    this: Box::new(this),
32630                                    format: Some(fmt_string),
32631                                },
32632                            )))
32633                        } else if let Some(fmt_e) = fmt_expr {
32634                            // Non-literal format (e.g., identifier `y`) -> build target expression directly
32635                            match target {
32636                                DialectType::DuckDB => {
32637                                    // STRFTIME(TO_TIMESTAMP(x), y)
32638                                    let to_ts = Expression::Function(Box::new(Function::new(
32639                                        "TO_TIMESTAMP".to_string(),
32640                                        vec![this],
32641                                    )));
32642                                    Ok(Expression::Function(Box::new(Function::new(
32643                                        "STRFTIME".to_string(),
32644                                        vec![to_ts, fmt_e],
32645                                    ))))
32646                                }
32647                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32648                                    // DATE_FORMAT(FROM_UNIXTIME(x), y)
32649                                    let from_unix = Expression::Function(Box::new(Function::new(
32650                                        "FROM_UNIXTIME".to_string(),
32651                                        vec![this],
32652                                    )));
32653                                    Ok(Expression::Function(Box::new(Function::new(
32654                                        "DATE_FORMAT".to_string(),
32655                                        vec![from_unix, fmt_e],
32656                                    ))))
32657                                }
32658                                DialectType::Hive
32659                                | DialectType::Spark
32660                                | DialectType::Databricks
32661                                | DialectType::Doris
32662                                | DialectType::StarRocks => {
32663                                    // FROM_UNIXTIME(x, y)
32664                                    Ok(Expression::Function(Box::new(Function::new(
32665                                        "FROM_UNIXTIME".to_string(),
32666                                        vec![this, fmt_e],
32667                                    ))))
32668                                }
32669                                _ => {
32670                                    // Default: keep as UNIX_TO_STR(x, y)
32671                                    Ok(Expression::Function(Box::new(Function::new(
32672                                        "UNIX_TO_STR".to_string(),
32673                                        vec![this, fmt_e],
32674                                    ))))
32675                                }
32676                            }
32677                        } else {
32678                            Ok(Expression::UnixToStr(Box::new(
32679                                crate::expressions::UnixToStr {
32680                                    this: Box::new(this),
32681                                    format: None,
32682                                },
32683                            )))
32684                        }
32685                    } else {
32686                        Ok(e)
32687                    }
32688                }
32689
32690                Action::UnixToTimeConvert => {
32691                    // UNIX_TO_TIME(x) -> convert to Expression::UnixToTime for generator
32692                    if let Expression::Function(f) = e {
32693                        let arg = f.args.into_iter().next().unwrap();
32694                        Ok(Expression::UnixToTime(Box::new(
32695                            crate::expressions::UnixToTime {
32696                                this: Box::new(arg),
32697                                scale: None,
32698                                zone: None,
32699                                hours: None,
32700                                minutes: None,
32701                                format: None,
32702                                target_type: None,
32703                            },
32704                        )))
32705                    } else {
32706                        Ok(e)
32707                    }
32708                }
32709
32710                Action::UnixToTimeStrConvert => {
32711                    // UNIX_TO_TIME_STR(x) -> dialect-specific
32712                    if let Expression::Function(f) = e {
32713                        let arg = f.args.into_iter().next().unwrap();
32714                        match target {
32715                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
32716                                // FROM_UNIXTIME(x)
32717                                Ok(Expression::Function(Box::new(Function::new(
32718                                    "FROM_UNIXTIME".to_string(),
32719                                    vec![arg],
32720                                ))))
32721                            }
32722                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32723                                // CAST(FROM_UNIXTIME(x) AS VARCHAR)
32724                                let from_unix = Expression::Function(Box::new(Function::new(
32725                                    "FROM_UNIXTIME".to_string(),
32726                                    vec![arg],
32727                                )));
32728                                Ok(Expression::Cast(Box::new(Cast {
32729                                    this: from_unix,
32730                                    to: DataType::VarChar {
32731                                        length: None,
32732                                        parenthesized_length: false,
32733                                    },
32734                                    double_colon_syntax: false,
32735                                    trailing_comments: Vec::new(),
32736                                    format: None,
32737                                    default: None,
32738                                    inferred_type: None,
32739                                })))
32740                            }
32741                            DialectType::DuckDB => {
32742                                // CAST(TO_TIMESTAMP(x) AS TEXT)
32743                                let to_ts = Expression::Function(Box::new(Function::new(
32744                                    "TO_TIMESTAMP".to_string(),
32745                                    vec![arg],
32746                                )));
32747                                Ok(Expression::Cast(Box::new(Cast {
32748                                    this: to_ts,
32749                                    to: DataType::Text,
32750                                    double_colon_syntax: false,
32751                                    trailing_comments: Vec::new(),
32752                                    format: None,
32753                                    default: None,
32754                                    inferred_type: None,
32755                                })))
32756                            }
32757                            _ => Ok(Expression::Function(Box::new(Function::new(
32758                                "UNIX_TO_TIME_STR".to_string(),
32759                                vec![arg],
32760                            )))),
32761                        }
32762                    } else {
32763                        Ok(e)
32764                    }
32765                }
32766
32767                Action::TimeToUnixConvert => {
32768                    // TIME_TO_UNIX(x) -> convert to Expression::TimeToUnix for generator
32769                    if let Expression::Function(f) = e {
32770                        let arg = f.args.into_iter().next().unwrap();
32771                        Ok(Expression::TimeToUnix(Box::new(
32772                            crate::expressions::UnaryFunc {
32773                                this: arg,
32774                                original_name: None,
32775                                inferred_type: None,
32776                            },
32777                        )))
32778                    } else {
32779                        Ok(e)
32780                    }
32781                }
32782
32783                Action::TimeToStrConvert => {
32784                    // TIME_TO_STR(x, fmt) -> convert to Expression::TimeToStr for generator
32785                    if let Expression::Function(f) = e {
32786                        let mut args = f.args;
32787                        let this = args.remove(0);
32788                        let fmt = match args.remove(0) {
32789                            Expression::Literal(lit)
32790                                if matches!(lit.as_ref(), Literal::String(_)) =>
32791                            {
32792                                let Literal::String(s) = lit.as_ref() else {
32793                                    unreachable!()
32794                                };
32795                                s.clone()
32796                            }
32797                            other => {
32798                                return Ok(Expression::Function(Box::new(Function::new(
32799                                    "TIME_TO_STR".to_string(),
32800                                    vec![this, other],
32801                                ))));
32802                            }
32803                        };
32804                        Ok(Expression::TimeToStr(Box::new(
32805                            crate::expressions::TimeToStr {
32806                                this: Box::new(this),
32807                                format: fmt,
32808                                culture: None,
32809                                zone: None,
32810                            },
32811                        )))
32812                    } else {
32813                        Ok(e)
32814                    }
32815                }
32816
32817                Action::StrToUnixConvert => {
32818                    // STR_TO_UNIX(x, fmt) -> convert to Expression::StrToUnix for generator
32819                    if let Expression::Function(f) = e {
32820                        let mut args = f.args;
32821                        let this = args.remove(0);
32822                        let fmt = match args.remove(0) {
32823                            Expression::Literal(lit)
32824                                if matches!(lit.as_ref(), Literal::String(_)) =>
32825                            {
32826                                let Literal::String(s) = lit.as_ref() else {
32827                                    unreachable!()
32828                                };
32829                                s.clone()
32830                            }
32831                            other => {
32832                                return Ok(Expression::Function(Box::new(Function::new(
32833                                    "STR_TO_UNIX".to_string(),
32834                                    vec![this, other],
32835                                ))));
32836                            }
32837                        };
32838                        Ok(Expression::StrToUnix(Box::new(
32839                            crate::expressions::StrToUnix {
32840                                this: Some(Box::new(this)),
32841                                format: Some(fmt),
32842                            },
32843                        )))
32844                    } else {
32845                        Ok(e)
32846                    }
32847                }
32848
32849                Action::TimeStrToUnixConvert => {
32850                    // TIME_STR_TO_UNIX(x) -> dialect-specific
32851                    if let Expression::Function(f) = e {
32852                        let arg = f.args.into_iter().next().unwrap();
32853                        match target {
32854                            DialectType::DuckDB => {
32855                                // EPOCH(CAST(x AS TIMESTAMP))
32856                                let cast_ts = Expression::Cast(Box::new(Cast {
32857                                    this: arg,
32858                                    to: DataType::Timestamp {
32859                                        timezone: false,
32860                                        precision: None,
32861                                    },
32862                                    double_colon_syntax: false,
32863                                    trailing_comments: Vec::new(),
32864                                    format: None,
32865                                    default: None,
32866                                    inferred_type: None,
32867                                }));
32868                                Ok(Expression::Function(Box::new(Function::new(
32869                                    "EPOCH".to_string(),
32870                                    vec![cast_ts],
32871                                ))))
32872                            }
32873                            DialectType::Hive
32874                            | DialectType::Doris
32875                            | DialectType::StarRocks
32876                            | DialectType::MySQL => {
32877                                // UNIX_TIMESTAMP(x)
32878                                Ok(Expression::Function(Box::new(Function::new(
32879                                    "UNIX_TIMESTAMP".to_string(),
32880                                    vec![arg],
32881                                ))))
32882                            }
32883                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
32884                                // TO_UNIXTIME(DATE_PARSE(x, '%Y-%m-%d %T'))
32885                                let date_parse = Expression::Function(Box::new(Function::new(
32886                                    "DATE_PARSE".to_string(),
32887                                    vec![arg, Expression::string("%Y-%m-%d %T")],
32888                                )));
32889                                Ok(Expression::Function(Box::new(Function::new(
32890                                    "TO_UNIXTIME".to_string(),
32891                                    vec![date_parse],
32892                                ))))
32893                            }
32894                            _ => Ok(Expression::Function(Box::new(Function::new(
32895                                "TIME_STR_TO_UNIX".to_string(),
32896                                vec![arg],
32897                            )))),
32898                        }
32899                    } else {
32900                        Ok(e)
32901                    }
32902                }
32903
32904                Action::TimeToTimeStrConvert => {
32905                    // TIME_TO_TIME_STR(x) -> CAST(x AS str_type) per dialect
32906                    if let Expression::Function(f) = e {
32907                        let arg = f.args.into_iter().next().unwrap();
32908                        let str_type = match target {
32909                            DialectType::DuckDB => DataType::Text,
32910                            DialectType::Hive
32911                            | DialectType::Spark
32912                            | DialectType::Databricks
32913                            | DialectType::Doris
32914                            | DialectType::StarRocks => DataType::Custom {
32915                                name: "STRING".to_string(),
32916                            },
32917                            DialectType::Redshift => DataType::Custom {
32918                                name: "VARCHAR(MAX)".to_string(),
32919                            },
32920                            _ => DataType::VarChar {
32921                                length: None,
32922                                parenthesized_length: false,
32923                            },
32924                        };
32925                        Ok(Expression::Cast(Box::new(Cast {
32926                            this: arg,
32927                            to: str_type,
32928                            double_colon_syntax: false,
32929                            trailing_comments: Vec::new(),
32930                            format: None,
32931                            default: None,
32932                            inferred_type: None,
32933                        })))
32934                    } else {
32935                        Ok(e)
32936                    }
32937                }
32938
32939                Action::DateTruncSwapArgs => {
32940                    // DATE_TRUNC('unit', x) from Generic -> target-specific
32941                    if let Expression::Function(f) = e {
32942                        if f.args.len() == 2 {
32943                            let unit_arg = f.args[0].clone();
32944                            let expr_arg = f.args[1].clone();
32945                            // Extract unit string from the first arg
32946                            let unit_str = match &unit_arg {
32947                                Expression::Literal(lit)
32948                                    if matches!(lit.as_ref(), Literal::String(_)) =>
32949                                {
32950                                    let Literal::String(s) = lit.as_ref() else {
32951                                        unreachable!()
32952                                    };
32953                                    s.to_ascii_uppercase()
32954                                }
32955                                _ => return Ok(Expression::Function(f)),
32956                            };
32957                            match target {
32958                                DialectType::BigQuery => {
32959                                    // BigQuery: DATE_TRUNC(x, UNIT) - unquoted unit
32960                                    let unit_ident =
32961                                        Expression::Column(Box::new(crate::expressions::Column {
32962                                            name: crate::expressions::Identifier::new(unit_str),
32963                                            table: None,
32964                                            join_mark: false,
32965                                            trailing_comments: Vec::new(),
32966                                            span: None,
32967                                            inferred_type: None,
32968                                        }));
32969                                    Ok(Expression::Function(Box::new(Function::new(
32970                                        "DATE_TRUNC".to_string(),
32971                                        vec![expr_arg, unit_ident],
32972                                    ))))
32973                                }
32974                                DialectType::Doris => {
32975                                    // Doris: DATE_TRUNC(x, 'UNIT')
32976                                    Ok(Expression::Function(Box::new(Function::new(
32977                                        "DATE_TRUNC".to_string(),
32978                                        vec![expr_arg, Expression::string(&unit_str)],
32979                                    ))))
32980                                }
32981                                DialectType::StarRocks => {
32982                                    // StarRocks: DATE_TRUNC('UNIT', x) - keep standard order
32983                                    Ok(Expression::Function(Box::new(Function::new(
32984                                        "DATE_TRUNC".to_string(),
32985                                        vec![Expression::string(&unit_str), expr_arg],
32986                                    ))))
32987                                }
32988                                DialectType::Spark | DialectType::Databricks => {
32989                                    // Spark: TRUNC(x, 'UNIT')
32990                                    Ok(Expression::Function(Box::new(Function::new(
32991                                        "TRUNC".to_string(),
32992                                        vec![expr_arg, Expression::string(&unit_str)],
32993                                    ))))
32994                                }
32995                                DialectType::MySQL => {
32996                                    // MySQL: complex expansion based on unit
32997                                    Self::date_trunc_to_mysql(&unit_str, &expr_arg)
32998                                }
32999                                _ => Ok(Expression::Function(f)),
33000                            }
33001                        } else {
33002                            Ok(Expression::Function(f))
33003                        }
33004                    } else {
33005                        Ok(e)
33006                    }
33007                }
33008
33009                Action::TimestampTruncConvert => {
33010                    // TIMESTAMP_TRUNC(x, UNIT[, tz]) from Generic -> target-specific
33011                    if let Expression::Function(f) = e {
33012                        if f.args.len() >= 2 {
33013                            let expr_arg = f.args[0].clone();
33014                            let unit_arg = f.args[1].clone();
33015                            let tz_arg = if f.args.len() >= 3 {
33016                                Some(f.args[2].clone())
33017                            } else {
33018                                None
33019                            };
33020                            // Extract unit string
33021                            let unit_str = match &unit_arg {
33022                                Expression::Literal(lit)
33023                                    if matches!(lit.as_ref(), Literal::String(_)) =>
33024                                {
33025                                    let Literal::String(s) = lit.as_ref() else {
33026                                        unreachable!()
33027                                    };
33028                                    s.to_ascii_uppercase()
33029                                }
33030                                Expression::Column(c) => c.name.name.to_ascii_uppercase(),
33031                                _ => {
33032                                    return Ok(Expression::Function(f));
33033                                }
33034                            };
33035                            match target {
33036                                DialectType::Spark | DialectType::Databricks => {
33037                                    // Spark: DATE_TRUNC('UNIT', x)
33038                                    Ok(Expression::Function(Box::new(Function::new(
33039                                        "DATE_TRUNC".to_string(),
33040                                        vec![Expression::string(&unit_str), expr_arg],
33041                                    ))))
33042                                }
33043                                DialectType::Doris | DialectType::StarRocks => {
33044                                    // Doris: DATE_TRUNC(x, 'UNIT')
33045                                    Ok(Expression::Function(Box::new(Function::new(
33046                                        "DATE_TRUNC".to_string(),
33047                                        vec![expr_arg, Expression::string(&unit_str)],
33048                                    ))))
33049                                }
33050                                DialectType::BigQuery => {
33051                                    // BigQuery: TIMESTAMP_TRUNC(x, UNIT) - keep but with unquoted unit
33052                                    let unit_ident =
33053                                        Expression::Column(Box::new(crate::expressions::Column {
33054                                            name: crate::expressions::Identifier::new(unit_str),
33055                                            table: None,
33056                                            join_mark: false,
33057                                            trailing_comments: Vec::new(),
33058                                            span: None,
33059                                            inferred_type: None,
33060                                        }));
33061                                    let mut args = vec![expr_arg, unit_ident];
33062                                    if let Some(tz) = tz_arg {
33063                                        args.push(tz);
33064                                    }
33065                                    Ok(Expression::Function(Box::new(Function::new(
33066                                        "TIMESTAMP_TRUNC".to_string(),
33067                                        args,
33068                                    ))))
33069                                }
33070                                DialectType::DuckDB => {
33071                                    // DuckDB with timezone: DATE_TRUNC('UNIT', x AT TIME ZONE 'tz') AT TIME ZONE 'tz'
33072                                    if let Some(tz) = tz_arg {
33073                                        let tz_str = match &tz {
33074                                            Expression::Literal(lit)
33075                                                if matches!(lit.as_ref(), Literal::String(_)) =>
33076                                            {
33077                                                let Literal::String(s) = lit.as_ref() else {
33078                                                    unreachable!()
33079                                                };
33080                                                s.clone()
33081                                            }
33082                                            _ => "UTC".to_string(),
33083                                        };
33084                                        // x AT TIME ZONE 'tz'
33085                                        let at_tz = Expression::AtTimeZone(Box::new(
33086                                            crate::expressions::AtTimeZone {
33087                                                this: expr_arg,
33088                                                zone: Expression::string(&tz_str),
33089                                            },
33090                                        ));
33091                                        // DATE_TRUNC('UNIT', x AT TIME ZONE 'tz')
33092                                        let trunc = Expression::Function(Box::new(Function::new(
33093                                            "DATE_TRUNC".to_string(),
33094                                            vec![Expression::string(&unit_str), at_tz],
33095                                        )));
33096                                        // DATE_TRUNC(...) AT TIME ZONE 'tz'
33097                                        Ok(Expression::AtTimeZone(Box::new(
33098                                            crate::expressions::AtTimeZone {
33099                                                this: trunc,
33100                                                zone: Expression::string(&tz_str),
33101                                            },
33102                                        )))
33103                                    } else {
33104                                        Ok(Expression::Function(Box::new(Function::new(
33105                                            "DATE_TRUNC".to_string(),
33106                                            vec![Expression::string(&unit_str), expr_arg],
33107                                        ))))
33108                                    }
33109                                }
33110                                DialectType::Presto
33111                                | DialectType::Trino
33112                                | DialectType::Athena
33113                                | DialectType::Snowflake => {
33114                                    // Presto/Snowflake: DATE_TRUNC('UNIT', x) - drop timezone
33115                                    Ok(Expression::Function(Box::new(Function::new(
33116                                        "DATE_TRUNC".to_string(),
33117                                        vec![Expression::string(&unit_str), expr_arg],
33118                                    ))))
33119                                }
33120                                _ => {
33121                                    // For most dialects: DATE_TRUNC('UNIT', x) + tz handling
33122                                    let mut args = vec![Expression::string(&unit_str), expr_arg];
33123                                    if let Some(tz) = tz_arg {
33124                                        args.push(tz);
33125                                    }
33126                                    Ok(Expression::Function(Box::new(Function::new(
33127                                        "DATE_TRUNC".to_string(),
33128                                        args,
33129                                    ))))
33130                                }
33131                            }
33132                        } else {
33133                            Ok(Expression::Function(f))
33134                        }
33135                    } else {
33136                        Ok(e)
33137                    }
33138                }
33139
33140                Action::StrToDateConvert => {
33141                    // STR_TO_DATE(x, fmt) from Generic -> dialect-specific date parsing
33142                    if let Expression::Function(f) = e {
33143                        if f.args.len() == 2 {
33144                            let mut args = f.args;
33145                            let this = args.remove(0);
33146                            let fmt_expr = args.remove(0);
33147                            let fmt_str = match &fmt_expr {
33148                                Expression::Literal(lit)
33149                                    if matches!(lit.as_ref(), Literal::String(_)) =>
33150                                {
33151                                    let Literal::String(s) = lit.as_ref() else {
33152                                        unreachable!()
33153                                    };
33154                                    Some(s.clone())
33155                                }
33156                                _ => None,
33157                            };
33158                            let default_date = "%Y-%m-%d";
33159                            let default_time = "%Y-%m-%d %H:%M:%S";
33160                            let is_default = fmt_str
33161                                .as_ref()
33162                                .map_or(false, |f| f == default_date || f == default_time);
33163
33164                            if is_default {
33165                                // Default format: handle per-dialect
33166                                match target {
33167                                    DialectType::MySQL
33168                                    | DialectType::Doris
33169                                    | DialectType::StarRocks => {
33170                                        // Keep STR_TO_DATE(x, fmt) as-is
33171                                        Ok(Expression::Function(Box::new(Function::new(
33172                                            "STR_TO_DATE".to_string(),
33173                                            vec![this, fmt_expr],
33174                                        ))))
33175                                    }
33176                                    DialectType::Hive => {
33177                                        // Hive: CAST(x AS DATE)
33178                                        Ok(Expression::Cast(Box::new(Cast {
33179                                            this,
33180                                            to: DataType::Date,
33181                                            double_colon_syntax: false,
33182                                            trailing_comments: Vec::new(),
33183                                            format: None,
33184                                            default: None,
33185                                            inferred_type: None,
33186                                        })))
33187                                    }
33188                                    DialectType::Presto
33189                                    | DialectType::Trino
33190                                    | DialectType::Athena => {
33191                                        // Presto: CAST(DATE_PARSE(x, '%Y-%m-%d') AS DATE)
33192                                        let date_parse =
33193                                            Expression::Function(Box::new(Function::new(
33194                                                "DATE_PARSE".to_string(),
33195                                                vec![this, fmt_expr],
33196                                            )));
33197                                        Ok(Expression::Cast(Box::new(Cast {
33198                                            this: date_parse,
33199                                            to: DataType::Date,
33200                                            double_colon_syntax: false,
33201                                            trailing_comments: Vec::new(),
33202                                            format: None,
33203                                            default: None,
33204                                            inferred_type: None,
33205                                        })))
33206                                    }
33207                                    _ => {
33208                                        // Others: TsOrDsToDate (delegates to generator)
33209                                        Ok(Expression::TsOrDsToDate(Box::new(
33210                                            crate::expressions::TsOrDsToDate {
33211                                                this: Box::new(this),
33212                                                format: None,
33213                                                safe: None,
33214                                            },
33215                                        )))
33216                                    }
33217                                }
33218                            } else if let Some(fmt) = fmt_str {
33219                                match target {
33220                                    DialectType::Doris
33221                                    | DialectType::StarRocks
33222                                    | DialectType::MySQL => {
33223                                        // Keep STR_TO_DATE but with normalized format (%H:%M:%S -> %T, %-d -> %e)
33224                                        let mut normalized = fmt.clone();
33225                                        normalized = normalized.replace("%-d", "%e");
33226                                        normalized = normalized.replace("%-m", "%c");
33227                                        normalized = normalized.replace("%H:%M:%S", "%T");
33228                                        Ok(Expression::Function(Box::new(Function::new(
33229                                            "STR_TO_DATE".to_string(),
33230                                            vec![this, Expression::string(&normalized)],
33231                                        ))))
33232                                    }
33233                                    DialectType::Hive => {
33234                                        // Hive: CAST(FROM_UNIXTIME(UNIX_TIMESTAMP(x, java_fmt)) AS DATE)
33235                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33236                                        let unix_ts =
33237                                            Expression::Function(Box::new(Function::new(
33238                                                "UNIX_TIMESTAMP".to_string(),
33239                                                vec![this, Expression::string(&java_fmt)],
33240                                            )));
33241                                        let from_unix =
33242                                            Expression::Function(Box::new(Function::new(
33243                                                "FROM_UNIXTIME".to_string(),
33244                                                vec![unix_ts],
33245                                            )));
33246                                        Ok(Expression::Cast(Box::new(Cast {
33247                                            this: from_unix,
33248                                            to: DataType::Date,
33249                                            double_colon_syntax: false,
33250                                            trailing_comments: Vec::new(),
33251                                            format: None,
33252                                            default: None,
33253                                            inferred_type: None,
33254                                        })))
33255                                    }
33256                                    DialectType::Spark | DialectType::Databricks => {
33257                                        // Spark: TO_DATE(x, java_fmt)
33258                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33259                                        Ok(Expression::Function(Box::new(Function::new(
33260                                            "TO_DATE".to_string(),
33261                                            vec![this, Expression::string(&java_fmt)],
33262                                        ))))
33263                                    }
33264                                    DialectType::Drill => {
33265                                        // Drill: TO_DATE(x, java_fmt) with T quoted as 'T' in Java format
33266                                        // The generator's string literal escaping will double the quotes: 'T' -> ''T''
33267                                        let java_fmt = crate::generator::Generator::strftime_to_java_format_static(&fmt);
33268                                        let java_fmt = java_fmt.replace('T', "'T'");
33269                                        Ok(Expression::Function(Box::new(Function::new(
33270                                            "TO_DATE".to_string(),
33271                                            vec![this, Expression::string(&java_fmt)],
33272                                        ))))
33273                                    }
33274                                    _ => {
33275                                        // For other dialects: use TsOrDsToDate which delegates to generator
33276                                        Ok(Expression::TsOrDsToDate(Box::new(
33277                                            crate::expressions::TsOrDsToDate {
33278                                                this: Box::new(this),
33279                                                format: Some(fmt),
33280                                                safe: None,
33281                                            },
33282                                        )))
33283                                    }
33284                                }
33285                            } else {
33286                                // Non-string format - keep as-is
33287                                let mut new_args = Vec::new();
33288                                new_args.push(this);
33289                                new_args.push(fmt_expr);
33290                                Ok(Expression::Function(Box::new(Function::new(
33291                                    "STR_TO_DATE".to_string(),
33292                                    new_args,
33293                                ))))
33294                            }
33295                        } else {
33296                            Ok(Expression::Function(f))
33297                        }
33298                    } else {
33299                        Ok(e)
33300                    }
33301                }
33302
33303                Action::TsOrDsAddConvert => {
33304                    // TS_OR_DS_ADD(x, n, 'UNIT') from Generic -> dialect-specific DATE_ADD
33305                    if let Expression::Function(f) = e {
33306                        if f.args.len() == 3 {
33307                            let mut args = f.args;
33308                            let x = args.remove(0);
33309                            let n = args.remove(0);
33310                            let unit_expr = args.remove(0);
33311                            let unit_str = match &unit_expr {
33312                                Expression::Literal(lit)
33313                                    if matches!(lit.as_ref(), Literal::String(_)) =>
33314                                {
33315                                    let Literal::String(s) = lit.as_ref() else {
33316                                        unreachable!()
33317                                    };
33318                                    s.to_ascii_uppercase()
33319                                }
33320                                _ => "DAY".to_string(),
33321                            };
33322
33323                            match target {
33324                                DialectType::Hive
33325                                | DialectType::Spark
33326                                | DialectType::Databricks => {
33327                                    // DATE_ADD(x, n) - only supports DAY unit
33328                                    Ok(Expression::Function(Box::new(Function::new(
33329                                        "DATE_ADD".to_string(),
33330                                        vec![x, n],
33331                                    ))))
33332                                }
33333                                DialectType::MySQL => {
33334                                    // DATE_ADD(x, INTERVAL n UNIT)
33335                                    let iu = match unit_str.as_str() {
33336                                        "YEAR" => crate::expressions::IntervalUnit::Year,
33337                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33338                                        "MONTH" => crate::expressions::IntervalUnit::Month,
33339                                        "WEEK" => crate::expressions::IntervalUnit::Week,
33340                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
33341                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
33342                                        "SECOND" => crate::expressions::IntervalUnit::Second,
33343                                        _ => crate::expressions::IntervalUnit::Day,
33344                                    };
33345                                    let interval = Expression::Interval(Box::new(
33346                                        crate::expressions::Interval {
33347                                            this: Some(n),
33348                                            unit: Some(
33349                                                crate::expressions::IntervalUnitSpec::Simple {
33350                                                    unit: iu,
33351                                                    use_plural: false,
33352                                                },
33353                                            ),
33354                                        },
33355                                    ));
33356                                    Ok(Expression::Function(Box::new(Function::new(
33357                                        "DATE_ADD".to_string(),
33358                                        vec![x, interval],
33359                                    ))))
33360                                }
33361                                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
33362                                    // DATE_ADD('UNIT', n, CAST(CAST(x AS TIMESTAMP) AS DATE))
33363                                    let cast_ts = Expression::Cast(Box::new(Cast {
33364                                        this: x,
33365                                        to: DataType::Timestamp {
33366                                            precision: None,
33367                                            timezone: false,
33368                                        },
33369                                        double_colon_syntax: false,
33370                                        trailing_comments: Vec::new(),
33371                                        format: None,
33372                                        default: None,
33373                                        inferred_type: None,
33374                                    }));
33375                                    let cast_date = Expression::Cast(Box::new(Cast {
33376                                        this: cast_ts,
33377                                        to: DataType::Date,
33378                                        double_colon_syntax: false,
33379                                        trailing_comments: Vec::new(),
33380                                        format: None,
33381                                        default: None,
33382                                        inferred_type: None,
33383                                    }));
33384                                    Ok(Expression::Function(Box::new(Function::new(
33385                                        "DATE_ADD".to_string(),
33386                                        vec![Expression::string(&unit_str), n, cast_date],
33387                                    ))))
33388                                }
33389                                DialectType::DuckDB => {
33390                                    // CAST(x AS DATE) + INTERVAL n UNIT
33391                                    let cast_date = Expression::Cast(Box::new(Cast {
33392                                        this: x,
33393                                        to: DataType::Date,
33394                                        double_colon_syntax: false,
33395                                        trailing_comments: Vec::new(),
33396                                        format: None,
33397                                        default: None,
33398                                        inferred_type: None,
33399                                    }));
33400                                    let iu = match unit_str.as_str() {
33401                                        "YEAR" => crate::expressions::IntervalUnit::Year,
33402                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33403                                        "MONTH" => crate::expressions::IntervalUnit::Month,
33404                                        "WEEK" => crate::expressions::IntervalUnit::Week,
33405                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
33406                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
33407                                        "SECOND" => crate::expressions::IntervalUnit::Second,
33408                                        _ => crate::expressions::IntervalUnit::Day,
33409                                    };
33410                                    let interval = Expression::Interval(Box::new(
33411                                        crate::expressions::Interval {
33412                                            this: Some(n),
33413                                            unit: Some(
33414                                                crate::expressions::IntervalUnitSpec::Simple {
33415                                                    unit: iu,
33416                                                    use_plural: false,
33417                                                },
33418                                            ),
33419                                        },
33420                                    ));
33421                                    Ok(Expression::Add(Box::new(crate::expressions::BinaryOp {
33422                                        left: cast_date,
33423                                        right: interval,
33424                                        left_comments: Vec::new(),
33425                                        operator_comments: Vec::new(),
33426                                        trailing_comments: Vec::new(),
33427                                        inferred_type: None,
33428                                    })))
33429                                }
33430                                DialectType::Drill => {
33431                                    // DATE_ADD(CAST(x AS DATE), INTERVAL n UNIT)
33432                                    let cast_date = Expression::Cast(Box::new(Cast {
33433                                        this: x,
33434                                        to: DataType::Date,
33435                                        double_colon_syntax: false,
33436                                        trailing_comments: Vec::new(),
33437                                        format: None,
33438                                        default: None,
33439                                        inferred_type: None,
33440                                    }));
33441                                    let iu = match unit_str.as_str() {
33442                                        "YEAR" => crate::expressions::IntervalUnit::Year,
33443                                        "QUARTER" => crate::expressions::IntervalUnit::Quarter,
33444                                        "MONTH" => crate::expressions::IntervalUnit::Month,
33445                                        "WEEK" => crate::expressions::IntervalUnit::Week,
33446                                        "HOUR" => crate::expressions::IntervalUnit::Hour,
33447                                        "MINUTE" => crate::expressions::IntervalUnit::Minute,
33448                                        "SECOND" => crate::expressions::IntervalUnit::Second,
33449                                        _ => crate::expressions::IntervalUnit::Day,
33450                                    };
33451                                    let interval = Expression::Interval(Box::new(
33452                                        crate::expressions::Interval {
33453                                            this: Some(n),
33454                                            unit: Some(
33455                                                crate::expressions::IntervalUnitSpec::Simple {
33456                                                    unit: iu,
33457                                                    use_plural: false,
33458                                                },
33459                                            ),
33460                                        },
33461                                    ));
33462                                    Ok(Expression::Function(Box::new(Function::new(
33463                                        "DATE_ADD".to_string(),
33464                                        vec![cast_date, interval],
33465                                    ))))
33466                                }
33467                                _ => {
33468                                    // Default: keep as TS_OR_DS_ADD
33469                                    Ok(Expression::Function(Box::new(Function::new(
33470                                        "TS_OR_DS_ADD".to_string(),
33471                                        vec![x, n, unit_expr],
33472                                    ))))
33473                                }
33474                            }
33475                        } else {
33476                            Ok(Expression::Function(f))
33477                        }
33478                    } else {
33479                        Ok(e)
33480                    }
33481                }
33482
33483                Action::DateFromUnixDateConvert => {
33484                    // DATE_FROM_UNIX_DATE(n) -> DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
33485                    if let Expression::Function(f) = e {
33486                        // Keep as-is for dialects that support DATE_FROM_UNIX_DATE natively
33487                        if matches!(
33488                            target,
33489                            DialectType::Spark | DialectType::Databricks | DialectType::BigQuery
33490                        ) {
33491                            return Ok(Expression::Function(Box::new(Function::new(
33492                                "DATE_FROM_UNIX_DATE".to_string(),
33493                                f.args,
33494                            ))));
33495                        }
33496                        let n = f.args.into_iter().next().unwrap();
33497                        let epoch_date = Expression::Cast(Box::new(Cast {
33498                            this: Expression::string("1970-01-01"),
33499                            to: DataType::Date,
33500                            double_colon_syntax: false,
33501                            trailing_comments: Vec::new(),
33502                            format: None,
33503                            default: None,
33504                            inferred_type: None,
33505                        }));
33506                        match target {
33507                            DialectType::DuckDB => {
33508                                // CAST('1970-01-01' AS DATE) + INTERVAL n DAY
33509                                let interval =
33510                                    Expression::Interval(Box::new(crate::expressions::Interval {
33511                                        this: Some(n),
33512                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33513                                            unit: crate::expressions::IntervalUnit::Day,
33514                                            use_plural: false,
33515                                        }),
33516                                    }));
33517                                Ok(Expression::Add(Box::new(
33518                                    crate::expressions::BinaryOp::new(epoch_date, interval),
33519                                )))
33520                            }
33521                            DialectType::Presto | DialectType::Trino | DialectType::Athena => {
33522                                // DATE_ADD('DAY', n, CAST('1970-01-01' AS DATE))
33523                                Ok(Expression::Function(Box::new(Function::new(
33524                                    "DATE_ADD".to_string(),
33525                                    vec![Expression::string("DAY"), n, epoch_date],
33526                                ))))
33527                            }
33528                            DialectType::Snowflake | DialectType::Redshift | DialectType::TSQL => {
33529                                // DATEADD(DAY, n, CAST('1970-01-01' AS DATE))
33530                                Ok(Expression::Function(Box::new(Function::new(
33531                                    "DATEADD".to_string(),
33532                                    vec![
33533                                        Expression::Identifier(Identifier::new("DAY")),
33534                                        n,
33535                                        epoch_date,
33536                                    ],
33537                                ))))
33538                            }
33539                            DialectType::BigQuery => {
33540                                // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
33541                                let interval =
33542                                    Expression::Interval(Box::new(crate::expressions::Interval {
33543                                        this: Some(n),
33544                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33545                                            unit: crate::expressions::IntervalUnit::Day,
33546                                            use_plural: false,
33547                                        }),
33548                                    }));
33549                                Ok(Expression::Function(Box::new(Function::new(
33550                                    "DATE_ADD".to_string(),
33551                                    vec![epoch_date, interval],
33552                                ))))
33553                            }
33554                            DialectType::MySQL
33555                            | DialectType::Doris
33556                            | DialectType::StarRocks
33557                            | DialectType::Drill => {
33558                                // DATE_ADD(CAST('1970-01-01' AS DATE), INTERVAL n DAY)
33559                                let interval =
33560                                    Expression::Interval(Box::new(crate::expressions::Interval {
33561                                        this: Some(n),
33562                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
33563                                            unit: crate::expressions::IntervalUnit::Day,
33564                                            use_plural: false,
33565                                        }),
33566                                    }));
33567                                Ok(Expression::Function(Box::new(Function::new(
33568                                    "DATE_ADD".to_string(),
33569                                    vec![epoch_date, interval],
33570                                ))))
33571                            }
33572                            DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
33573                                // DATE_ADD(CAST('1970-01-01' AS DATE), n)
33574                                Ok(Expression::Function(Box::new(Function::new(
33575                                    "DATE_ADD".to_string(),
33576                                    vec![epoch_date, n],
33577                                ))))
33578                            }
33579                            DialectType::PostgreSQL
33580                            | DialectType::Materialize
33581                            | DialectType::RisingWave => {
33582                                // CAST('1970-01-01' AS DATE) + INTERVAL 'n DAY'
33583                                let n_str = match &n {
33584                                    Expression::Literal(lit)
33585                                        if matches!(lit.as_ref(), Literal::Number(_)) =>
33586                                    {
33587                                        let Literal::Number(s) = lit.as_ref() else {
33588                                            unreachable!()
33589                                        };
33590                                        s.clone()
33591                                    }
33592                                    _ => Self::expr_to_string_static(&n),
33593                                };
33594                                let interval =
33595                                    Expression::Interval(Box::new(crate::expressions::Interval {
33596                                        this: Some(Expression::string(&format!("{} DAY", n_str))),
33597                                        unit: None,
33598                                    }));
33599                                Ok(Expression::Add(Box::new(
33600                                    crate::expressions::BinaryOp::new(epoch_date, interval),
33601                                )))
33602                            }
33603                            _ => {
33604                                // Default: keep as-is
33605                                Ok(Expression::Function(Box::new(Function::new(
33606                                    "DATE_FROM_UNIX_DATE".to_string(),
33607                                    vec![n],
33608                                ))))
33609                            }
33610                        }
33611                    } else {
33612                        Ok(e)
33613                    }
33614                }
33615
33616                Action::ArrayRemoveConvert => {
33617                    // ARRAY_REMOVE(arr, target) -> LIST_FILTER/arrayFilter
33618                    if let Expression::ArrayRemove(bf) = e {
33619                        let arr = bf.this;
33620                        let target_val = bf.expression;
33621                        match target {
33622                            DialectType::DuckDB => {
33623                                let u_id = crate::expressions::Identifier::new("_u");
33624                                let lambda =
33625                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
33626                                        parameters: vec![u_id.clone()],
33627                                        body: Expression::Neq(Box::new(BinaryOp {
33628                                            left: Expression::Identifier(u_id),
33629                                            right: target_val,
33630                                            left_comments: Vec::new(),
33631                                            operator_comments: Vec::new(),
33632                                            trailing_comments: Vec::new(),
33633                                            inferred_type: None,
33634                                        })),
33635                                        colon: false,
33636                                        parameter_types: Vec::new(),
33637                                    }));
33638                                Ok(Expression::Function(Box::new(Function::new(
33639                                    "LIST_FILTER".to_string(),
33640                                    vec![arr, lambda],
33641                                ))))
33642                            }
33643                            DialectType::ClickHouse => {
33644                                let u_id = crate::expressions::Identifier::new("_u");
33645                                let lambda =
33646                                    Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
33647                                        parameters: vec![u_id.clone()],
33648                                        body: Expression::Neq(Box::new(BinaryOp {
33649                                            left: Expression::Identifier(u_id),
33650                                            right: target_val,
33651                                            left_comments: Vec::new(),
33652                                            operator_comments: Vec::new(),
33653                                            trailing_comments: Vec::new(),
33654                                            inferred_type: None,
33655                                        })),
33656                                        colon: false,
33657                                        parameter_types: Vec::new(),
33658                                    }));
33659                                Ok(Expression::Function(Box::new(Function::new(
33660                                    "arrayFilter".to_string(),
33661                                    vec![lambda, arr],
33662                                ))))
33663                            }
33664                            DialectType::BigQuery => {
33665                                // ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)
33666                                let u_id = crate::expressions::Identifier::new("_u");
33667                                let u_col =
33668                                    Expression::Column(Box::new(crate::expressions::Column {
33669                                        name: u_id.clone(),
33670                                        table: None,
33671                                        join_mark: false,
33672                                        trailing_comments: Vec::new(),
33673                                        span: None,
33674                                        inferred_type: None,
33675                                    }));
33676                                let unnest_expr =
33677                                    Expression::Unnest(Box::new(crate::expressions::UnnestFunc {
33678                                        this: arr,
33679                                        expressions: Vec::new(),
33680                                        with_ordinality: false,
33681                                        alias: None,
33682                                        offset_alias: None,
33683                                    }));
33684                                let aliased_unnest =
33685                                    Expression::Alias(Box::new(crate::expressions::Alias {
33686                                        this: unnest_expr,
33687                                        alias: u_id.clone(),
33688                                        column_aliases: Vec::new(),
33689                                        alias_explicit_as: false,
33690                                        alias_keyword: None,
33691                                        pre_alias_comments: Vec::new(),
33692                                        trailing_comments: Vec::new(),
33693                                        inferred_type: None,
33694                                    }));
33695                                let where_cond = Expression::Neq(Box::new(BinaryOp {
33696                                    left: u_col.clone(),
33697                                    right: target_val,
33698                                    left_comments: Vec::new(),
33699                                    operator_comments: Vec::new(),
33700                                    trailing_comments: Vec::new(),
33701                                    inferred_type: None,
33702                                }));
33703                                let subquery = Expression::Select(Box::new(
33704                                    crate::expressions::Select::new()
33705                                        .column(u_col)
33706                                        .from(aliased_unnest)
33707                                        .where_(where_cond),
33708                                ));
33709                                Ok(Expression::ArrayFunc(Box::new(
33710                                    crate::expressions::ArrayConstructor {
33711                                        expressions: vec![subquery],
33712                                        bracket_notation: false,
33713                                        use_list_keyword: false,
33714                                    },
33715                                )))
33716                            }
33717                            _ => Ok(Expression::ArrayRemove(Box::new(
33718                                crate::expressions::BinaryFunc {
33719                                    original_name: None,
33720                                    this: arr,
33721                                    expression: target_val,
33722                                    inferred_type: None,
33723                                },
33724                            ))),
33725                        }
33726                    } else {
33727                        Ok(e)
33728                    }
33729                }
33730
33731                Action::ArrayReverseConvert => {
33732                    // ARRAY_REVERSE(x) -> arrayReverse(x) for ClickHouse
33733                    if let Expression::ArrayReverse(af) = e {
33734                        Ok(Expression::Function(Box::new(Function::new(
33735                            "arrayReverse".to_string(),
33736                            vec![af.this],
33737                        ))))
33738                    } else {
33739                        Ok(e)
33740                    }
33741                }
33742
33743                Action::JsonKeysConvert => {
33744                    // JSON_KEYS(x) -> JSON_OBJECT_KEYS/OBJECT_KEYS
33745                    if let Expression::JsonKeys(uf) = e {
33746                        match target {
33747                            DialectType::Spark | DialectType::Databricks => {
33748                                Ok(Expression::Function(Box::new(Function::new(
33749                                    "JSON_OBJECT_KEYS".to_string(),
33750                                    vec![uf.this],
33751                                ))))
33752                            }
33753                            DialectType::Snowflake => Ok(Expression::Function(Box::new(
33754                                Function::new("OBJECT_KEYS".to_string(), vec![uf.this]),
33755                            ))),
33756                            _ => Ok(Expression::JsonKeys(uf)),
33757                        }
33758                    } else {
33759                        Ok(e)
33760                    }
33761                }
33762
33763                Action::ParseJsonStrip => {
33764                    // PARSE_JSON(x) -> x (strip wrapper for SQLite/Doris)
33765                    if let Expression::ParseJson(uf) = e {
33766                        Ok(uf.this)
33767                    } else {
33768                        Ok(e)
33769                    }
33770                }
33771
33772                Action::ArraySizeDrill => {
33773                    // ARRAY_SIZE(x) -> REPEATED_COUNT(x) for Drill
33774                    if let Expression::ArraySize(uf) = e {
33775                        Ok(Expression::Function(Box::new(Function::new(
33776                            "REPEATED_COUNT".to_string(),
33777                            vec![uf.this],
33778                        ))))
33779                    } else {
33780                        Ok(e)
33781                    }
33782                }
33783
33784                Action::WeekOfYearToWeekIso => {
33785                    // WEEKOFYEAR(x) -> WEEKISO(x) for Snowflake (cross-dialect normalization)
33786                    if let Expression::WeekOfYear(uf) = e {
33787                        Ok(Expression::Function(Box::new(Function::new(
33788                            "WEEKISO".to_string(),
33789                            vec![uf.this],
33790                        ))))
33791                    } else {
33792                        Ok(e)
33793                    }
33794                }
33795            }
33796        })
33797    }
33798
33799    /// Convert DATE_TRUNC('unit', x) to MySQL-specific expansion
33800    fn date_trunc_to_mysql(unit: &str, expr: &Expression) -> Result<Expression> {
33801        use crate::expressions::Function;
33802        match unit {
33803            "DAY" => {
33804                // DATE(x)
33805                Ok(Expression::Function(Box::new(Function::new(
33806                    "DATE".to_string(),
33807                    vec![expr.clone()],
33808                ))))
33809            }
33810            "WEEK" => {
33811                // STR_TO_DATE(CONCAT(YEAR(x), ' ', WEEK(x, 1), ' 1'), '%Y %u %w')
33812                let year_x = Expression::Function(Box::new(Function::new(
33813                    "YEAR".to_string(),
33814                    vec![expr.clone()],
33815                )));
33816                let week_x = Expression::Function(Box::new(Function::new(
33817                    "WEEK".to_string(),
33818                    vec![expr.clone(), Expression::number(1)],
33819                )));
33820                let concat_args = vec![
33821                    year_x,
33822                    Expression::string(" "),
33823                    week_x,
33824                    Expression::string(" 1"),
33825                ];
33826                let concat = Expression::Function(Box::new(Function::new(
33827                    "CONCAT".to_string(),
33828                    concat_args,
33829                )));
33830                Ok(Expression::Function(Box::new(Function::new(
33831                    "STR_TO_DATE".to_string(),
33832                    vec![concat, Expression::string("%Y %u %w")],
33833                ))))
33834            }
33835            "MONTH" => {
33836                // STR_TO_DATE(CONCAT(YEAR(x), ' ', MONTH(x), ' 1'), '%Y %c %e')
33837                let year_x = Expression::Function(Box::new(Function::new(
33838                    "YEAR".to_string(),
33839                    vec![expr.clone()],
33840                )));
33841                let month_x = Expression::Function(Box::new(Function::new(
33842                    "MONTH".to_string(),
33843                    vec![expr.clone()],
33844                )));
33845                let concat_args = vec![
33846                    year_x,
33847                    Expression::string(" "),
33848                    month_x,
33849                    Expression::string(" 1"),
33850                ];
33851                let concat = Expression::Function(Box::new(Function::new(
33852                    "CONCAT".to_string(),
33853                    concat_args,
33854                )));
33855                Ok(Expression::Function(Box::new(Function::new(
33856                    "STR_TO_DATE".to_string(),
33857                    vec![concat, Expression::string("%Y %c %e")],
33858                ))))
33859            }
33860            "QUARTER" => {
33861                // STR_TO_DATE(CONCAT(YEAR(x), ' ', QUARTER(x) * 3 - 2, ' 1'), '%Y %c %e')
33862                let year_x = Expression::Function(Box::new(Function::new(
33863                    "YEAR".to_string(),
33864                    vec![expr.clone()],
33865                )));
33866                let quarter_x = Expression::Function(Box::new(Function::new(
33867                    "QUARTER".to_string(),
33868                    vec![expr.clone()],
33869                )));
33870                // QUARTER(x) * 3 - 2
33871                let mul = Expression::Mul(Box::new(crate::expressions::BinaryOp {
33872                    left: quarter_x,
33873                    right: Expression::number(3),
33874                    left_comments: Vec::new(),
33875                    operator_comments: Vec::new(),
33876                    trailing_comments: Vec::new(),
33877                    inferred_type: None,
33878                }));
33879                let sub = Expression::Sub(Box::new(crate::expressions::BinaryOp {
33880                    left: mul,
33881                    right: Expression::number(2),
33882                    left_comments: Vec::new(),
33883                    operator_comments: Vec::new(),
33884                    trailing_comments: Vec::new(),
33885                    inferred_type: None,
33886                }));
33887                let concat_args = vec![
33888                    year_x,
33889                    Expression::string(" "),
33890                    sub,
33891                    Expression::string(" 1"),
33892                ];
33893                let concat = Expression::Function(Box::new(Function::new(
33894                    "CONCAT".to_string(),
33895                    concat_args,
33896                )));
33897                Ok(Expression::Function(Box::new(Function::new(
33898                    "STR_TO_DATE".to_string(),
33899                    vec![concat, Expression::string("%Y %c %e")],
33900                ))))
33901            }
33902            "YEAR" => {
33903                // STR_TO_DATE(CONCAT(YEAR(x), ' 1 1'), '%Y %c %e')
33904                let year_x = Expression::Function(Box::new(Function::new(
33905                    "YEAR".to_string(),
33906                    vec![expr.clone()],
33907                )));
33908                let concat_args = vec![year_x, Expression::string(" 1 1")];
33909                let concat = Expression::Function(Box::new(Function::new(
33910                    "CONCAT".to_string(),
33911                    concat_args,
33912                )));
33913                Ok(Expression::Function(Box::new(Function::new(
33914                    "STR_TO_DATE".to_string(),
33915                    vec![concat, Expression::string("%Y %c %e")],
33916                ))))
33917            }
33918            _ => {
33919                // Unsupported unit -> keep as DATE_TRUNC
33920                Ok(Expression::Function(Box::new(Function::new(
33921                    "DATE_TRUNC".to_string(),
33922                    vec![Expression::string(unit), expr.clone()],
33923                ))))
33924            }
33925        }
33926    }
33927
33928    /// Check if a DataType is or contains VARCHAR/CHAR (for Spark VARCHAR->STRING normalization)
33929    fn has_varchar_char_type(dt: &crate::expressions::DataType) -> bool {
33930        use crate::expressions::DataType;
33931        match dt {
33932            DataType::VarChar { .. } | DataType::Char { .. } => true,
33933            DataType::Struct { fields, .. } => fields
33934                .iter()
33935                .any(|f| Self::has_varchar_char_type(&f.data_type)),
33936            _ => false,
33937        }
33938    }
33939
33940    /// Recursively normalize VARCHAR/CHAR to STRING in a DataType (for Spark)
33941    fn normalize_varchar_to_string(
33942        dt: crate::expressions::DataType,
33943    ) -> crate::expressions::DataType {
33944        use crate::expressions::DataType;
33945        match dt {
33946            DataType::VarChar { .. } | DataType::Char { .. } => DataType::Custom {
33947                name: "STRING".to_string(),
33948            },
33949            DataType::Struct { fields, nested } => {
33950                let fields = fields
33951                    .into_iter()
33952                    .map(|mut f| {
33953                        f.data_type = Self::normalize_varchar_to_string(f.data_type);
33954                        f
33955                    })
33956                    .collect();
33957                DataType::Struct { fields, nested }
33958            }
33959            other => other,
33960        }
33961    }
33962
33963    /// Normalize an interval string like '1day' or '  2   days  ' to proper INTERVAL expression
33964    fn normalize_interval_string(expr: Expression, target: DialectType) -> Expression {
33965        if let Expression::Literal(ref lit) = expr {
33966            if let crate::expressions::Literal::String(ref s) = lit.as_ref() {
33967                // Try to parse patterns like '1day', '1 day', '2 days', '  2   days  '
33968                let trimmed = s.trim();
33969
33970                // Find where digits end and unit text begins
33971                let digit_end = trimmed
33972                    .find(|c: char| !c.is_ascii_digit())
33973                    .unwrap_or(trimmed.len());
33974                if digit_end == 0 || digit_end == trimmed.len() {
33975                    return expr;
33976                }
33977                let num = &trimmed[..digit_end];
33978                let unit_text = trimmed[digit_end..].trim().to_ascii_uppercase();
33979                if unit_text.is_empty() {
33980                    return expr;
33981                }
33982
33983                let known_units = [
33984                    "DAY", "DAYS", "HOUR", "HOURS", "MINUTE", "MINUTES", "SECOND", "SECONDS",
33985                    "WEEK", "WEEKS", "MONTH", "MONTHS", "YEAR", "YEARS",
33986                ];
33987                if !known_units.contains(&unit_text.as_str()) {
33988                    return expr;
33989                }
33990
33991                let unit_str = unit_text.clone();
33992                // Singularize
33993                let unit_singular = if unit_str.ends_with('S') && unit_str.len() > 3 {
33994                    &unit_str[..unit_str.len() - 1]
33995                } else {
33996                    &unit_str
33997                };
33998                let unit = unit_singular;
33999
34000                match target {
34001                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
34002                        // INTERVAL '2' DAY
34003                        let iu = match unit {
34004                            "DAY" => crate::expressions::IntervalUnit::Day,
34005                            "HOUR" => crate::expressions::IntervalUnit::Hour,
34006                            "MINUTE" => crate::expressions::IntervalUnit::Minute,
34007                            "SECOND" => crate::expressions::IntervalUnit::Second,
34008                            "WEEK" => crate::expressions::IntervalUnit::Week,
34009                            "MONTH" => crate::expressions::IntervalUnit::Month,
34010                            "YEAR" => crate::expressions::IntervalUnit::Year,
34011                            _ => return expr,
34012                        };
34013                        return Expression::Interval(Box::new(crate::expressions::Interval {
34014                            this: Some(Expression::string(num)),
34015                            unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34016                                unit: iu,
34017                                use_plural: false,
34018                            }),
34019                        }));
34020                    }
34021                    DialectType::PostgreSQL | DialectType::Redshift | DialectType::DuckDB => {
34022                        // INTERVAL '2 DAYS'
34023                        let plural = if num != "1" && !unit_str.ends_with('S') {
34024                            format!("{} {}S", num, unit)
34025                        } else if unit_str.ends_with('S') {
34026                            format!("{} {}", num, unit_str)
34027                        } else {
34028                            format!("{} {}", num, unit)
34029                        };
34030                        return Expression::Interval(Box::new(crate::expressions::Interval {
34031                            this: Some(Expression::string(&plural)),
34032                            unit: None,
34033                        }));
34034                    }
34035                    _ => {
34036                        // Spark/Databricks/Hive: INTERVAL '1' DAY
34037                        let iu = match unit {
34038                            "DAY" => crate::expressions::IntervalUnit::Day,
34039                            "HOUR" => crate::expressions::IntervalUnit::Hour,
34040                            "MINUTE" => crate::expressions::IntervalUnit::Minute,
34041                            "SECOND" => crate::expressions::IntervalUnit::Second,
34042                            "WEEK" => crate::expressions::IntervalUnit::Week,
34043                            "MONTH" => crate::expressions::IntervalUnit::Month,
34044                            "YEAR" => crate::expressions::IntervalUnit::Year,
34045                            _ => return expr,
34046                        };
34047                        return Expression::Interval(Box::new(crate::expressions::Interval {
34048                            this: Some(Expression::string(num)),
34049                            unit: Some(crate::expressions::IntervalUnitSpec::Simple {
34050                                unit: iu,
34051                                use_plural: false,
34052                            }),
34053                        }));
34054                    }
34055                }
34056            }
34057        }
34058        // If it's already an INTERVAL expression, pass through
34059        expr
34060    }
34061
34062    /// Rewrite SELECT expressions containing UNNEST into expanded form with CROSS JOINs.
34063    /// DuckDB: SELECT UNNEST(arr1), UNNEST(arr2) ->
34064    /// BigQuery: SELECT IF(pos = pos_2, col, NULL) AS col, ... FROM UNNEST(GENERATE_ARRAY(0, ...)) AS pos CROSS JOIN ...
34065    /// Presto:  SELECT IF(_u.pos = _u_2.pos_2, _u_2.col) AS col, ... FROM UNNEST(SEQUENCE(1, ...)) AS _u(pos) CROSS JOIN ...
34066    fn rewrite_unnest_expansion(
34067        select: &crate::expressions::Select,
34068        target: DialectType,
34069    ) -> Option<crate::expressions::Select> {
34070        use crate::expressions::{
34071            Alias, BinaryOp, Column, From, Function, Identifier, Join, JoinKind, Literal,
34072            UnnestFunc,
34073        };
34074
34075        let index_offset: i64 = match target {
34076            DialectType::Presto | DialectType::Trino => 1,
34077            _ => 0, // BigQuery, Snowflake
34078        };
34079
34080        let if_func_name = match target {
34081            DialectType::Snowflake => "IFF",
34082            _ => "IF",
34083        };
34084
34085        let array_length_func = match target {
34086            DialectType::BigQuery => "ARRAY_LENGTH",
34087            DialectType::Presto | DialectType::Trino => "CARDINALITY",
34088            DialectType::Snowflake => "ARRAY_SIZE",
34089            _ => "ARRAY_LENGTH",
34090        };
34091
34092        let use_table_aliases = matches!(
34093            target,
34094            DialectType::Presto | DialectType::Trino | DialectType::Snowflake
34095        );
34096        let null_third_arg = matches!(target, DialectType::BigQuery | DialectType::Snowflake);
34097
34098        fn make_col(name: &str, table: Option<&str>) -> Expression {
34099            if let Some(tbl) = table {
34100                Expression::boxed_column(Column {
34101                    name: Identifier::new(name.to_string()),
34102                    table: Some(Identifier::new(tbl.to_string())),
34103                    join_mark: false,
34104                    trailing_comments: Vec::new(),
34105                    span: None,
34106                    inferred_type: None,
34107                })
34108            } else {
34109                Expression::Identifier(Identifier::new(name.to_string()))
34110            }
34111        }
34112
34113        fn make_join(this: Expression) -> Join {
34114            Join {
34115                this,
34116                on: None,
34117                using: Vec::new(),
34118                kind: JoinKind::Cross,
34119                use_inner_keyword: false,
34120                use_outer_keyword: false,
34121                deferred_condition: false,
34122                join_hint: None,
34123                match_condition: None,
34124                pivots: Vec::new(),
34125                comments: Vec::new(),
34126                nesting_group: 0,
34127                directed: false,
34128            }
34129        }
34130
34131        // Collect UNNEST info from SELECT expressions
34132        struct UnnestInfo {
34133            arr_expr: Expression,
34134            col_alias: String,
34135            pos_alias: String,
34136            source_alias: String,
34137            original_expr: Expression,
34138            has_outer_alias: Option<String>,
34139        }
34140
34141        let mut unnest_infos: Vec<UnnestInfo> = Vec::new();
34142        let mut col_counter = 0usize;
34143        let mut pos_counter = 1usize;
34144        let mut source_counter = 1usize;
34145
34146        fn extract_unnest_arg(expr: &Expression) -> Option<Expression> {
34147            match expr {
34148                Expression::Unnest(u) => Some(u.this.clone()),
34149                Expression::Function(f)
34150                    if f.name.eq_ignore_ascii_case("UNNEST") && !f.args.is_empty() =>
34151                {
34152                    Some(f.args[0].clone())
34153                }
34154                Expression::Alias(a) => extract_unnest_arg(&a.this),
34155                Expression::Add(op)
34156                | Expression::Sub(op)
34157                | Expression::Mul(op)
34158                | Expression::Div(op) => {
34159                    extract_unnest_arg(&op.left).or_else(|| extract_unnest_arg(&op.right))
34160                }
34161                _ => None,
34162            }
34163        }
34164
34165        fn get_alias_name(expr: &Expression) -> Option<String> {
34166            if let Expression::Alias(a) = expr {
34167                Some(a.alias.name.clone())
34168            } else {
34169                None
34170            }
34171        }
34172
34173        for sel_expr in &select.expressions {
34174            if let Some(arr) = extract_unnest_arg(sel_expr) {
34175                col_counter += 1;
34176                pos_counter += 1;
34177                source_counter += 1;
34178
34179                let col_alias = if col_counter == 1 {
34180                    "col".to_string()
34181                } else {
34182                    format!("col_{}", col_counter)
34183                };
34184                let pos_alias = format!("pos_{}", pos_counter);
34185                let source_alias = format!("_u_{}", source_counter);
34186                let has_outer_alias = get_alias_name(sel_expr);
34187
34188                unnest_infos.push(UnnestInfo {
34189                    arr_expr: arr,
34190                    col_alias,
34191                    pos_alias,
34192                    source_alias,
34193                    original_expr: sel_expr.clone(),
34194                    has_outer_alias,
34195                });
34196            }
34197        }
34198
34199        if unnest_infos.is_empty() {
34200            return None;
34201        }
34202
34203        let series_alias = "pos".to_string();
34204        let series_source_alias = "_u".to_string();
34205        let tbl_ref = if use_table_aliases {
34206            Some(series_source_alias.as_str())
34207        } else {
34208            None
34209        };
34210
34211        // Build new SELECT expressions
34212        let mut new_select_exprs = Vec::new();
34213        for info in &unnest_infos {
34214            let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
34215            let src_ref = if use_table_aliases {
34216                Some(info.source_alias.as_str())
34217            } else {
34218                None
34219            };
34220
34221            let pos_col = make_col(&series_alias, tbl_ref);
34222            let unnest_pos_col = make_col(&info.pos_alias, src_ref);
34223            let col_ref = make_col(actual_col_name, src_ref);
34224
34225            let eq_cond = Expression::Eq(Box::new(BinaryOp::new(
34226                pos_col.clone(),
34227                unnest_pos_col.clone(),
34228            )));
34229            let mut if_args = vec![eq_cond, col_ref];
34230            if null_third_arg {
34231                if_args.push(Expression::Null(crate::expressions::Null));
34232            }
34233
34234            let if_expr =
34235                Expression::Function(Box::new(Function::new(if_func_name.to_string(), if_args)));
34236            let final_expr = Self::replace_unnest_with_if(&info.original_expr, &if_expr);
34237
34238            new_select_exprs.push(Expression::Alias(Box::new(Alias::new(
34239                final_expr,
34240                Identifier::new(actual_col_name.clone()),
34241            ))));
34242        }
34243
34244        // Build array size expressions for GREATEST
34245        let size_exprs: Vec<Expression> = unnest_infos
34246            .iter()
34247            .map(|info| {
34248                Expression::Function(Box::new(Function::new(
34249                    array_length_func.to_string(),
34250                    vec![info.arr_expr.clone()],
34251                )))
34252            })
34253            .collect();
34254
34255        let greatest =
34256            Expression::Function(Box::new(Function::new("GREATEST".to_string(), size_exprs)));
34257
34258        let series_end = if index_offset == 0 {
34259            Expression::Sub(Box::new(BinaryOp::new(
34260                greatest,
34261                Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34262            )))
34263        } else {
34264            greatest
34265        };
34266
34267        // Build the position array source
34268        let series_unnest_expr = match target {
34269            DialectType::BigQuery => {
34270                let gen_array = Expression::Function(Box::new(Function::new(
34271                    "GENERATE_ARRAY".to_string(),
34272                    vec![
34273                        Expression::Literal(Box::new(Literal::Number("0".to_string()))),
34274                        series_end,
34275                    ],
34276                )));
34277                Expression::Unnest(Box::new(UnnestFunc {
34278                    this: gen_array,
34279                    expressions: Vec::new(),
34280                    with_ordinality: false,
34281                    alias: None,
34282                    offset_alias: None,
34283                }))
34284            }
34285            DialectType::Presto | DialectType::Trino => {
34286                let sequence = Expression::Function(Box::new(Function::new(
34287                    "SEQUENCE".to_string(),
34288                    vec![
34289                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34290                        series_end,
34291                    ],
34292                )));
34293                Expression::Unnest(Box::new(UnnestFunc {
34294                    this: sequence,
34295                    expressions: Vec::new(),
34296                    with_ordinality: false,
34297                    alias: None,
34298                    offset_alias: None,
34299                }))
34300            }
34301            DialectType::Snowflake => {
34302                let range_end = Expression::Add(Box::new(BinaryOp::new(
34303                    Expression::Paren(Box::new(crate::expressions::Paren {
34304                        this: series_end,
34305                        trailing_comments: Vec::new(),
34306                    })),
34307                    Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34308                )));
34309                let gen_range = Expression::Function(Box::new(Function::new(
34310                    "ARRAY_GENERATE_RANGE".to_string(),
34311                    vec![
34312                        Expression::Literal(Box::new(Literal::Number("0".to_string()))),
34313                        range_end,
34314                    ],
34315                )));
34316                let flatten_arg =
34317                    Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
34318                        name: Identifier::new("INPUT".to_string()),
34319                        value: gen_range,
34320                        separator: crate::expressions::NamedArgSeparator::DArrow,
34321                    }));
34322                let flatten = Expression::Function(Box::new(Function::new(
34323                    "FLATTEN".to_string(),
34324                    vec![flatten_arg],
34325                )));
34326                Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])))
34327            }
34328            _ => return None,
34329        };
34330
34331        // Build series alias expression
34332        let series_alias_expr = if use_table_aliases {
34333            let col_aliases = if matches!(target, DialectType::Snowflake) {
34334                vec![
34335                    Identifier::new("seq".to_string()),
34336                    Identifier::new("key".to_string()),
34337                    Identifier::new("path".to_string()),
34338                    Identifier::new("index".to_string()),
34339                    Identifier::new(series_alias.clone()),
34340                    Identifier::new("this".to_string()),
34341                ]
34342            } else {
34343                vec![Identifier::new(series_alias.clone())]
34344            };
34345            Expression::Alias(Box::new(Alias {
34346                this: series_unnest_expr,
34347                alias: Identifier::new(series_source_alias.clone()),
34348                column_aliases: col_aliases,
34349                alias_explicit_as: false,
34350                alias_keyword: None,
34351                pre_alias_comments: Vec::new(),
34352                trailing_comments: Vec::new(),
34353                inferred_type: None,
34354            }))
34355        } else {
34356            Expression::Alias(Box::new(Alias::new(
34357                series_unnest_expr,
34358                Identifier::new(series_alias.clone()),
34359            )))
34360        };
34361
34362        // Build CROSS JOINs for each UNNEST
34363        let mut joins = Vec::new();
34364        for info in &unnest_infos {
34365            let actual_col_name = info.has_outer_alias.as_ref().unwrap_or(&info.col_alias);
34366
34367            let unnest_join_expr = match target {
34368                DialectType::BigQuery => {
34369                    // UNNEST([1,2,3]) AS col WITH OFFSET AS pos_2
34370                    let unnest = UnnestFunc {
34371                        this: info.arr_expr.clone(),
34372                        expressions: Vec::new(),
34373                        with_ordinality: true,
34374                        alias: Some(Identifier::new(actual_col_name.clone())),
34375                        offset_alias: Some(Identifier::new(info.pos_alias.clone())),
34376                    };
34377                    Expression::Unnest(Box::new(unnest))
34378                }
34379                DialectType::Presto | DialectType::Trino => {
34380                    let unnest = UnnestFunc {
34381                        this: info.arr_expr.clone(),
34382                        expressions: Vec::new(),
34383                        with_ordinality: true,
34384                        alias: None,
34385                        offset_alias: None,
34386                    };
34387                    Expression::Alias(Box::new(Alias {
34388                        this: Expression::Unnest(Box::new(unnest)),
34389                        alias: Identifier::new(info.source_alias.clone()),
34390                        column_aliases: vec![
34391                            Identifier::new(actual_col_name.clone()),
34392                            Identifier::new(info.pos_alias.clone()),
34393                        ],
34394                        alias_explicit_as: false,
34395                        alias_keyword: None,
34396                        pre_alias_comments: Vec::new(),
34397                        trailing_comments: Vec::new(),
34398                        inferred_type: None,
34399                    }))
34400                }
34401                DialectType::Snowflake => {
34402                    let flatten_arg =
34403                        Expression::NamedArgument(Box::new(crate::expressions::NamedArgument {
34404                            name: Identifier::new("INPUT".to_string()),
34405                            value: info.arr_expr.clone(),
34406                            separator: crate::expressions::NamedArgSeparator::DArrow,
34407                        }));
34408                    let flatten = Expression::Function(Box::new(Function::new(
34409                        "FLATTEN".to_string(),
34410                        vec![flatten_arg],
34411                    )));
34412                    let table_fn = Expression::Function(Box::new(Function::new(
34413                        "TABLE".to_string(),
34414                        vec![flatten],
34415                    )));
34416                    Expression::Alias(Box::new(Alias {
34417                        this: table_fn,
34418                        alias: Identifier::new(info.source_alias.clone()),
34419                        column_aliases: vec![
34420                            Identifier::new("seq".to_string()),
34421                            Identifier::new("key".to_string()),
34422                            Identifier::new("path".to_string()),
34423                            Identifier::new(info.pos_alias.clone()),
34424                            Identifier::new(actual_col_name.clone()),
34425                            Identifier::new("this".to_string()),
34426                        ],
34427                        alias_explicit_as: false,
34428                        alias_keyword: None,
34429                        pre_alias_comments: Vec::new(),
34430                        trailing_comments: Vec::new(),
34431                        inferred_type: None,
34432                    }))
34433                }
34434                _ => return None,
34435            };
34436
34437            joins.push(make_join(unnest_join_expr));
34438        }
34439
34440        // Build WHERE clause
34441        let mut where_conditions: Vec<Expression> = Vec::new();
34442        for info in &unnest_infos {
34443            let src_ref = if use_table_aliases {
34444                Some(info.source_alias.as_str())
34445            } else {
34446                None
34447            };
34448            let pos_col = make_col(&series_alias, tbl_ref);
34449            let unnest_pos_col = make_col(&info.pos_alias, src_ref);
34450
34451            let arr_size = Expression::Function(Box::new(Function::new(
34452                array_length_func.to_string(),
34453                vec![info.arr_expr.clone()],
34454            )));
34455
34456            let size_ref = if index_offset == 0 {
34457                Expression::Paren(Box::new(crate::expressions::Paren {
34458                    this: Expression::Sub(Box::new(BinaryOp::new(
34459                        arr_size,
34460                        Expression::Literal(Box::new(Literal::Number("1".to_string()))),
34461                    ))),
34462                    trailing_comments: Vec::new(),
34463                }))
34464            } else {
34465                arr_size
34466            };
34467
34468            let eq = Expression::Eq(Box::new(BinaryOp::new(
34469                pos_col.clone(),
34470                unnest_pos_col.clone(),
34471            )));
34472            let gt = Expression::Gt(Box::new(BinaryOp::new(pos_col, size_ref.clone())));
34473            let pos_eq_size = Expression::Eq(Box::new(BinaryOp::new(unnest_pos_col, size_ref)));
34474            let and_cond = Expression::And(Box::new(BinaryOp::new(gt, pos_eq_size)));
34475            let paren_and = Expression::Paren(Box::new(crate::expressions::Paren {
34476                this: and_cond,
34477                trailing_comments: Vec::new(),
34478            }));
34479            let or_cond = Expression::Or(Box::new(BinaryOp::new(eq, paren_and)));
34480
34481            where_conditions.push(or_cond);
34482        }
34483
34484        let where_expr = if where_conditions.len() == 1 {
34485            // Single condition: no parens needed
34486            where_conditions.into_iter().next().unwrap()
34487        } else {
34488            // Multiple conditions: wrap each OR in parens, then combine with AND
34489            let wrap = |e: Expression| {
34490                Expression::Paren(Box::new(crate::expressions::Paren {
34491                    this: e,
34492                    trailing_comments: Vec::new(),
34493                }))
34494            };
34495            let mut iter = where_conditions.into_iter();
34496            let first = wrap(iter.next().unwrap());
34497            let second = wrap(iter.next().unwrap());
34498            let mut combined = Expression::Paren(Box::new(crate::expressions::Paren {
34499                this: Expression::And(Box::new(BinaryOp::new(first, second))),
34500                trailing_comments: Vec::new(),
34501            }));
34502            for cond in iter {
34503                combined = Expression::And(Box::new(BinaryOp::new(combined, wrap(cond))));
34504            }
34505            combined
34506        };
34507
34508        // Build the new SELECT
34509        let mut new_select = select.clone();
34510        new_select.expressions = new_select_exprs;
34511
34512        if new_select.from.is_some() {
34513            let mut all_joins = vec![make_join(series_alias_expr)];
34514            all_joins.extend(joins);
34515            new_select.joins.extend(all_joins);
34516        } else {
34517            new_select.from = Some(From {
34518                expressions: vec![series_alias_expr],
34519            });
34520            new_select.joins.extend(joins);
34521        }
34522
34523        if let Some(ref existing_where) = new_select.where_clause {
34524            let combined = Expression::And(Box::new(BinaryOp::new(
34525                existing_where.this.clone(),
34526                where_expr,
34527            )));
34528            new_select.where_clause = Some(crate::expressions::Where { this: combined });
34529        } else {
34530            new_select.where_clause = Some(crate::expressions::Where { this: where_expr });
34531        }
34532
34533        Some(new_select)
34534    }
34535
34536    /// Helper to replace UNNEST(...) inside an expression with a replacement expression.
34537    fn replace_unnest_with_if(original: &Expression, replacement: &Expression) -> Expression {
34538        match original {
34539            Expression::Unnest(_) => replacement.clone(),
34540            Expression::Function(f) if f.name.eq_ignore_ascii_case("UNNEST") => replacement.clone(),
34541            Expression::Alias(a) => Self::replace_unnest_with_if(&a.this, replacement),
34542            Expression::Add(op) => {
34543                let left = Self::replace_unnest_with_if(&op.left, replacement);
34544                let right = Self::replace_unnest_with_if(&op.right, replacement);
34545                Expression::Add(Box::new(crate::expressions::BinaryOp::new(left, right)))
34546            }
34547            Expression::Sub(op) => {
34548                let left = Self::replace_unnest_with_if(&op.left, replacement);
34549                let right = Self::replace_unnest_with_if(&op.right, replacement);
34550                Expression::Sub(Box::new(crate::expressions::BinaryOp::new(left, right)))
34551            }
34552            Expression::Mul(op) => {
34553                let left = Self::replace_unnest_with_if(&op.left, replacement);
34554                let right = Self::replace_unnest_with_if(&op.right, replacement);
34555                Expression::Mul(Box::new(crate::expressions::BinaryOp::new(left, right)))
34556            }
34557            Expression::Div(op) => {
34558                let left = Self::replace_unnest_with_if(&op.left, replacement);
34559                let right = Self::replace_unnest_with_if(&op.right, replacement);
34560                Expression::Div(Box::new(crate::expressions::BinaryOp::new(left, right)))
34561            }
34562            _ => original.clone(),
34563        }
34564    }
34565
34566    /// Decompose a JSON path like `$.y[0].z` into individual parts: `["y", "0", "z"]`.
34567    /// Strips `$` prefix, handles bracket notation, quoted strings, and removes `[*]` wildcards.
34568    fn decompose_json_path(path: &str) -> Vec<String> {
34569        let mut parts = Vec::new();
34570        let path = if path.starts_with("$.") {
34571            &path[2..]
34572        } else if path.starts_with('$') {
34573            &path[1..]
34574        } else {
34575            path
34576        };
34577        if path.is_empty() {
34578            return parts;
34579        }
34580        let mut current = String::new();
34581        let chars: Vec<char> = path.chars().collect();
34582        let mut i = 0;
34583        while i < chars.len() {
34584            match chars[i] {
34585                '.' => {
34586                    if !current.is_empty() {
34587                        parts.push(current.clone());
34588                        current.clear();
34589                    }
34590                    i += 1;
34591                }
34592                '[' => {
34593                    if !current.is_empty() {
34594                        parts.push(current.clone());
34595                        current.clear();
34596                    }
34597                    i += 1;
34598                    let mut bracket_content = String::new();
34599                    while i < chars.len() && chars[i] != ']' {
34600                        if chars[i] == '"' || chars[i] == '\'' {
34601                            let quote = chars[i];
34602                            i += 1;
34603                            while i < chars.len() && chars[i] != quote {
34604                                bracket_content.push(chars[i]);
34605                                i += 1;
34606                            }
34607                            if i < chars.len() {
34608                                i += 1;
34609                            }
34610                        } else {
34611                            bracket_content.push(chars[i]);
34612                            i += 1;
34613                        }
34614                    }
34615                    if i < chars.len() {
34616                        i += 1;
34617                    }
34618                    if bracket_content != "*" {
34619                        parts.push(bracket_content);
34620                    }
34621                }
34622                _ => {
34623                    current.push(chars[i]);
34624                    i += 1;
34625                }
34626            }
34627        }
34628        if !current.is_empty() {
34629            parts.push(current);
34630        }
34631        parts
34632    }
34633
34634    /// Normalize a JSON path operand for T-SQL/Fabric JSON_QUERY/JSON_VALUE.
34635    ///
34636    /// PostgreSQL arrow operators accept bare keys (`json -> 'name'`) and numeric
34637    /// array indexes (`json -> 0`), while T-SQL JSON paths must be rooted at `$`.
34638    /// PostgreSQL #>/#>> path arrays (`'{a,0}'`) are also converted here.
34639    fn normalize_tsql_json_path_expr(
34640        path: Expression,
34641        postgres_path_array_literal: bool,
34642        string_numbers_are_indexes: bool,
34643    ) -> Expression {
34644        match path {
34645            Expression::Literal(lit) => match lit.as_ref() {
34646                Literal::String(s) => {
34647                    let normalized = if postgres_path_array_literal {
34648                        if let Some(parts) = Self::parse_postgres_json_path_array_literal(s) {
34649                            Self::tsql_json_path_from_segments(&parts, true)
34650                        } else {
34651                            Self::normalize_tsql_json_path_string(s, string_numbers_are_indexes)
34652                        }
34653                    } else {
34654                        Self::normalize_tsql_json_path_string(s, string_numbers_are_indexes)
34655                    };
34656                    Expression::string(normalized)
34657                }
34658                Literal::Number(n) => Expression::string(format!("$[{n}]")),
34659                _ => Expression::Literal(lit),
34660            },
34661            other => other,
34662        }
34663    }
34664
34665    fn normalize_tsql_json_path_parts(paths: Vec<Expression>) -> Expression {
34666        if paths.len() == 1 {
34667            return Self::normalize_tsql_json_path_expr(
34668                paths.into_iter().next().expect("checked len"),
34669                true,
34670                true,
34671            );
34672        }
34673
34674        let mut segments = Vec::new();
34675        for path in paths {
34676            match path {
34677                Expression::Literal(lit) => match lit.as_ref() {
34678                    Literal::String(s) => {
34679                        if let Some(parts) = Self::parse_postgres_json_path_array_literal(s) {
34680                            segments.extend(parts);
34681                        } else {
34682                            segments.push(s.clone());
34683                        }
34684                    }
34685                    Literal::Number(n) => segments.push(n.clone()),
34686                    _ => return Expression::Literal(lit),
34687                },
34688                other => return other,
34689            }
34690        }
34691
34692        Expression::string(Self::tsql_json_path_from_segments(&segments, true))
34693    }
34694
34695    fn normalize_tsql_json_path_string(path: &str, string_numbers_are_indexes: bool) -> String {
34696        let trimmed = path.trim();
34697        let lower = trimmed.to_ascii_lowercase();
34698
34699        if trimmed.starts_with('$') || lower.starts_with("lax $") || lower.starts_with("strict $") {
34700            return Self::bracket_to_dot_notation(trimmed);
34701        }
34702
34703        if trimmed.starts_with('[') {
34704            return format!("${}", Self::bracket_to_dot_notation(trimmed));
34705        }
34706
34707        if string_numbers_are_indexes && Self::is_json_array_index(trimmed) {
34708            return format!("$[{trimmed}]");
34709        }
34710
34711        let mut result = "$".to_string();
34712        Self::push_tsql_json_path_segment(&mut result, trimmed, false);
34713        result
34714    }
34715
34716    fn parse_postgres_json_path_array_literal(path: &str) -> Option<Vec<String>> {
34717        let trimmed = path.trim();
34718        if !(trimmed.starts_with('{') && trimmed.ends_with('}')) {
34719            return None;
34720        }
34721
34722        let inner = &trimmed[1..trimmed.len().saturating_sub(1)];
34723        let mut parts = Vec::new();
34724        let mut current = String::new();
34725        let mut chars = inner.chars().peekable();
34726        let mut in_quotes = false;
34727        let mut escaped = false;
34728
34729        while let Some(ch) = chars.next() {
34730            if escaped {
34731                current.push(ch);
34732                escaped = false;
34733                continue;
34734            }
34735
34736            if in_quotes {
34737                match ch {
34738                    '\\' => escaped = true,
34739                    '"' => {
34740                        if matches!(chars.peek(), Some('"')) {
34741                            current.push('"');
34742                            chars.next();
34743                        } else {
34744                            in_quotes = false;
34745                        }
34746                    }
34747                    _ => current.push(ch),
34748                }
34749                continue;
34750            }
34751
34752            match ch {
34753                '"' => in_quotes = true,
34754                ',' => {
34755                    parts.push(current.trim().to_string());
34756                    current.clear();
34757                }
34758                _ => current.push(ch),
34759            }
34760        }
34761
34762        parts.push(current.trim().to_string());
34763        Some(parts)
34764    }
34765
34766    fn tsql_json_path_from_segments(
34767        segments: &[String],
34768        numeric_strings_are_indexes: bool,
34769    ) -> String {
34770        let mut path = "$".to_string();
34771        for segment in segments {
34772            Self::push_tsql_json_path_segment(&mut path, segment, numeric_strings_are_indexes);
34773        }
34774        path
34775    }
34776
34777    fn push_tsql_json_path_segment(
34778        path: &mut String,
34779        segment: &str,
34780        numeric_string_is_index: bool,
34781    ) {
34782        if numeric_string_is_index && Self::is_json_array_index(segment) {
34783            path.push('[');
34784            path.push_str(segment);
34785            path.push(']');
34786            return;
34787        }
34788
34789        if Self::is_simple_json_path_key(segment) {
34790            path.push('.');
34791            path.push_str(segment);
34792        } else {
34793            path.push_str(".\"");
34794            path.push_str(&segment.replace('\\', "\\\\").replace('"', "\\\""));
34795            path.push('"');
34796        }
34797    }
34798
34799    fn is_json_array_index(segment: &str) -> bool {
34800        !segment.is_empty() && segment.chars().all(|c| c.is_ascii_digit())
34801    }
34802
34803    fn is_simple_json_path_key(segment: &str) -> bool {
34804        !segment.is_empty()
34805            && !segment.starts_with('$')
34806            && segment
34807                .chars()
34808                .all(|c| c.is_ascii_alphanumeric() || c == '_')
34809    }
34810
34811    fn build_tsql_json_function(name: &str, this: Expression, path: Expression) -> Expression {
34812        let (this, path) = Self::collapse_nested_tsql_json_query(this, path);
34813        Expression::Function(Box::new(crate::expressions::Function::new(
34814            name.to_string(),
34815            vec![this, path],
34816        )))
34817    }
34818
34819    fn collapse_nested_tsql_json_query(
34820        this: Expression,
34821        path: Expression,
34822    ) -> (Expression, Expression) {
34823        if let Expression::Function(f) = &this {
34824            if f.name.eq_ignore_ascii_case("JSON_QUERY") && f.args.len() == 2 {
34825                if let (Some(prefix), Some(suffix)) = (
34826                    Self::literal_string_value(&f.args[1]),
34827                    Self::literal_string_value(&path),
34828                ) {
34829                    if let Some(combined) = Self::join_tsql_json_paths(prefix, suffix) {
34830                        return (f.args[0].clone(), Expression::string(combined));
34831                    }
34832                }
34833            }
34834        }
34835
34836        (this, path)
34837    }
34838
34839    fn literal_string_value(expr: &Expression) -> Option<&str> {
34840        match expr {
34841            Expression::Literal(lit) => match lit.as_ref() {
34842                Literal::String(s) => Some(s.as_str()),
34843                _ => None,
34844            },
34845            _ => None,
34846        }
34847    }
34848
34849    fn join_tsql_json_paths(prefix: &str, suffix: &str) -> Option<String> {
34850        if prefix == "$" {
34851            return Some(suffix.to_string());
34852        }
34853        if suffix == "$" {
34854            return Some(prefix.to_string());
34855        }
34856
34857        if let Some(rest) = suffix.strip_prefix("$.") {
34858            Some(format!("{prefix}.{rest}"))
34859        } else {
34860            suffix
34861                .strip_prefix("$[")
34862                .map(|rest| format!("{prefix}[{rest}"))
34863        }
34864    }
34865
34866    /// Strip `$` prefix from a JSON path, keeping the rest.
34867    /// `$.y[0].z` -> `y[0].z`, `$["a b"]` -> `["a b"]`
34868    fn strip_json_dollar_prefix(path: &str) -> String {
34869        if path.starts_with("$.") {
34870            path[2..].to_string()
34871        } else if path.starts_with('$') {
34872            path[1..].to_string()
34873        } else {
34874            path.to_string()
34875        }
34876    }
34877
34878    /// Strip `[*]` wildcards from a JSON path.
34879    /// `$.y[*]` -> `$.y`, `$.y[*].z` -> `$.y.z`
34880    fn strip_json_wildcards(path: &str) -> String {
34881        path.replace("[*]", "")
34882            .replace("..", ".") // Clean double dots from `$.y[*].z` -> `$.y..z`
34883            .trim_end_matches('.')
34884            .to_string()
34885    }
34886
34887    /// Convert bracket notation to dot notation for JSON paths.
34888    /// `$["a b"]` -> `$."a b"`, `$["key"]` -> `$.key`
34889    fn bracket_to_dot_notation(path: &str) -> String {
34890        let mut result = String::new();
34891        let chars: Vec<char> = path.chars().collect();
34892        let mut i = 0;
34893        while i < chars.len() {
34894            if chars[i] == '[' {
34895                // Read bracket content
34896                i += 1;
34897                let mut bracket_content = String::new();
34898                let mut is_quoted = false;
34899                let mut _quote_char = '"';
34900                while i < chars.len() && chars[i] != ']' {
34901                    if chars[i] == '"' || chars[i] == '\'' {
34902                        is_quoted = true;
34903                        _quote_char = chars[i];
34904                        i += 1;
34905                        while i < chars.len() && chars[i] != _quote_char {
34906                            bracket_content.push(chars[i]);
34907                            i += 1;
34908                        }
34909                        if i < chars.len() {
34910                            i += 1;
34911                        }
34912                    } else {
34913                        bracket_content.push(chars[i]);
34914                        i += 1;
34915                    }
34916                }
34917                if i < chars.len() {
34918                    i += 1;
34919                } // skip ]
34920                if bracket_content == "*" {
34921                    // Keep wildcard as-is
34922                    result.push_str("[*]");
34923                } else if is_quoted {
34924                    // Quoted bracket -> dot notation with quotes
34925                    result.push('.');
34926                    result.push('"');
34927                    result.push_str(&bracket_content);
34928                    result.push('"');
34929                } else {
34930                    // Numeric index -> keep as bracket
34931                    result.push('[');
34932                    result.push_str(&bracket_content);
34933                    result.push(']');
34934                }
34935            } else {
34936                result.push(chars[i]);
34937                i += 1;
34938            }
34939        }
34940        result
34941    }
34942
34943    /// Convert JSON path bracket quoted strings to use single quotes instead of double quotes.
34944    /// `$["a b"]` -> `$['a b']`
34945    fn bracket_to_single_quotes(path: &str) -> String {
34946        let mut result = String::new();
34947        let chars: Vec<char> = path.chars().collect();
34948        let mut i = 0;
34949        while i < chars.len() {
34950            if chars[i] == '[' && i + 1 < chars.len() && chars[i + 1] == '"' {
34951                result.push('[');
34952                result.push('\'');
34953                i += 2; // skip [ and "
34954                while i < chars.len() && chars[i] != '"' {
34955                    result.push(chars[i]);
34956                    i += 1;
34957                }
34958                if i < chars.len() {
34959                    i += 1;
34960                } // skip closing "
34961                result.push('\'');
34962            } else {
34963                result.push(chars[i]);
34964                i += 1;
34965            }
34966        }
34967        result
34968    }
34969
34970    /// Transform TSQL SELECT INTO -> CREATE TABLE AS for DuckDB/Snowflake
34971    /// or PostgreSQL #temp -> TEMPORARY.
34972    /// Also strips # from INSERT INTO #table for non-TSQL targets.
34973    fn transform_select_into(
34974        expr: Expression,
34975        _source: DialectType,
34976        target: DialectType,
34977    ) -> Expression {
34978        use crate::expressions::{CreateTable, Expression, TableRef};
34979
34980        // Handle INSERT INTO #temp -> INSERT INTO temp for non-TSQL targets
34981        if let Expression::Insert(ref insert) = expr {
34982            if insert.table.name.name.starts_with('#')
34983                && !matches!(target, DialectType::TSQL | DialectType::Fabric)
34984            {
34985                let mut new_insert = insert.clone();
34986                new_insert.table.name.name =
34987                    insert.table.name.name.trim_start_matches('#').to_string();
34988                return Expression::Insert(new_insert);
34989            }
34990            return expr;
34991        }
34992
34993        if let Expression::Select(ref select) = expr {
34994            if let Some(ref into) = select.into {
34995                let table_name_raw = match &into.this {
34996                    Expression::Table(tr) => tr.name.name.clone(),
34997                    Expression::Identifier(id) => id.name.clone(),
34998                    _ => String::new(),
34999                };
35000                let is_temp = table_name_raw.starts_with('#') || into.temporary;
35001                let clean_name = table_name_raw.trim_start_matches('#').to_string();
35002
35003                match target {
35004                    DialectType::DuckDB | DialectType::Snowflake => {
35005                        // SELECT INTO -> CREATE TABLE AS SELECT
35006                        let mut new_select = select.clone();
35007                        new_select.into = None;
35008                        let ct = CreateTable {
35009                            name: TableRef::new(clean_name),
35010                            on_cluster: None,
35011                            columns: Vec::new(),
35012                            constraints: Vec::new(),
35013                            if_not_exists: false,
35014                            temporary: is_temp,
35015                            or_replace: false,
35016                            table_modifier: None,
35017                            as_select: Some(Expression::Select(new_select)),
35018                            as_select_parenthesized: false,
35019                            on_commit: None,
35020                            clone_source: None,
35021                            clone_at_clause: None,
35022                            shallow_clone: false,
35023                            deep_clone: false,
35024                            is_copy: false,
35025                            leading_comments: Vec::new(),
35026                            with_properties: Vec::new(),
35027                            teradata_post_name_options: Vec::new(),
35028                            with_data: None,
35029                            with_statistics: None,
35030                            teradata_indexes: Vec::new(),
35031                            with_cte: None,
35032                            properties: Vec::new(),
35033                            partition_of: None,
35034                            post_table_properties: Vec::new(),
35035                            mysql_table_options: Vec::new(),
35036                            inherits: Vec::new(),
35037                            on_property: None,
35038                            copy_grants: false,
35039                            using_template: None,
35040                            rollup: None,
35041                            uuid: None,
35042                            with_partition_columns: Vec::new(),
35043                            with_connection: None,
35044                        };
35045                        return Expression::CreateTable(Box::new(ct));
35046                    }
35047                    DialectType::PostgreSQL | DialectType::Redshift => {
35048                        // PostgreSQL: #foo -> INTO TEMPORARY foo
35049                        if is_temp && !into.temporary {
35050                            let mut new_select = select.clone();
35051                            let mut new_into = into.clone();
35052                            new_into.temporary = true;
35053                            new_into.unlogged = false;
35054                            new_into.this = Expression::Table(Box::new(TableRef::new(clean_name)));
35055                            new_select.into = Some(new_into);
35056                            Expression::Select(new_select)
35057                        } else {
35058                            expr
35059                        }
35060                    }
35061                    _ => expr,
35062                }
35063            } else {
35064                expr
35065            }
35066        } else {
35067            expr
35068        }
35069    }
35070
35071    /// Transform CREATE TABLE WITH properties for cross-dialect transpilation.
35072    /// Handles FORMAT, PARTITIONED_BY, and other Presto WITH properties.
35073    fn transform_create_table_properties(
35074        ct: &mut crate::expressions::CreateTable,
35075        _source: DialectType,
35076        target: DialectType,
35077    ) {
35078        use crate::expressions::{
35079            BinaryOp, BooleanLiteral, Expression, FileFormatProperty, Identifier, Literal,
35080            Properties,
35081        };
35082
35083        // Helper to convert a raw property value string to the correct Expression
35084        let value_to_expr = |v: &str| -> Expression {
35085            let trimmed = v.trim();
35086            // Check if it's a quoted string (starts and ends with ')
35087            if trimmed.starts_with('\'') && trimmed.ends_with('\'') {
35088                Expression::Literal(Box::new(Literal::String(
35089                    trimmed[1..trimmed.len() - 1].to_string(),
35090                )))
35091            }
35092            // Check if it's a number
35093            else if trimmed.parse::<i64>().is_ok() || trimmed.parse::<f64>().is_ok() {
35094                Expression::Literal(Box::new(Literal::Number(trimmed.to_string())))
35095            }
35096            // Check if it's ARRAY[...] or ARRAY(...)
35097            else if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
35098                // Convert ARRAY['y'] to ARRAY('y') for Hive/Spark
35099                let inner = trimmed
35100                    .trim_start_matches(|c: char| c.is_alphabetic()) // Remove ARRAY
35101                    .trim_start_matches('[')
35102                    .trim_start_matches('(')
35103                    .trim_end_matches(']')
35104                    .trim_end_matches(')');
35105                let elements: Vec<Expression> = inner
35106                    .split(',')
35107                    .map(|e| {
35108                        let elem = e.trim().trim_matches('\'');
35109                        Expression::Literal(Box::new(Literal::String(elem.to_string())))
35110                    })
35111                    .collect();
35112                Expression::Function(Box::new(crate::expressions::Function::new(
35113                    "ARRAY".to_string(),
35114                    elements,
35115                )))
35116            }
35117            // Otherwise, just output as identifier (unquoted)
35118            else {
35119                Expression::Identifier(Identifier::new(trimmed.to_string()))
35120            }
35121        };
35122
35123        if ct.with_properties.is_empty() && ct.properties.is_empty() {
35124            return;
35125        }
35126
35127        // Handle Presto-style WITH properties
35128        if !ct.with_properties.is_empty() {
35129            // Extract FORMAT property and remaining properties
35130            let mut format_value: Option<String> = None;
35131            let mut partitioned_by: Option<String> = None;
35132            let mut other_props: Vec<(String, String)> = Vec::new();
35133
35134            for (key, value) in ct.with_properties.drain(..) {
35135                if key.eq_ignore_ascii_case("FORMAT") {
35136                    // Strip surrounding quotes from value if present
35137                    format_value = Some(value.trim_matches('\'').to_string());
35138                } else if key.eq_ignore_ascii_case("PARTITIONED_BY") {
35139                    partitioned_by = Some(value);
35140                } else {
35141                    other_props.push((key, value));
35142                }
35143            }
35144
35145            match target {
35146                DialectType::Presto | DialectType::Trino | DialectType::Athena => {
35147                    // Presto: keep WITH properties but lowercase 'format' key
35148                    if let Some(fmt) = format_value {
35149                        ct.with_properties
35150                            .push(("format".to_string(), format!("'{}'", fmt)));
35151                    }
35152                    if let Some(part) = partitioned_by {
35153                        // Convert (col1, col2) to ARRAY['col1', 'col2'] format
35154                        let trimmed = part.trim();
35155                        let inner = trimmed.trim_start_matches('(').trim_end_matches(')');
35156                        // Also handle ARRAY['...'] format - keep as-is
35157                        if trimmed.len() >= 5 && trimmed[..5].eq_ignore_ascii_case("ARRAY") {
35158                            ct.with_properties
35159                                .push(("PARTITIONED_BY".to_string(), part));
35160                        } else {
35161                            // Parse column names from the parenthesized list
35162                            let cols: Vec<&str> = inner
35163                                .split(',')
35164                                .map(|c| c.trim().trim_matches('"').trim_matches('\''))
35165                                .collect();
35166                            let array_val = format!(
35167                                "ARRAY[{}]",
35168                                cols.iter()
35169                                    .map(|c| format!("'{}'", c))
35170                                    .collect::<Vec<_>>()
35171                                    .join(", ")
35172                            );
35173                            ct.with_properties
35174                                .push(("PARTITIONED_BY".to_string(), array_val));
35175                        }
35176                    }
35177                    ct.with_properties.extend(other_props);
35178                }
35179                DialectType::Hive => {
35180                    // Hive: FORMAT -> STORED AS, other props -> TBLPROPERTIES
35181                    if let Some(fmt) = format_value {
35182                        ct.properties.push(Expression::FileFormatProperty(Box::new(
35183                            FileFormatProperty {
35184                                this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
35185                                expressions: vec![],
35186                                hive_format: Some(Box::new(Expression::Boolean(BooleanLiteral {
35187                                    value: true,
35188                                }))),
35189                            },
35190                        )));
35191                    }
35192                    if let Some(_part) = partitioned_by {
35193                        // PARTITIONED_BY handling is complex - move columns to partitioned by
35194                        // For now, the partition columns are extracted from the column list
35195                        Self::apply_partitioned_by(ct, &_part, target);
35196                    }
35197                    if !other_props.is_empty() {
35198                        let eq_exprs: Vec<Expression> = other_props
35199                            .into_iter()
35200                            .map(|(k, v)| {
35201                                Expression::Eq(Box::new(BinaryOp::new(
35202                                    Expression::Literal(Box::new(Literal::String(k))),
35203                                    value_to_expr(&v),
35204                                )))
35205                            })
35206                            .collect();
35207                        ct.properties
35208                            .push(Expression::Properties(Box::new(Properties {
35209                                expressions: eq_exprs,
35210                            })));
35211                    }
35212                }
35213                DialectType::Spark | DialectType::Databricks => {
35214                    // Spark: FORMAT -> USING, other props -> TBLPROPERTIES
35215                    if let Some(fmt) = format_value {
35216                        ct.properties.push(Expression::FileFormatProperty(Box::new(
35217                            FileFormatProperty {
35218                                this: Some(Box::new(Expression::Identifier(Identifier::new(fmt)))),
35219                                expressions: vec![],
35220                                hive_format: None, // None means USING syntax
35221                            },
35222                        )));
35223                    }
35224                    if let Some(_part) = partitioned_by {
35225                        Self::apply_partitioned_by(ct, &_part, target);
35226                    }
35227                    if !other_props.is_empty() {
35228                        let eq_exprs: Vec<Expression> = other_props
35229                            .into_iter()
35230                            .map(|(k, v)| {
35231                                Expression::Eq(Box::new(BinaryOp::new(
35232                                    Expression::Literal(Box::new(Literal::String(k))),
35233                                    value_to_expr(&v),
35234                                )))
35235                            })
35236                            .collect();
35237                        ct.properties
35238                            .push(Expression::Properties(Box::new(Properties {
35239                                expressions: eq_exprs,
35240                            })));
35241                    }
35242                }
35243                DialectType::DuckDB => {
35244                    // DuckDB: strip all WITH properties (FORMAT, PARTITIONED_BY, etc.)
35245                    // Keep nothing
35246                }
35247                _ => {
35248                    // For other dialects, keep WITH properties as-is
35249                    if let Some(fmt) = format_value {
35250                        ct.with_properties
35251                            .push(("FORMAT".to_string(), format!("'{}'", fmt)));
35252                    }
35253                    if let Some(part) = partitioned_by {
35254                        ct.with_properties
35255                            .push(("PARTITIONED_BY".to_string(), part));
35256                    }
35257                    ct.with_properties.extend(other_props);
35258                }
35259            }
35260        }
35261
35262        // Handle STORED AS 'PARQUET' (quoted format name) -> STORED AS PARQUET (unquoted)
35263        // and Hive STORED AS -> Presto WITH (format=...) conversion
35264        if !ct.properties.is_empty() {
35265            let is_presto_target = matches!(
35266                target,
35267                DialectType::Presto | DialectType::Trino | DialectType::Athena
35268            );
35269            let is_duckdb_target = matches!(target, DialectType::DuckDB);
35270
35271            if is_presto_target || is_duckdb_target {
35272                let mut new_properties = Vec::new();
35273                for prop in ct.properties.drain(..) {
35274                    match &prop {
35275                        Expression::FileFormatProperty(ffp) => {
35276                            if is_presto_target {
35277                                // Convert STORED AS/USING to WITH (format=...)
35278                                if let Some(ref fmt_expr) = ffp.this {
35279                                    let fmt_str = match fmt_expr.as_ref() {
35280                                        Expression::Identifier(id) => id.name.clone(),
35281                                        Expression::Literal(lit)
35282                                            if matches!(lit.as_ref(), Literal::String(_)) =>
35283                                        {
35284                                            let Literal::String(s) = lit.as_ref() else {
35285                                                unreachable!()
35286                                            };
35287                                            s.clone()
35288                                        }
35289                                        _ => {
35290                                            new_properties.push(prop);
35291                                            continue;
35292                                        }
35293                                    };
35294                                    ct.with_properties
35295                                        .push(("format".to_string(), format!("'{}'", fmt_str)));
35296                                }
35297                            }
35298                            // DuckDB: just strip file format properties
35299                        }
35300                        // Convert TBLPROPERTIES to WITH properties for Presto target
35301                        Expression::Properties(props) if is_presto_target => {
35302                            for expr in &props.expressions {
35303                                if let Expression::Eq(eq) = expr {
35304                                    // Extract key and value from the Eq expression
35305                                    let key = match &eq.left {
35306                                        Expression::Literal(lit)
35307                                            if matches!(lit.as_ref(), Literal::String(_)) =>
35308                                        {
35309                                            let Literal::String(s) = lit.as_ref() else {
35310                                                unreachable!()
35311                                            };
35312                                            s.clone()
35313                                        }
35314                                        Expression::Identifier(id) => id.name.clone(),
35315                                        _ => continue,
35316                                    };
35317                                    let value = match &eq.right {
35318                                        Expression::Literal(lit)
35319                                            if matches!(lit.as_ref(), Literal::String(_)) =>
35320                                        {
35321                                            let Literal::String(s) = lit.as_ref() else {
35322                                                unreachable!()
35323                                            };
35324                                            format!("'{}'", s)
35325                                        }
35326                                        Expression::Literal(lit)
35327                                            if matches!(lit.as_ref(), Literal::Number(_)) =>
35328                                        {
35329                                            let Literal::Number(n) = lit.as_ref() else {
35330                                                unreachable!()
35331                                            };
35332                                            n.clone()
35333                                        }
35334                                        Expression::Identifier(id) => id.name.clone(),
35335                                        _ => continue,
35336                                    };
35337                                    ct.with_properties.push((key, value));
35338                                }
35339                            }
35340                        }
35341                        // Convert PartitionedByProperty for Presto target
35342                        Expression::PartitionedByProperty(ref pbp) if is_presto_target => {
35343                            // Check if it contains ColumnDef expressions (Hive-style with types)
35344                            if let Expression::Tuple(ref tuple) = *pbp.this {
35345                                let mut col_names: Vec<String> = Vec::new();
35346                                let mut col_defs: Vec<crate::expressions::ColumnDef> = Vec::new();
35347                                let mut has_col_defs = false;
35348                                for expr in &tuple.expressions {
35349                                    if let Expression::ColumnDef(ref cd) = expr {
35350                                        has_col_defs = true;
35351                                        col_names.push(cd.name.name.clone());
35352                                        col_defs.push(*cd.clone());
35353                                    } else if let Expression::Column(ref col) = expr {
35354                                        col_names.push(col.name.name.clone());
35355                                    } else if let Expression::Identifier(ref id) = expr {
35356                                        col_names.push(id.name.clone());
35357                                    } else {
35358                                        // For function expressions like MONTHS(y), serialize to SQL
35359                                        let generic = Dialect::get(DialectType::Generic);
35360                                        if let Ok(sql) = generic.generate(expr) {
35361                                            col_names.push(sql);
35362                                        }
35363                                    }
35364                                }
35365                                if has_col_defs {
35366                                    // Merge partition column defs into the main column list
35367                                    for cd in col_defs {
35368                                        ct.columns.push(cd);
35369                                    }
35370                                }
35371                                if !col_names.is_empty() {
35372                                    // Add PARTITIONED_BY property
35373                                    let array_val = format!(
35374                                        "ARRAY[{}]",
35375                                        col_names
35376                                            .iter()
35377                                            .map(|n| format!("'{}'", n))
35378                                            .collect::<Vec<_>>()
35379                                            .join(", ")
35380                                    );
35381                                    ct.with_properties
35382                                        .push(("PARTITIONED_BY".to_string(), array_val));
35383                                }
35384                            }
35385                            // Skip - don't keep in properties
35386                        }
35387                        _ => {
35388                            if !is_duckdb_target {
35389                                new_properties.push(prop);
35390                            }
35391                        }
35392                    }
35393                }
35394                ct.properties = new_properties;
35395            } else {
35396                // For Hive/Spark targets, unquote format names in STORED AS
35397                for prop in &mut ct.properties {
35398                    if let Expression::FileFormatProperty(ref mut ffp) = prop {
35399                        if let Some(ref mut fmt_expr) = ffp.this {
35400                            if let Expression::Literal(lit) = fmt_expr.as_ref() {
35401                                if let Literal::String(s) = lit.as_ref() {
35402                                    // Convert STORED AS 'PARQUET' to STORED AS PARQUET (unquote)
35403                                    let unquoted = s.clone();
35404                                    *fmt_expr =
35405                                        Box::new(Expression::Identifier(Identifier::new(unquoted)));
35406                                }
35407                            }
35408                        }
35409                    }
35410                }
35411            }
35412        }
35413    }
35414
35415    /// Apply PARTITIONED_BY conversion: move partition columns from column list to PARTITIONED BY
35416    fn apply_partitioned_by(
35417        ct: &mut crate::expressions::CreateTable,
35418        partitioned_by_value: &str,
35419        target: DialectType,
35420    ) {
35421        use crate::expressions::{Column, Expression, Identifier, PartitionedByProperty, Tuple};
35422
35423        // Parse the ARRAY['col1', 'col2'] value to extract column names
35424        let mut col_names: Vec<String> = Vec::new();
35425        // The value looks like ARRAY['y', 'z'] or ARRAY('y', 'z')
35426        let inner = partitioned_by_value
35427            .trim()
35428            .trim_start_matches("ARRAY")
35429            .trim_start_matches('[')
35430            .trim_start_matches('(')
35431            .trim_end_matches(']')
35432            .trim_end_matches(')');
35433        for part in inner.split(',') {
35434            let col = part.trim().trim_matches('\'').trim_matches('"');
35435            if !col.is_empty() {
35436                col_names.push(col.to_string());
35437            }
35438        }
35439
35440        if col_names.is_empty() {
35441            return;
35442        }
35443
35444        if matches!(target, DialectType::Hive) {
35445            // Hive: PARTITIONED BY (col_name type, ...) - move columns out of column list
35446            let mut partition_col_defs = Vec::new();
35447            for col_name in &col_names {
35448                // Find and remove from columns
35449                if let Some(pos) = ct
35450                    .columns
35451                    .iter()
35452                    .position(|c| c.name.name.eq_ignore_ascii_case(col_name))
35453                {
35454                    let col_def = ct.columns.remove(pos);
35455                    partition_col_defs.push(Expression::ColumnDef(Box::new(col_def)));
35456                }
35457            }
35458            if !partition_col_defs.is_empty() {
35459                ct.properties
35460                    .push(Expression::PartitionedByProperty(Box::new(
35461                        PartitionedByProperty {
35462                            this: Box::new(Expression::Tuple(Box::new(Tuple {
35463                                expressions: partition_col_defs,
35464                            }))),
35465                        },
35466                    )));
35467            }
35468        } else if matches!(target, DialectType::Spark | DialectType::Databricks) {
35469            // Spark: PARTITIONED BY (col1, col2) - just column names, keep in column list
35470            // Use quoted identifiers to match the quoting style of the original column definitions
35471            let partition_exprs: Vec<Expression> = col_names
35472                .iter()
35473                .map(|name| {
35474                    // Check if the column exists in the column list and use its quoting
35475                    let is_quoted = ct
35476                        .columns
35477                        .iter()
35478                        .any(|c| c.name.name.eq_ignore_ascii_case(name) && c.name.quoted);
35479                    let ident = if is_quoted {
35480                        Identifier::quoted(name.clone())
35481                    } else {
35482                        Identifier::new(name.clone())
35483                    };
35484                    Expression::boxed_column(Column {
35485                        name: ident,
35486                        table: None,
35487                        join_mark: false,
35488                        trailing_comments: Vec::new(),
35489                        span: None,
35490                        inferred_type: None,
35491                    })
35492                })
35493                .collect();
35494            ct.properties
35495                .push(Expression::PartitionedByProperty(Box::new(
35496                    PartitionedByProperty {
35497                        this: Box::new(Expression::Tuple(Box::new(Tuple {
35498                            expressions: partition_exprs,
35499                        }))),
35500                    },
35501                )));
35502        }
35503        // DuckDB: strip partitioned_by entirely (already handled)
35504    }
35505
35506    /// Convert a DataType to Spark's type string format (using angle brackets)
35507    fn data_type_to_spark_string(dt: &crate::expressions::DataType) -> String {
35508        use crate::expressions::DataType;
35509        match dt {
35510            DataType::Int { .. } => "INT".to_string(),
35511            DataType::BigInt { .. } => "BIGINT".to_string(),
35512            DataType::SmallInt { .. } => "SMALLINT".to_string(),
35513            DataType::TinyInt { .. } => "TINYINT".to_string(),
35514            DataType::Float { .. } => "FLOAT".to_string(),
35515            DataType::Double { .. } => "DOUBLE".to_string(),
35516            DataType::Decimal {
35517                precision: Some(p),
35518                scale: Some(s),
35519            } => format!("DECIMAL({}, {})", p, s),
35520            DataType::Decimal {
35521                precision: Some(p), ..
35522            } => format!("DECIMAL({})", p),
35523            DataType::Decimal { .. } => "DECIMAL".to_string(),
35524            DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
35525                "STRING".to_string()
35526            }
35527            DataType::Char { .. } => "STRING".to_string(),
35528            DataType::Boolean => "BOOLEAN".to_string(),
35529            DataType::Date => "DATE".to_string(),
35530            DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
35531            DataType::Json | DataType::JsonB => "STRING".to_string(),
35532            DataType::Binary { .. } => "BINARY".to_string(),
35533            DataType::Array { element_type, .. } => {
35534                format!("ARRAY<{}>", Self::data_type_to_spark_string(element_type))
35535            }
35536            DataType::Map {
35537                key_type,
35538                value_type,
35539            } => format!(
35540                "MAP<{}, {}>",
35541                Self::data_type_to_spark_string(key_type),
35542                Self::data_type_to_spark_string(value_type)
35543            ),
35544            DataType::Struct { fields, .. } => {
35545                let field_strs: Vec<String> = fields
35546                    .iter()
35547                    .map(|f| {
35548                        if f.name.is_empty() {
35549                            Self::data_type_to_spark_string(&f.data_type)
35550                        } else {
35551                            format!(
35552                                "{}: {}",
35553                                f.name,
35554                                Self::data_type_to_spark_string(&f.data_type)
35555                            )
35556                        }
35557                    })
35558                    .collect();
35559                format!("STRUCT<{}>", field_strs.join(", "))
35560            }
35561            DataType::Custom { name } => name.clone(),
35562            _ => format!("{:?}", dt),
35563        }
35564    }
35565
35566    fn cast_expr(this: Expression, to: DataType) -> Expression {
35567        Expression::Cast(Box::new(Cast {
35568            this,
35569            to,
35570            trailing_comments: Vec::new(),
35571            double_colon_syntax: false,
35572            format: None,
35573            default: None,
35574            inferred_type: None,
35575        }))
35576    }
35577
35578    fn lower_expr(this: Expression) -> Expression {
35579        Expression::Function(Box::new(Function::new("LOWER".to_string(), vec![this])))
35580    }
35581
35582    fn build_tsql_regex_patindex_predicate(
35583        this: Expression,
35584        pattern: Expression,
35585        case_insensitive: bool,
35586    ) -> Expression {
35587        let (this, pattern) = if case_insensitive {
35588            (Self::lower_expr(this), Self::lower_expr(pattern))
35589        } else {
35590            (this, pattern)
35591        };
35592        let patindex = Expression::Function(Box::new(Function::new(
35593            "PATINDEX".to_string(),
35594            vec![pattern, this],
35595        )));
35596
35597        Expression::Gt(Box::new(BinaryOp::new(patindex, Expression::number(0))))
35598    }
35599
35600    fn similar_to_can_lower_to_tsql_like(f: &crate::expressions::SimilarToExpr) -> bool {
35601        match &f.pattern {
35602            Expression::Literal(literal) if literal.is_string() => {
35603                Self::similar_to_literal_pattern_is_like_compatible(literal.value_str())
35604            }
35605            _ => false,
35606        }
35607    }
35608
35609    fn similar_to_literal_pattern_is_like_compatible(pattern: &str) -> bool {
35610        if pattern.contains('\\') {
35611            return false;
35612        }
35613
35614        !pattern
35615            .chars()
35616            .any(|ch| matches!(ch, '|' | '*' | '+' | '?' | '{' | '}' | '(' | ')'))
35617    }
35618
35619    fn build_json_object_from_pairs(
35620        args: Vec<Expression>,
35621    ) -> Option<Box<crate::expressions::JsonObjectFunc>> {
35622        if args.len() % 2 != 0 {
35623            return None;
35624        }
35625
35626        let mut pairs = Vec::with_capacity(args.len() / 2);
35627        let mut iter = args.into_iter();
35628        while let Some(key) = iter.next() {
35629            let value = iter.next()?;
35630            pairs.push((key, value));
35631        }
35632
35633        Some(Box::new(crate::expressions::JsonObjectFunc {
35634            pairs,
35635            null_handling: None,
35636            with_unique_keys: false,
35637            returning_type: None,
35638            format_json: false,
35639            encoding: None,
35640            star: false,
35641        }))
35642    }
35643
35644    fn build_tsql_div_func(left: Expression, right: Expression, target: DialectType) -> Expression {
35645        let cast_left = Self::cast_expr(
35646            left,
35647            DataType::Double {
35648                precision: None,
35649                scale: None,
35650            },
35651        );
35652        let divided = Expression::Div(Box::new(BinaryOp::new(cast_left, right)));
35653        let cast_int = Self::cast_expr(
35654            divided,
35655            DataType::Int {
35656                length: None,
35657                integer_spelling: true,
35658            },
35659        );
35660        let numeric_type = match target {
35661            DialectType::Fabric => DataType::Custom {
35662                name: "DECIMAL".to_string(),
35663            },
35664            _ => DataType::Custom {
35665                name: "NUMERIC".to_string(),
35666            },
35667        };
35668        Self::cast_expr(cast_int, numeric_type)
35669    }
35670
35671    fn build_tsql_cbrt_power(this: Expression) -> Expression {
35672        let base = Self::cast_expr(
35673            this,
35674            DataType::Double {
35675                precision: None,
35676                scale: None,
35677            },
35678        );
35679        let exponent = Expression::Div(Box::new(BinaryOp::new(
35680            Expression::Literal(Box::new(Literal::Number("1.0".to_string()))),
35681            Expression::Literal(Box::new(Literal::Number("3.0".to_string()))),
35682        )));
35683
35684        Expression::Power(Box::new(crate::expressions::BinaryFunc {
35685            this: base,
35686            expression: exponent,
35687            original_name: None,
35688            inferred_type: None,
35689        }))
35690    }
35691
35692    /// Extract value and unit from an Interval expression
35693    /// Returns (value_expression, IntervalUnit)
35694    fn extract_interval_parts(
35695        interval_expr: &Expression,
35696    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
35697        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
35698
35699        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
35700            match unit.trim().to_ascii_uppercase().as_str() {
35701                "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
35702                "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
35703                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
35704                "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
35705                    Some(IntervalUnit::Week)
35706                }
35707                "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
35708                "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
35709                "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
35710                "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
35711                "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
35712                | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
35713                    Some(IntervalUnit::Millisecond)
35714                }
35715                "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
35716                | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
35717                "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
35718                | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
35719                _ => None,
35720            }
35721        }
35722
35723        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
35724            let mut parts = s.split_whitespace();
35725            let value = parts.next()?;
35726            let unit = unit_from_str(parts.next()?)?;
35727            Some((
35728                Expression::Literal(Box::new(Literal::String(value.to_string()))),
35729                unit,
35730            ))
35731        }
35732
35733        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
35734            match unit {
35735                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
35736                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
35737                    Expression::Day(_) => Some(IntervalUnit::Day),
35738                    Expression::Month(_) => Some(IntervalUnit::Month),
35739                    Expression::Year(_) => Some(IntervalUnit::Year),
35740                    Expression::Identifier(id) => unit_from_str(&id.name),
35741                    Expression::Var(v) => unit_from_str(&v.this),
35742                    Expression::Column(col) => unit_from_str(&col.name.name),
35743                    _ => None,
35744                },
35745                _ => None,
35746            }
35747        }
35748
35749        match interval_expr {
35750            Expression::Interval(iv) => {
35751                let val = iv.this.clone().unwrap_or(Expression::number(0));
35752                if let Expression::Literal(lit) = &val {
35753                    if let Literal::String(s) = lit.as_ref() {
35754                        if let Some(parts) = parts_from_literal_string(s) {
35755                            return Some(parts);
35756                        }
35757                    }
35758                }
35759                let unit = iv
35760                    .unit
35761                    .as_ref()
35762                    .and_then(unit_from_spec)
35763                    .unwrap_or(IntervalUnit::Day);
35764                Some((val, unit))
35765            }
35766            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
35767                if let Expression::Literal(lit) = &cast.this {
35768                    if let Literal::String(s) = lit.as_ref() {
35769                        if let Some(parts) = parts_from_literal_string(s) {
35770                            return Some(parts);
35771                        }
35772                    }
35773                }
35774                let unit = match &cast.to {
35775                    DataType::Interval {
35776                        unit: Some(unit), ..
35777                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
35778                    _ => IntervalUnit::Day,
35779                };
35780                Some((cast.this.clone(), unit))
35781            }
35782            _ => None,
35783        }
35784    }
35785
35786    fn data_type_is_interval(dt: &DataType) -> bool {
35787        match dt {
35788            DataType::Interval { .. } => true,
35789            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
35790            _ => false,
35791        }
35792    }
35793
35794    fn node_is_interval_cast(node: &Expression) -> bool {
35795        match node {
35796            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
35797                Self::data_type_is_interval(&c.to)
35798            }
35799            _ => false,
35800        }
35801    }
35802
35803    fn reject_tsql_interval_casts(
35804        expr: &Expression,
35805        target: DialectType,
35806        opts: &TranspileOptions,
35807    ) -> Result<()> {
35808        if !matches!(
35809            opts.unsupported_level,
35810            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
35811        ) {
35812            return Ok(());
35813        }
35814
35815        if expr.dfs().any(Self::node_is_interval_cast) {
35816            return Err(crate::error::Error::unsupported(
35817                "INTERVAL casts",
35818                target.to_string(),
35819            ));
35820        }
35821
35822        Ok(())
35823    }
35824
35825    fn tsql_varchar_max_type() -> DataType {
35826        DataType::Custom {
35827            name: "VARCHAR(MAX)".to_string(),
35828        }
35829    }
35830
35831    fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
35832        transform_recursive(expr, &|e| match e {
35833            Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35834                cast.to = Self::tsql_varchar_max_type();
35835                cast.double_colon_syntax = false;
35836                Ok(Expression::Cast(cast))
35837            }
35838            Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35839                cast.to = Self::tsql_varchar_max_type();
35840                cast.double_colon_syntax = false;
35841                Ok(Expression::TryCast(cast))
35842            }
35843            Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
35844                cast.to = Self::tsql_varchar_max_type();
35845                cast.double_colon_syntax = false;
35846                Ok(Expression::SafeCast(cast))
35847            }
35848            _ => Ok(e),
35849        })
35850    }
35851
35852    fn rewrite_tsql_interval_arithmetic(
35853        expr: &Expression,
35854        source: DialectType,
35855    ) -> Option<Expression> {
35856        match expr {
35857            Expression::Add(op) => {
35858                if Self::extract_interval_parts(&op.right).is_some() {
35859                    return Some(Self::build_tsql_dateadd_from_interval(
35860                        op.left.clone(),
35861                        &op.right,
35862                        false,
35863                    ));
35864                }
35865
35866                if Self::is_postgres_family_source(source) {
35867                    if Self::is_explicit_date_expr(&op.left)
35868                        && Self::is_integer_day_offset_expr(&op.right)
35869                    {
35870                        return Some(Self::build_tsql_dateadd_days(
35871                            op.left.clone(),
35872                            op.right.clone(),
35873                            false,
35874                        ));
35875                    }
35876
35877                    if Self::is_integer_day_offset_expr(&op.left)
35878                        && Self::is_explicit_date_expr(&op.right)
35879                    {
35880                        return Some(Self::build_tsql_dateadd_days(
35881                            op.right.clone(),
35882                            op.left.clone(),
35883                            false,
35884                        ));
35885                    }
35886                }
35887
35888                None
35889            }
35890            Expression::Sub(op) => {
35891                if Self::extract_interval_parts(&op.right).is_some() {
35892                    return Some(Self::build_tsql_dateadd_from_interval(
35893                        op.left.clone(),
35894                        &op.right,
35895                        true,
35896                    ));
35897                }
35898
35899                if Self::is_postgres_family_source(source) {
35900                    if Self::is_explicit_date_expr(&op.left)
35901                        && Self::is_explicit_date_expr(&op.right)
35902                    {
35903                        return Some(Self::build_tsql_datediff_days(
35904                            op.right.clone(),
35905                            op.left.clone(),
35906                        ));
35907                    }
35908
35909                    if Self::is_explicit_date_expr(&op.left)
35910                        && Self::is_integer_day_offset_expr(&op.right)
35911                    {
35912                        return Some(Self::build_tsql_dateadd_days(
35913                            op.left.clone(),
35914                            op.right.clone(),
35915                            true,
35916                        ));
35917                    }
35918                }
35919
35920                None
35921            }
35922            _ => None,
35923        }
35924    }
35925
35926    fn is_postgres_family_source(source: DialectType) -> bool {
35927        matches!(
35928            source,
35929            DialectType::PostgreSQL
35930                | DialectType::Redshift
35931                | DialectType::Materialize
35932                | DialectType::RisingWave
35933                | DialectType::CockroachDB
35934        )
35935    }
35936
35937    fn is_explicit_date_expr(expr: &Expression) -> bool {
35938        use crate::expressions::Literal;
35939
35940        match expr {
35941            Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
35942            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
35943                matches!(c.to, crate::expressions::DataType::Date)
35944            }
35945            Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
35946            Expression::CurrentDate(_)
35947            | Expression::Date(_)
35948            | Expression::MakeDate(_)
35949            | Expression::ToDate(_)
35950            | Expression::DateStrToDate(_) => true,
35951            _ => false,
35952        }
35953    }
35954
35955    fn is_integer_day_offset_expr(expr: &Expression) -> bool {
35956        use crate::expressions::Literal;
35957
35958        match expr {
35959            Expression::Literal(lit) => match lit.as_ref() {
35960                Literal::Number(n) => n.parse::<i64>().is_ok(),
35961                _ => false,
35962            },
35963            Expression::Parameter(_) | Expression::Placeholder(_) => true,
35964            Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
35965            Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
35966            _ => false,
35967        }
35968    }
35969
35970    fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
35971        Expression::Function(Box::new(Function::new(
35972            "DATEDIFF".to_string(),
35973            vec![Expression::Identifier(Identifier::new("DAY")), start, end],
35974        )))
35975    }
35976
35977    fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
35978        Expression::Function(Box::new(Function::new(
35979            "DATEADD".to_string(),
35980            vec![
35981                Expression::Identifier(Identifier::new("DAY")),
35982                Self::tsql_dateadd_amount(amount, subtract),
35983                date,
35984            ],
35985        )))
35986    }
35987
35988    fn build_tsql_dateadd_from_interval(
35989        date: Expression,
35990        interval: &Expression,
35991        subtract: bool,
35992    ) -> Expression {
35993        let (value, unit) = Self::extract_interval_parts(interval)
35994            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
35995        let unit = Self::interval_unit_to_string(&unit);
35996        let amount = Self::tsql_dateadd_amount(value, subtract);
35997
35998        Expression::Function(Box::new(Function::new(
35999            "DATEADD".to_string(),
36000            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
36001        )))
36002    }
36003
36004    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
36005        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
36006
36007        fn numeric_literal_value(value: &Expression) -> Option<&str> {
36008            match value {
36009                Expression::Literal(lit) => match lit.as_ref() {
36010                    crate::expressions::Literal::Number(n)
36011                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
36012                    _ => None,
36013                },
36014                _ => None,
36015            }
36016        }
36017
36018        fn colon_parameter(value: &Expression) -> Option<Expression> {
36019            let Expression::Literal(lit) = value else {
36020                return None;
36021            };
36022            let crate::expressions::Literal::String(s) = lit.as_ref() else {
36023                return None;
36024            };
36025            let name = s.strip_prefix(':')?;
36026            if name.is_empty()
36027                || !name
36028                    .chars()
36029                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
36030            {
36031                return None;
36032            }
36033
36034            Some(Expression::Parameter(Box::new(Parameter {
36035                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
36036                    None
36037                } else {
36038                    Some(name.to_string())
36039                },
36040                index: name.parse::<u32>().ok(),
36041                style: ParameterStyle::Colon,
36042                quoted: false,
36043                string_quoted: false,
36044                expression: None,
36045            })))
36046        }
36047
36048        let value = colon_parameter(&value).unwrap_or(value);
36049
36050        if let Some(n) = numeric_literal_value(&value) {
36051            if let Ok(parsed) = n.parse::<f64>() {
36052                let normalized = if negate { -parsed } else { parsed };
36053                let rendered = if normalized.fract() == 0.0 {
36054                    format!("{}", normalized as i64)
36055                } else {
36056                    normalized.to_string()
36057                };
36058                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
36059                    rendered,
36060                )));
36061            }
36062        }
36063
36064        if !negate {
36065            return value;
36066        }
36067
36068        match value {
36069            Expression::Neg(op) => op.this,
36070            other => Expression::Neg(Box::new(UnaryOp {
36071                this: other,
36072                inferred_type: None,
36073            })),
36074        }
36075    }
36076
36077    /// Normalize BigQuery-specific functions to standard forms that target dialects can handle
36078    fn normalize_bigquery_function(
36079        e: Expression,
36080        source: DialectType,
36081        target: DialectType,
36082    ) -> Result<Expression> {
36083        use crate::expressions::{BinaryOp, Cast, DataType, Function, Identifier, Literal, Paren};
36084
36085        let f = if let Expression::Function(f) = e {
36086            *f
36087        } else {
36088            return Ok(e);
36089        };
36090        let name = f.name.to_ascii_uppercase();
36091        let mut args = f.args;
36092
36093        /// Helper to extract unit string from an identifier, column, or literal expression
36094        fn get_unit_str(expr: &Expression) -> String {
36095            match expr {
36096                Expression::Identifier(id) => id.name.to_ascii_uppercase(),
36097                Expression::Var(v) => v.this.to_ascii_uppercase(),
36098                Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
36099                    let Literal::String(s) = lit.as_ref() else {
36100                        unreachable!()
36101                    };
36102                    s.to_ascii_uppercase()
36103                }
36104                Expression::Column(col) => col.name.name.to_ascii_uppercase(),
36105                // Handle WEEK(MONDAY), WEEK(SUNDAY) etc. which are parsed as Function("WEEK", [Column("MONDAY")])
36106                Expression::Function(f) => {
36107                    let base = f.name.to_ascii_uppercase();
36108                    if !f.args.is_empty() {
36109                        // e.g., WEEK(MONDAY) -> "WEEK(MONDAY)"
36110                        let inner = get_unit_str(&f.args[0]);
36111                        format!("{}({})", base, inner)
36112                    } else {
36113                        base
36114                    }
36115                }
36116                _ => "DAY".to_string(),
36117            }
36118        }
36119
36120        /// Parse unit string to IntervalUnit
36121        fn parse_interval_unit(s: &str) -> crate::expressions::IntervalUnit {
36122            match s {
36123                "YEAR" => crate::expressions::IntervalUnit::Year,
36124                "QUARTER" => crate::expressions::IntervalUnit::Quarter,
36125                "MONTH" => crate::expressions::IntervalUnit::Month,
36126                "WEEK" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
36127                "DAY" => crate::expressions::IntervalUnit::Day,
36128                "HOUR" => crate::expressions::IntervalUnit::Hour,
36129                "MINUTE" => crate::expressions::IntervalUnit::Minute,
36130                "SECOND" => crate::expressions::IntervalUnit::Second,
36131                "MILLISECOND" => crate::expressions::IntervalUnit::Millisecond,
36132                "MICROSECOND" => crate::expressions::IntervalUnit::Microsecond,
36133                _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
36134                _ => crate::expressions::IntervalUnit::Day,
36135            }
36136        }
36137
36138        match name.as_str() {
36139            // TIMESTAMP_DIFF(date1, date2, unit) -> TIMESTAMPDIFF(unit, date2, date1)
36140            // (BigQuery: result = date1 - date2, Standard: result = end - start)
36141            "TIMESTAMP_DIFF" | "DATETIME_DIFF" | "TIME_DIFF" if args.len() == 3 => {
36142                let date1 = args.remove(0);
36143                let date2 = args.remove(0);
36144                let unit_expr = args.remove(0);
36145                let unit_str = get_unit_str(&unit_expr);
36146
36147                if matches!(target, DialectType::BigQuery) {
36148                    // BigQuery -> BigQuery: just uppercase the unit
36149                    let unit = Expression::Identifier(Identifier::new(unit_str.clone()));
36150                    return Ok(Expression::Function(Box::new(Function::new(
36151                        f.name,
36152                        vec![date1, date2, unit],
36153                    ))));
36154                }
36155
36156                // For Snowflake: use TimestampDiff expression so it generates TIMESTAMPDIFF
36157                // (Function("TIMESTAMPDIFF") would be converted to DATEDIFF by Snowflake's function normalization)
36158                if matches!(target, DialectType::Snowflake) {
36159                    return Ok(Expression::TimestampDiff(Box::new(
36160                        crate::expressions::TimestampDiff {
36161                            this: Box::new(date2),
36162                            expression: Box::new(date1),
36163                            unit: Some(unit_str),
36164                        },
36165                    )));
36166                }
36167
36168                // For DuckDB: DATE_DIFF('UNIT', start, end) with proper CAST
36169                if matches!(target, DialectType::DuckDB) {
36170                    let (cast_d1, cast_d2) = if name == "TIME_DIFF" {
36171                        // CAST to TIME
36172                        let cast_fn = |e: Expression| -> Expression {
36173                            match e {
36174                                Expression::Literal(lit)
36175                                    if matches!(lit.as_ref(), Literal::String(_)) =>
36176                                {
36177                                    let Literal::String(s) = lit.as_ref() else {
36178                                        unreachable!()
36179                                    };
36180                                    Expression::Cast(Box::new(Cast {
36181                                        this: Expression::Literal(Box::new(Literal::String(
36182                                            s.clone(),
36183                                        ))),
36184                                        to: DataType::Custom {
36185                                            name: "TIME".to_string(),
36186                                        },
36187                                        trailing_comments: vec![],
36188                                        double_colon_syntax: false,
36189                                        format: None,
36190                                        default: None,
36191                                        inferred_type: None,
36192                                    }))
36193                                }
36194                                other => other,
36195                            }
36196                        };
36197                        (cast_fn(date1), cast_fn(date2))
36198                    } else if name == "DATETIME_DIFF" {
36199                        // CAST to TIMESTAMP
36200                        (
36201                            Self::ensure_cast_timestamp(date1),
36202                            Self::ensure_cast_timestamp(date2),
36203                        )
36204                    } else {
36205                        // TIMESTAMP_DIFF: CAST to TIMESTAMPTZ
36206                        (
36207                            Self::ensure_cast_timestamptz(date1),
36208                            Self::ensure_cast_timestamptz(date2),
36209                        )
36210                    };
36211                    return Ok(Expression::Function(Box::new(Function::new(
36212                        "DATE_DIFF".to_string(),
36213                        vec![
36214                            Expression::Literal(Box::new(Literal::String(unit_str))),
36215                            cast_d2,
36216                            cast_d1,
36217                        ],
36218                    ))));
36219                }
36220
36221                // Convert to standard TIMESTAMPDIFF(unit, start, end)
36222                let unit = Expression::Identifier(Identifier::new(unit_str));
36223                Ok(Expression::Function(Box::new(Function::new(
36224                    "TIMESTAMPDIFF".to_string(),
36225                    vec![unit, date2, date1],
36226                ))))
36227            }
36228
36229            // DATEDIFF(unit, start, end) -> target-specific form
36230            // Used by: Redshift, Snowflake, TSQL, Databricks, Spark
36231            "DATEDIFF" if args.len() == 3 => {
36232                let arg0 = args.remove(0);
36233                let arg1 = args.remove(0);
36234                let arg2 = args.remove(0);
36235                let unit_str = get_unit_str(&arg0);
36236
36237                // Redshift DATEDIFF(unit, start, end) order: result = end - start
36238                // Snowflake DATEDIFF(unit, start, end) order: result = end - start
36239                // TSQL DATEDIFF(unit, start, end) order: result = end - start
36240
36241                if matches!(target, DialectType::Snowflake) {
36242                    // Snowflake: DATEDIFF(UNIT, start, end) - uppercase unit
36243                    let unit = Expression::Identifier(Identifier::new(unit_str));
36244                    return Ok(Expression::Function(Box::new(Function::new(
36245                        "DATEDIFF".to_string(),
36246                        vec![unit, arg1, arg2],
36247                    ))));
36248                }
36249
36250                if matches!(target, DialectType::DuckDB) {
36251                    // DuckDB: DATE_DIFF('UNIT', start, end) with CAST
36252                    let cast_d1 = Self::ensure_cast_timestamp(arg1);
36253                    let cast_d2 = Self::ensure_cast_timestamp(arg2);
36254                    return Ok(Expression::Function(Box::new(Function::new(
36255                        "DATE_DIFF".to_string(),
36256                        vec![
36257                            Expression::Literal(Box::new(Literal::String(unit_str))),
36258                            cast_d1,
36259                            cast_d2,
36260                        ],
36261                    ))));
36262                }
36263
36264                if matches!(target, DialectType::BigQuery) {
36265                    // BigQuery: DATE_DIFF(end_date, start_date, UNIT) - reversed args, CAST to DATETIME
36266                    let cast_d1 = Self::ensure_cast_datetime(arg1);
36267                    let cast_d2 = Self::ensure_cast_datetime(arg2);
36268                    let unit = Expression::Identifier(Identifier::new(unit_str));
36269                    return Ok(Expression::Function(Box::new(Function::new(
36270                        "DATE_DIFF".to_string(),
36271                        vec![cast_d2, cast_d1, unit],
36272                    ))));
36273                }
36274
36275                if matches!(target, DialectType::Spark | DialectType::Databricks) {
36276                    // Spark/Databricks: DATEDIFF(UNIT, start, end) - uppercase unit
36277                    let unit = Expression::Identifier(Identifier::new(unit_str));
36278                    return Ok(Expression::Function(Box::new(Function::new(
36279                        "DATEDIFF".to_string(),
36280                        vec![unit, arg1, arg2],
36281                    ))));
36282                }
36283
36284                if matches!(target, DialectType::Hive) {
36285                    // Hive: DATEDIFF(end, start) for DAY only, use MONTHS_BETWEEN for MONTH
36286                    match unit_str.as_str() {
36287                        "MONTH" => {
36288                            return Ok(Expression::Function(Box::new(Function::new(
36289                                "CAST".to_string(),
36290                                vec![Expression::Function(Box::new(Function::new(
36291                                    "MONTHS_BETWEEN".to_string(),
36292                                    vec![arg2, arg1],
36293                                )))],
36294                            ))));
36295                        }
36296                        "WEEK" => {
36297                            return Ok(Expression::Cast(Box::new(Cast {
36298                                this: Expression::Div(Box::new(crate::expressions::BinaryOp::new(
36299                                    Expression::Function(Box::new(Function::new(
36300                                        "DATEDIFF".to_string(),
36301                                        vec![arg2, arg1],
36302                                    ))),
36303                                    Expression::Literal(Box::new(Literal::Number("7".to_string()))),
36304                                ))),
36305                                to: DataType::Int {
36306                                    length: None,
36307                                    integer_spelling: false,
36308                                },
36309                                trailing_comments: vec![],
36310                                double_colon_syntax: false,
36311                                format: None,
36312                                default: None,
36313                                inferred_type: None,
36314                            })));
36315                        }
36316                        _ => {
36317                            // Default: DATEDIFF(end, start) for DAY
36318                            return Ok(Expression::Function(Box::new(Function::new(
36319                                "DATEDIFF".to_string(),
36320                                vec![arg2, arg1],
36321                            ))));
36322                        }
36323                    }
36324                }
36325
36326                if matches!(
36327                    target,
36328                    DialectType::Presto | DialectType::Trino | DialectType::Athena
36329                ) {
36330                    // Presto/Trino: DATE_DIFF('UNIT', start, end)
36331                    return Ok(Expression::Function(Box::new(Function::new(
36332                        "DATE_DIFF".to_string(),
36333                        vec![
36334                            Expression::Literal(Box::new(Literal::String(unit_str))),
36335                            arg1,
36336                            arg2,
36337                        ],
36338                    ))));
36339                }
36340
36341                if matches!(target, DialectType::TSQL) {
36342                    // TSQL: DATEDIFF(UNIT, start, CAST(end AS DATETIME2))
36343                    let cast_d2 = Self::ensure_cast_datetime2(arg2);
36344                    let unit = Expression::Identifier(Identifier::new(unit_str));
36345                    return Ok(Expression::Function(Box::new(Function::new(
36346                        "DATEDIFF".to_string(),
36347                        vec![unit, arg1, cast_d2],
36348                    ))));
36349                }
36350
36351                if matches!(target, DialectType::PostgreSQL) {
36352                    // PostgreSQL doesn't have DATEDIFF - use date subtraction or EXTRACT
36353                    // For now, use DATEDIFF (passthrough) with uppercased unit
36354                    let unit = Expression::Identifier(Identifier::new(unit_str));
36355                    return Ok(Expression::Function(Box::new(Function::new(
36356                        "DATEDIFF".to_string(),
36357                        vec![unit, arg1, arg2],
36358                    ))));
36359                }
36360
36361                // Default: DATEDIFF(UNIT, start, end) with uppercase unit
36362                let unit = Expression::Identifier(Identifier::new(unit_str));
36363                Ok(Expression::Function(Box::new(Function::new(
36364                    "DATEDIFF".to_string(),
36365                    vec![unit, arg1, arg2],
36366                ))))
36367            }
36368
36369            // DATE_DIFF(date1, date2, unit) -> standard form
36370            "DATE_DIFF" if args.len() == 3 => {
36371                let date1 = args.remove(0);
36372                let date2 = args.remove(0);
36373                let unit_expr = args.remove(0);
36374                let unit_str = get_unit_str(&unit_expr);
36375
36376                if matches!(target, DialectType::BigQuery) {
36377                    // BigQuery -> BigQuery: just uppercase the unit, normalize WEEK(SUNDAY) -> WEEK
36378                    let norm_unit = if unit_str == "WEEK(SUNDAY)" {
36379                        "WEEK".to_string()
36380                    } else {
36381                        unit_str
36382                    };
36383                    let norm_d1 = Self::date_literal_to_cast(date1);
36384                    let norm_d2 = Self::date_literal_to_cast(date2);
36385                    let unit = Expression::Identifier(Identifier::new(norm_unit));
36386                    return Ok(Expression::Function(Box::new(Function::new(
36387                        f.name,
36388                        vec![norm_d1, norm_d2, unit],
36389                    ))));
36390                }
36391
36392                if matches!(target, DialectType::MySQL) {
36393                    // MySQL DATEDIFF only takes 2 args (date1, date2), returns day difference
36394                    let norm_d1 = Self::date_literal_to_cast(date1);
36395                    let norm_d2 = Self::date_literal_to_cast(date2);
36396                    return Ok(Expression::Function(Box::new(Function::new(
36397                        "DATEDIFF".to_string(),
36398                        vec![norm_d1, norm_d2],
36399                    ))));
36400                }
36401
36402                if matches!(target, DialectType::StarRocks) {
36403                    // StarRocks: DATE_DIFF('UNIT', date1, date2) - unit as string, args NOT swapped
36404                    let norm_d1 = Self::date_literal_to_cast(date1);
36405                    let norm_d2 = Self::date_literal_to_cast(date2);
36406                    return Ok(Expression::Function(Box::new(Function::new(
36407                        "DATE_DIFF".to_string(),
36408                        vec![
36409                            Expression::Literal(Box::new(Literal::String(unit_str))),
36410                            norm_d1,
36411                            norm_d2,
36412                        ],
36413                    ))));
36414                }
36415
36416                if matches!(target, DialectType::DuckDB) {
36417                    // DuckDB: DATE_DIFF('UNIT', date2, date1) with proper CAST for dates
36418                    let norm_d1 = Self::ensure_cast_date(date1);
36419                    let norm_d2 = Self::ensure_cast_date(date2);
36420
36421                    // Handle WEEK variants: WEEK(MONDAY)/WEEK(SUNDAY)/ISOWEEK/WEEK
36422                    let is_week_variant = unit_str == "WEEK"
36423                        || unit_str.starts_with("WEEK(")
36424                        || unit_str == "ISOWEEK";
36425                    if is_week_variant {
36426                        // For DuckDB, WEEK-based diffs use DATE_TRUNC approach
36427                        // WEEK(MONDAY) / ISOWEEK: DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2), DATE_TRUNC('WEEK', d1))
36428                        // WEEK / WEEK(SUNDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '1' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '1' DAY))
36429                        // WEEK(SATURDAY): DATE_DIFF('WEEK', DATE_TRUNC('WEEK', d2 + INTERVAL '-5' DAY), DATE_TRUNC('WEEK', d1 + INTERVAL '-5' DAY))
36430                        let day_offset = if unit_str == "WEEK(MONDAY)" || unit_str == "ISOWEEK" {
36431                            None // ISO weeks start on Monday, aligned with DATE_TRUNC('WEEK')
36432                        } else if unit_str == "WEEK" || unit_str == "WEEK(SUNDAY)" {
36433                            Some("1") // Shift Sunday to Monday alignment
36434                        } else if unit_str == "WEEK(SATURDAY)" {
36435                            Some("-5")
36436                        } else if unit_str == "WEEK(TUESDAY)" {
36437                            Some("-1")
36438                        } else if unit_str == "WEEK(WEDNESDAY)" {
36439                            Some("-2")
36440                        } else if unit_str == "WEEK(THURSDAY)" {
36441                            Some("-3")
36442                        } else if unit_str == "WEEK(FRIDAY)" {
36443                            Some("-4")
36444                        } else {
36445                            Some("1") // default to Sunday
36446                        };
36447
36448                        let make_trunc = |date: Expression, offset: Option<&str>| -> Expression {
36449                            let shifted = if let Some(off) = offset {
36450                                let interval =
36451                                    Expression::Interval(Box::new(crate::expressions::Interval {
36452                                        this: Some(Expression::Literal(Box::new(Literal::String(
36453                                            off.to_string(),
36454                                        )))),
36455                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36456                                            unit: crate::expressions::IntervalUnit::Day,
36457                                            use_plural: false,
36458                                        }),
36459                                    }));
36460                                Expression::Add(Box::new(crate::expressions::BinaryOp::new(
36461                                    date, interval,
36462                                )))
36463                            } else {
36464                                date
36465                            };
36466                            Expression::Function(Box::new(Function::new(
36467                                "DATE_TRUNC".to_string(),
36468                                vec![
36469                                    Expression::Literal(Box::new(Literal::String(
36470                                        "WEEK".to_string(),
36471                                    ))),
36472                                    shifted,
36473                                ],
36474                            )))
36475                        };
36476
36477                        let trunc_d2 = make_trunc(norm_d2, day_offset);
36478                        let trunc_d1 = make_trunc(norm_d1, day_offset);
36479                        return Ok(Expression::Function(Box::new(Function::new(
36480                            "DATE_DIFF".to_string(),
36481                            vec![
36482                                Expression::Literal(Box::new(Literal::String("WEEK".to_string()))),
36483                                trunc_d2,
36484                                trunc_d1,
36485                            ],
36486                        ))));
36487                    }
36488
36489                    return Ok(Expression::Function(Box::new(Function::new(
36490                        "DATE_DIFF".to_string(),
36491                        vec![
36492                            Expression::Literal(Box::new(Literal::String(unit_str))),
36493                            norm_d2,
36494                            norm_d1,
36495                        ],
36496                    ))));
36497                }
36498
36499                // Default: DATEDIFF(unit, date2, date1)
36500                let unit = Expression::Identifier(Identifier::new(unit_str));
36501                Ok(Expression::Function(Box::new(Function::new(
36502                    "DATEDIFF".to_string(),
36503                    vec![unit, date2, date1],
36504                ))))
36505            }
36506
36507            // TIMESTAMP_ADD(ts, INTERVAL n UNIT) -> target-specific
36508            "TIMESTAMP_ADD" | "DATETIME_ADD" | "TIME_ADD" if args.len() == 2 => {
36509                let ts = args.remove(0);
36510                let interval_expr = args.remove(0);
36511                let (val, unit) =
36512                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36513                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36514                    });
36515
36516                match target {
36517                    DialectType::Snowflake => {
36518                        // TIMESTAMPADD(UNIT, val, CAST(ts AS TIMESTAMPTZ))
36519                        // Use TimestampAdd expression so Snowflake generates TIMESTAMPADD
36520                        // (Function("TIMESTAMPADD") would be converted to DATEADD by Snowflake's function normalization)
36521                        let unit_str = Self::interval_unit_to_string(&unit);
36522                        let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
36523                        Ok(Expression::TimestampAdd(Box::new(
36524                            crate::expressions::TimestampAdd {
36525                                this: Box::new(val),
36526                                expression: Box::new(cast_ts),
36527                                unit: Some(unit_str.to_string()),
36528                            },
36529                        )))
36530                    }
36531                    DialectType::Spark | DialectType::Databricks => {
36532                        if name == "DATETIME_ADD" && matches!(target, DialectType::Spark) {
36533                            // Spark DATETIME_ADD: ts + INTERVAL val UNIT
36534                            let interval =
36535                                Expression::Interval(Box::new(crate::expressions::Interval {
36536                                    this: Some(val),
36537                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36538                                        unit,
36539                                        use_plural: false,
36540                                    }),
36541                                }));
36542                            Ok(Expression::Add(Box::new(
36543                                crate::expressions::BinaryOp::new(ts, interval),
36544                            )))
36545                        } else if name == "DATETIME_ADD"
36546                            && matches!(target, DialectType::Databricks)
36547                        {
36548                            // Databricks DATETIME_ADD: TIMESTAMPADD(UNIT, val, ts)
36549                            let unit_str = Self::interval_unit_to_string(&unit);
36550                            Ok(Expression::Function(Box::new(Function::new(
36551                                "TIMESTAMPADD".to_string(),
36552                                vec![Expression::Identifier(Identifier::new(unit_str)), val, ts],
36553                            ))))
36554                        } else {
36555                            // Presto-style: DATE_ADD('unit', val, CAST(ts AS TIMESTAMP))
36556                            let unit_str = Self::interval_unit_to_string(&unit);
36557                            let cast_ts =
36558                                if name.starts_with("TIMESTAMP") || name.starts_with("DATETIME") {
36559                                    Self::maybe_cast_ts(ts)
36560                                } else {
36561                                    ts
36562                                };
36563                            Ok(Expression::Function(Box::new(Function::new(
36564                                "DATE_ADD".to_string(),
36565                                vec![
36566                                    Expression::Identifier(Identifier::new(unit_str)),
36567                                    val,
36568                                    cast_ts,
36569                                ],
36570                            ))))
36571                        }
36572                    }
36573                    DialectType::MySQL => {
36574                        // DATE_ADD(TIMESTAMP(ts), INTERVAL val UNIT) for MySQL
36575                        let mysql_ts = if name.starts_with("TIMESTAMP") {
36576                            // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
36577                            match &ts {
36578                                Expression::Function(ref inner_f)
36579                                    if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
36580                                {
36581                                    // Already wrapped, keep as-is
36582                                    ts
36583                                }
36584                                _ => {
36585                                    // Unwrap typed literals: TIMESTAMP '...' -> '...' for TIMESTAMP() wrapper
36586                                    let unwrapped = match ts {
36587                                        Expression::Literal(lit)
36588                                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
36589                                        {
36590                                            let Literal::Timestamp(s) = lit.as_ref() else {
36591                                                unreachable!()
36592                                            };
36593                                            Expression::Literal(Box::new(Literal::String(
36594                                                s.clone(),
36595                                            )))
36596                                        }
36597                                        other => other,
36598                                    };
36599                                    Expression::Function(Box::new(Function::new(
36600                                        "TIMESTAMP".to_string(),
36601                                        vec![unwrapped],
36602                                    )))
36603                                }
36604                            }
36605                        } else {
36606                            ts
36607                        };
36608                        Ok(Expression::DateAdd(Box::new(
36609                            crate::expressions::DateAddFunc {
36610                                this: mysql_ts,
36611                                interval: val,
36612                                unit,
36613                            },
36614                        )))
36615                    }
36616                    _ => {
36617                        // DuckDB and others use DateAdd expression (DuckDB converts to + INTERVAL)
36618                        let cast_ts = if matches!(target, DialectType::DuckDB) {
36619                            if name == "DATETIME_ADD" {
36620                                Self::ensure_cast_timestamp(ts)
36621                            } else if name.starts_with("TIMESTAMP") {
36622                                Self::maybe_cast_ts_to_tz(ts, &name)
36623                            } else {
36624                                ts
36625                            }
36626                        } else {
36627                            ts
36628                        };
36629                        Ok(Expression::DateAdd(Box::new(
36630                            crate::expressions::DateAddFunc {
36631                                this: cast_ts,
36632                                interval: val,
36633                                unit,
36634                            },
36635                        )))
36636                    }
36637                }
36638            }
36639
36640            // TIMESTAMP_SUB(ts, INTERVAL n UNIT) -> target-specific
36641            "TIMESTAMP_SUB" | "DATETIME_SUB" | "TIME_SUB" if args.len() == 2 => {
36642                let ts = args.remove(0);
36643                let interval_expr = args.remove(0);
36644                let (val, unit) =
36645                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36646                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36647                    });
36648
36649                match target {
36650                    DialectType::Snowflake => {
36651                        // TIMESTAMPADD(UNIT, val * -1, CAST(ts AS TIMESTAMPTZ))
36652                        let unit_str = Self::interval_unit_to_string(&unit);
36653                        let cast_ts = Self::maybe_cast_ts_to_tz(ts, &name);
36654                        let neg_val = Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36655                            val,
36656                            Expression::Neg(Box::new(crate::expressions::UnaryOp {
36657                                this: Expression::number(1),
36658                                inferred_type: None,
36659                            })),
36660                        )));
36661                        Ok(Expression::TimestampAdd(Box::new(
36662                            crate::expressions::TimestampAdd {
36663                                this: Box::new(neg_val),
36664                                expression: Box::new(cast_ts),
36665                                unit: Some(unit_str.to_string()),
36666                            },
36667                        )))
36668                    }
36669                    DialectType::Spark | DialectType::Databricks => {
36670                        if (name == "DATETIME_SUB" && matches!(target, DialectType::Spark))
36671                            || (name == "TIMESTAMP_SUB" && matches!(target, DialectType::Spark))
36672                        {
36673                            // Spark: ts - INTERVAL val UNIT
36674                            let cast_ts = if name.starts_with("TIMESTAMP") {
36675                                Self::maybe_cast_ts(ts)
36676                            } else {
36677                                ts
36678                            };
36679                            let interval =
36680                                Expression::Interval(Box::new(crate::expressions::Interval {
36681                                    this: Some(val),
36682                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36683                                        unit,
36684                                        use_plural: false,
36685                                    }),
36686                                }));
36687                            Ok(Expression::Sub(Box::new(
36688                                crate::expressions::BinaryOp::new(cast_ts, interval),
36689                            )))
36690                        } else {
36691                            // Databricks: TIMESTAMPADD(UNIT, val * -1, ts)
36692                            let unit_str = Self::interval_unit_to_string(&unit);
36693                            let neg_val =
36694                                Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36695                                    val,
36696                                    Expression::Neg(Box::new(crate::expressions::UnaryOp {
36697                                        this: Expression::number(1),
36698                                        inferred_type: None,
36699                                    })),
36700                                )));
36701                            Ok(Expression::Function(Box::new(Function::new(
36702                                "TIMESTAMPADD".to_string(),
36703                                vec![
36704                                    Expression::Identifier(Identifier::new(unit_str)),
36705                                    neg_val,
36706                                    ts,
36707                                ],
36708                            ))))
36709                        }
36710                    }
36711                    DialectType::MySQL => {
36712                        let mysql_ts = if name.starts_with("TIMESTAMP") {
36713                            // Check if already wrapped in TIMESTAMP() function (from cross-dialect normalization)
36714                            match &ts {
36715                                Expression::Function(ref inner_f)
36716                                    if inner_f.name.eq_ignore_ascii_case("TIMESTAMP") =>
36717                                {
36718                                    // Already wrapped, keep as-is
36719                                    ts
36720                                }
36721                                _ => {
36722                                    let unwrapped = match ts {
36723                                        Expression::Literal(lit)
36724                                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
36725                                        {
36726                                            let Literal::Timestamp(s) = lit.as_ref() else {
36727                                                unreachable!()
36728                                            };
36729                                            Expression::Literal(Box::new(Literal::String(
36730                                                s.clone(),
36731                                            )))
36732                                        }
36733                                        other => other,
36734                                    };
36735                                    Expression::Function(Box::new(Function::new(
36736                                        "TIMESTAMP".to_string(),
36737                                        vec![unwrapped],
36738                                    )))
36739                                }
36740                            }
36741                        } else {
36742                            ts
36743                        };
36744                        Ok(Expression::DateSub(Box::new(
36745                            crate::expressions::DateAddFunc {
36746                                this: mysql_ts,
36747                                interval: val,
36748                                unit,
36749                            },
36750                        )))
36751                    }
36752                    _ => {
36753                        let cast_ts = if matches!(target, DialectType::DuckDB) {
36754                            if name == "DATETIME_SUB" {
36755                                Self::ensure_cast_timestamp(ts)
36756                            } else if name.starts_with("TIMESTAMP") {
36757                                Self::maybe_cast_ts_to_tz(ts, &name)
36758                            } else {
36759                                ts
36760                            }
36761                        } else {
36762                            ts
36763                        };
36764                        Ok(Expression::DateSub(Box::new(
36765                            crate::expressions::DateAddFunc {
36766                                this: cast_ts,
36767                                interval: val,
36768                                unit,
36769                            },
36770                        )))
36771                    }
36772                }
36773            }
36774
36775            // DATE_SUB(date, INTERVAL n UNIT) -> target-specific
36776            "DATE_SUB" if args.len() == 2 => {
36777                let date = args.remove(0);
36778                let interval_expr = args.remove(0);
36779                let (val, unit) =
36780                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
36781                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
36782                    });
36783
36784                match target {
36785                    DialectType::Databricks | DialectType::Spark => {
36786                        // Databricks/Spark: DATE_ADD(date, -val)
36787                        // Use DateAdd expression with negative val so it generates correctly
36788                        // The generator will output DATE_ADD(date, INTERVAL -val DAY)
36789                        // Then Databricks transform converts 2-arg DATE_ADD(date, interval) to DATEADD(DAY, interval, date)
36790                        // Instead, we directly output as a simple negated DateSub
36791                        Ok(Expression::DateSub(Box::new(
36792                            crate::expressions::DateAddFunc {
36793                                this: date,
36794                                interval: val,
36795                                unit,
36796                            },
36797                        )))
36798                    }
36799                    DialectType::DuckDB => {
36800                        // DuckDB: CAST(date AS DATE) - INTERVAL 'val' UNIT
36801                        let cast_date = Self::ensure_cast_date(date);
36802                        let interval =
36803                            Expression::Interval(Box::new(crate::expressions::Interval {
36804                                this: Some(val),
36805                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36806                                    unit,
36807                                    use_plural: false,
36808                                }),
36809                            }));
36810                        Ok(Expression::Sub(Box::new(
36811                            crate::expressions::BinaryOp::new(cast_date, interval),
36812                        )))
36813                    }
36814                    DialectType::Snowflake => {
36815                        // Snowflake: Let Snowflake's own DateSub -> DATEADD(UNIT, val * -1, date) handler work
36816                        // Just ensure the date is cast properly
36817                        let cast_date = Self::ensure_cast_date(date);
36818                        Ok(Expression::DateSub(Box::new(
36819                            crate::expressions::DateAddFunc {
36820                                this: cast_date,
36821                                interval: val,
36822                                unit,
36823                            },
36824                        )))
36825                    }
36826                    DialectType::PostgreSQL => {
36827                        // PostgreSQL: date - INTERVAL 'val UNIT'
36828                        let unit_str = Self::interval_unit_to_string(&unit);
36829                        let interval =
36830                            Expression::Interval(Box::new(crate::expressions::Interval {
36831                                this: Some(Expression::Literal(Box::new(Literal::String(
36832                                    format!("{} {}", Self::expr_to_string(&val), unit_str),
36833                                )))),
36834                                unit: None,
36835                            }));
36836                        Ok(Expression::Sub(Box::new(
36837                            crate::expressions::BinaryOp::new(date, interval),
36838                        )))
36839                    }
36840                    _ => Ok(Expression::DateSub(Box::new(
36841                        crate::expressions::DateAddFunc {
36842                            this: date,
36843                            interval: val,
36844                            unit,
36845                        },
36846                    ))),
36847                }
36848            }
36849
36850            // DATEADD(unit, val, date) -> target-specific form
36851            // Used by: Redshift, Snowflake, TSQL, ClickHouse
36852            "DATEADD" if args.len() == 3 => {
36853                let arg0 = args.remove(0);
36854                let arg1 = args.remove(0);
36855                let arg2 = args.remove(0);
36856                let unit_str = get_unit_str(&arg0);
36857
36858                if matches!(target, DialectType::Snowflake | DialectType::TSQL) {
36859                    // Keep DATEADD(UNIT, val, date) with uppercased unit
36860                    let unit = Expression::Identifier(Identifier::new(unit_str));
36861                    // Only CAST to DATETIME2 for TSQL target when source is NOT Spark/Databricks family
36862                    let date = if matches!(target, DialectType::TSQL)
36863                        && !matches!(
36864                            source,
36865                            DialectType::Spark | DialectType::Databricks | DialectType::Hive
36866                        ) {
36867                        Self::ensure_cast_datetime2(arg2)
36868                    } else {
36869                        arg2
36870                    };
36871                    return Ok(Expression::Function(Box::new(Function::new(
36872                        "DATEADD".to_string(),
36873                        vec![unit, arg1, date],
36874                    ))));
36875                }
36876
36877                if matches!(target, DialectType::DuckDB) {
36878                    // DuckDB: date + INTERVAL 'val' UNIT
36879                    let iu = parse_interval_unit(&unit_str);
36880                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
36881                        this: Some(arg1),
36882                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36883                            unit: iu,
36884                            use_plural: false,
36885                        }),
36886                    }));
36887                    let cast_date = Self::ensure_cast_timestamp(arg2);
36888                    return Ok(Expression::Add(Box::new(
36889                        crate::expressions::BinaryOp::new(cast_date, interval),
36890                    )));
36891                }
36892
36893                if matches!(target, DialectType::BigQuery) {
36894                    // BigQuery: DATE_ADD(date, INTERVAL val UNIT) or TIMESTAMP_ADD(ts, INTERVAL val UNIT)
36895                    let iu = parse_interval_unit(&unit_str);
36896                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
36897                        this: Some(arg1),
36898                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
36899                            unit: iu,
36900                            use_plural: false,
36901                        }),
36902                    }));
36903                    return Ok(Expression::Function(Box::new(Function::new(
36904                        "DATE_ADD".to_string(),
36905                        vec![arg2, interval],
36906                    ))));
36907                }
36908
36909                if matches!(target, DialectType::Databricks) {
36910                    // Databricks: keep DATEADD(UNIT, val, date) format
36911                    let unit = Expression::Identifier(Identifier::new(unit_str));
36912                    return Ok(Expression::Function(Box::new(Function::new(
36913                        "DATEADD".to_string(),
36914                        vec![unit, arg1, arg2],
36915                    ))));
36916                }
36917
36918                if matches!(target, DialectType::Spark) {
36919                    // Spark: convert month-based units to ADD_MONTHS, rest to DATE_ADD
36920                    fn multiply_expr_dateadd(expr: Expression, factor: i64) -> Expression {
36921                        if let Expression::Literal(lit) = &expr {
36922                            if let crate::expressions::Literal::Number(n) = lit.as_ref() {
36923                                if let Ok(val) = n.parse::<i64>() {
36924                                    return Expression::Literal(Box::new(
36925                                        crate::expressions::Literal::Number(
36926                                            (val * factor).to_string(),
36927                                        ),
36928                                    ));
36929                                }
36930                            }
36931                        }
36932                        Expression::Mul(Box::new(crate::expressions::BinaryOp::new(
36933                            expr,
36934                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
36935                                factor.to_string(),
36936                            ))),
36937                        )))
36938                    }
36939                    match unit_str.as_str() {
36940                        "YEAR" => {
36941                            let months = multiply_expr_dateadd(arg1, 12);
36942                            return Ok(Expression::Function(Box::new(Function::new(
36943                                "ADD_MONTHS".to_string(),
36944                                vec![arg2, months],
36945                            ))));
36946                        }
36947                        "QUARTER" => {
36948                            let months = multiply_expr_dateadd(arg1, 3);
36949                            return Ok(Expression::Function(Box::new(Function::new(
36950                                "ADD_MONTHS".to_string(),
36951                                vec![arg2, months],
36952                            ))));
36953                        }
36954                        "MONTH" => {
36955                            return Ok(Expression::Function(Box::new(Function::new(
36956                                "ADD_MONTHS".to_string(),
36957                                vec![arg2, arg1],
36958                            ))));
36959                        }
36960                        "WEEK" => {
36961                            let days = multiply_expr_dateadd(arg1, 7);
36962                            return Ok(Expression::Function(Box::new(Function::new(
36963                                "DATE_ADD".to_string(),
36964                                vec![arg2, days],
36965                            ))));
36966                        }
36967                        "DAY" => {
36968                            return Ok(Expression::Function(Box::new(Function::new(
36969                                "DATE_ADD".to_string(),
36970                                vec![arg2, arg1],
36971                            ))));
36972                        }
36973                        _ => {
36974                            let unit = Expression::Identifier(Identifier::new(unit_str));
36975                            return Ok(Expression::Function(Box::new(Function::new(
36976                                "DATE_ADD".to_string(),
36977                                vec![unit, arg1, arg2],
36978                            ))));
36979                        }
36980                    }
36981                }
36982
36983                if matches!(target, DialectType::Hive) {
36984                    // Hive: DATE_ADD(date, val) for DAY, or date + INTERVAL for others
36985                    match unit_str.as_str() {
36986                        "DAY" => {
36987                            return Ok(Expression::Function(Box::new(Function::new(
36988                                "DATE_ADD".to_string(),
36989                                vec![arg2, arg1],
36990                            ))));
36991                        }
36992                        "MONTH" => {
36993                            return Ok(Expression::Function(Box::new(Function::new(
36994                                "ADD_MONTHS".to_string(),
36995                                vec![arg2, arg1],
36996                            ))));
36997                        }
36998                        _ => {
36999                            let iu = parse_interval_unit(&unit_str);
37000                            let interval =
37001                                Expression::Interval(Box::new(crate::expressions::Interval {
37002                                    this: Some(arg1),
37003                                    unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37004                                        unit: iu,
37005                                        use_plural: false,
37006                                    }),
37007                                }));
37008                            return Ok(Expression::Add(Box::new(
37009                                crate::expressions::BinaryOp::new(arg2, interval),
37010                            )));
37011                        }
37012                    }
37013                }
37014
37015                if matches!(target, DialectType::PostgreSQL) {
37016                    // PostgreSQL: date + INTERVAL 'val UNIT'
37017                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37018                        this: Some(Expression::Literal(Box::new(Literal::String(format!(
37019                            "{} {}",
37020                            Self::expr_to_string(&arg1),
37021                            unit_str
37022                        ))))),
37023                        unit: None,
37024                    }));
37025                    return Ok(Expression::Add(Box::new(
37026                        crate::expressions::BinaryOp::new(arg2, interval),
37027                    )));
37028                }
37029
37030                if matches!(
37031                    target,
37032                    DialectType::Presto | DialectType::Trino | DialectType::Athena
37033                ) {
37034                    // Presto/Trino: DATE_ADD('UNIT', val, date)
37035                    return Ok(Expression::Function(Box::new(Function::new(
37036                        "DATE_ADD".to_string(),
37037                        vec![
37038                            Expression::Literal(Box::new(Literal::String(unit_str))),
37039                            arg1,
37040                            arg2,
37041                        ],
37042                    ))));
37043                }
37044
37045                if matches!(target, DialectType::ClickHouse) {
37046                    // ClickHouse: DATE_ADD(UNIT, val, date)
37047                    let unit = Expression::Identifier(Identifier::new(unit_str));
37048                    return Ok(Expression::Function(Box::new(Function::new(
37049                        "DATE_ADD".to_string(),
37050                        vec![unit, arg1, arg2],
37051                    ))));
37052                }
37053
37054                // Default: keep DATEADD with uppercased unit
37055                let unit = Expression::Identifier(Identifier::new(unit_str));
37056                Ok(Expression::Function(Box::new(Function::new(
37057                    "DATEADD".to_string(),
37058                    vec![unit, arg1, arg2],
37059                ))))
37060            }
37061
37062            // DATE_ADD(unit, val, date) - 3 arg form from ClickHouse/Presto
37063            "DATE_ADD" if args.len() == 3 => {
37064                let arg0 = args.remove(0);
37065                let arg1 = args.remove(0);
37066                let arg2 = args.remove(0);
37067                let unit_str = get_unit_str(&arg0);
37068
37069                if matches!(
37070                    target,
37071                    DialectType::Presto | DialectType::Trino | DialectType::Athena
37072                ) {
37073                    // Presto/Trino: DATE_ADD('UNIT', val, date)
37074                    return Ok(Expression::Function(Box::new(Function::new(
37075                        "DATE_ADD".to_string(),
37076                        vec![
37077                            Expression::Literal(Box::new(Literal::String(unit_str))),
37078                            arg1,
37079                            arg2,
37080                        ],
37081                    ))));
37082                }
37083
37084                if matches!(
37085                    target,
37086                    DialectType::Snowflake | DialectType::TSQL | DialectType::Redshift
37087                ) {
37088                    // DATEADD(UNIT, val, date)
37089                    let unit = Expression::Identifier(Identifier::new(unit_str));
37090                    let date = if matches!(target, DialectType::TSQL) {
37091                        Self::ensure_cast_datetime2(arg2)
37092                    } else {
37093                        arg2
37094                    };
37095                    return Ok(Expression::Function(Box::new(Function::new(
37096                        "DATEADD".to_string(),
37097                        vec![unit, arg1, date],
37098                    ))));
37099                }
37100
37101                if matches!(target, DialectType::DuckDB) {
37102                    // DuckDB: date + INTERVAL val UNIT
37103                    let iu = parse_interval_unit(&unit_str);
37104                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37105                        this: Some(arg1),
37106                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37107                            unit: iu,
37108                            use_plural: false,
37109                        }),
37110                    }));
37111                    return Ok(Expression::Add(Box::new(
37112                        crate::expressions::BinaryOp::new(arg2, interval),
37113                    )));
37114                }
37115
37116                if matches!(target, DialectType::Spark | DialectType::Databricks) {
37117                    // Spark: DATE_ADD(UNIT, val, date) with uppercased unit
37118                    let unit = Expression::Identifier(Identifier::new(unit_str));
37119                    return Ok(Expression::Function(Box::new(Function::new(
37120                        "DATE_ADD".to_string(),
37121                        vec![unit, arg1, arg2],
37122                    ))));
37123                }
37124
37125                // Default: DATE_ADD(UNIT, val, date)
37126                let unit = Expression::Identifier(Identifier::new(unit_str));
37127                Ok(Expression::Function(Box::new(Function::new(
37128                    "DATE_ADD".to_string(),
37129                    vec![unit, arg1, arg2],
37130                ))))
37131            }
37132
37133            // DATE_ADD(date, INTERVAL val UNIT) - 2 arg BigQuery form
37134            "DATE_ADD" if args.len() == 2 => {
37135                let date = args.remove(0);
37136                let interval_expr = args.remove(0);
37137                let (val, unit) =
37138                    Self::extract_interval_parts(&interval_expr).unwrap_or_else(|| {
37139                        (interval_expr.clone(), crate::expressions::IntervalUnit::Day)
37140                    });
37141                let unit_str = Self::interval_unit_to_string(&unit);
37142
37143                match target {
37144                    DialectType::DuckDB => {
37145                        // DuckDB: CAST(date AS DATE) + INTERVAL 'val' UNIT
37146                        let cast_date = Self::ensure_cast_date(date);
37147                        let quoted_val = Self::quote_interval_val(&val);
37148                        let interval =
37149                            Expression::Interval(Box::new(crate::expressions::Interval {
37150                                this: Some(quoted_val),
37151                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37152                                    unit,
37153                                    use_plural: false,
37154                                }),
37155                            }));
37156                        Ok(Expression::Add(Box::new(
37157                            crate::expressions::BinaryOp::new(cast_date, interval),
37158                        )))
37159                    }
37160                    DialectType::PostgreSQL => {
37161                        // PostgreSQL: date + INTERVAL 'val UNIT'
37162                        let interval =
37163                            Expression::Interval(Box::new(crate::expressions::Interval {
37164                                this: Some(Expression::Literal(Box::new(Literal::String(
37165                                    format!("{} {}", Self::expr_to_string(&val), unit_str),
37166                                )))),
37167                                unit: None,
37168                            }));
37169                        Ok(Expression::Add(Box::new(
37170                            crate::expressions::BinaryOp::new(date, interval),
37171                        )))
37172                    }
37173                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
37174                        // Presto: DATE_ADD('UNIT', CAST('val' AS BIGINT), date)
37175                        let val_str = Self::expr_to_string(&val);
37176                        Ok(Expression::Function(Box::new(Function::new(
37177                            "DATE_ADD".to_string(),
37178                            vec![
37179                                Expression::Literal(Box::new(Literal::String(
37180                                    unit_str.to_string(),
37181                                ))),
37182                                Expression::Cast(Box::new(Cast {
37183                                    this: Expression::Literal(Box::new(Literal::String(val_str))),
37184                                    to: DataType::BigInt { length: None },
37185                                    trailing_comments: vec![],
37186                                    double_colon_syntax: false,
37187                                    format: None,
37188                                    default: None,
37189                                    inferred_type: None,
37190                                })),
37191                                date,
37192                            ],
37193                        ))))
37194                    }
37195                    DialectType::Spark | DialectType::Hive => {
37196                        // Spark/Hive: DATE_ADD(date, val) for DAY
37197                        match unit_str {
37198                            "DAY" => Ok(Expression::Function(Box::new(Function::new(
37199                                "DATE_ADD".to_string(),
37200                                vec![date, val],
37201                            )))),
37202                            "MONTH" => Ok(Expression::Function(Box::new(Function::new(
37203                                "ADD_MONTHS".to_string(),
37204                                vec![date, val],
37205                            )))),
37206                            _ => {
37207                                let iu = parse_interval_unit(&unit_str);
37208                                let interval =
37209                                    Expression::Interval(Box::new(crate::expressions::Interval {
37210                                        this: Some(val),
37211                                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37212                                            unit: iu,
37213                                            use_plural: false,
37214                                        }),
37215                                    }));
37216                                Ok(Expression::Function(Box::new(Function::new(
37217                                    "DATE_ADD".to_string(),
37218                                    vec![date, interval],
37219                                ))))
37220                            }
37221                        }
37222                    }
37223                    DialectType::Snowflake => {
37224                        // Snowflake: DATEADD(UNIT, 'val', CAST(date AS DATE))
37225                        let cast_date = Self::ensure_cast_date(date);
37226                        let val_str = Self::expr_to_string(&val);
37227                        Ok(Expression::Function(Box::new(Function::new(
37228                            "DATEADD".to_string(),
37229                            vec![
37230                                Expression::Identifier(Identifier::new(unit_str)),
37231                                Expression::Literal(Box::new(Literal::String(val_str))),
37232                                cast_date,
37233                            ],
37234                        ))))
37235                    }
37236                    DialectType::TSQL | DialectType::Fabric => {
37237                        let cast_date = Self::ensure_cast_datetime2(date);
37238                        Ok(Expression::Function(Box::new(Function::new(
37239                            "DATEADD".to_string(),
37240                            vec![
37241                                Expression::Identifier(Identifier::new(unit_str)),
37242                                val,
37243                                cast_date,
37244                            ],
37245                        ))))
37246                    }
37247                    DialectType::Redshift => Ok(Expression::Function(Box::new(Function::new(
37248                        "DATEADD".to_string(),
37249                        vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
37250                    )))),
37251                    DialectType::MySQL => {
37252                        // MySQL: DATE_ADD(date, INTERVAL 'val' UNIT)
37253                        let quoted_val = Self::quote_interval_val(&val);
37254                        let iu = parse_interval_unit(&unit_str);
37255                        let interval =
37256                            Expression::Interval(Box::new(crate::expressions::Interval {
37257                                this: Some(quoted_val),
37258                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37259                                    unit: iu,
37260                                    use_plural: false,
37261                                }),
37262                            }));
37263                        Ok(Expression::Function(Box::new(Function::new(
37264                            "DATE_ADD".to_string(),
37265                            vec![date, interval],
37266                        ))))
37267                    }
37268                    DialectType::BigQuery => {
37269                        // BigQuery: DATE_ADD(date, INTERVAL 'val' UNIT)
37270                        let quoted_val = Self::quote_interval_val(&val);
37271                        let iu = parse_interval_unit(&unit_str);
37272                        let interval =
37273                            Expression::Interval(Box::new(crate::expressions::Interval {
37274                                this: Some(quoted_val),
37275                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37276                                    unit: iu,
37277                                    use_plural: false,
37278                                }),
37279                            }));
37280                        Ok(Expression::Function(Box::new(Function::new(
37281                            "DATE_ADD".to_string(),
37282                            vec![date, interval],
37283                        ))))
37284                    }
37285                    DialectType::Databricks => Ok(Expression::Function(Box::new(Function::new(
37286                        "DATEADD".to_string(),
37287                        vec![Expression::Identifier(Identifier::new(unit_str)), val, date],
37288                    )))),
37289                    _ => {
37290                        // Default: keep as DATE_ADD with decomposed interval
37291                        Ok(Expression::DateAdd(Box::new(
37292                            crate::expressions::DateAddFunc {
37293                                this: date,
37294                                interval: val,
37295                                unit,
37296                            },
37297                        )))
37298                    }
37299                }
37300            }
37301
37302            // ADD_MONTHS(date, val) -> target-specific form
37303            "ADD_MONTHS" if args.len() == 2 => {
37304                let date = args.remove(0);
37305                let val = args.remove(0);
37306
37307                if matches!(target, DialectType::TSQL) {
37308                    // TSQL: DATEADD(MONTH, val, CAST(date AS DATETIME2))
37309                    let cast_date = Self::ensure_cast_datetime2(date);
37310                    return Ok(Expression::Function(Box::new(Function::new(
37311                        "DATEADD".to_string(),
37312                        vec![
37313                            Expression::Identifier(Identifier::new("MONTH")),
37314                            val,
37315                            cast_date,
37316                        ],
37317                    ))));
37318                }
37319
37320                if matches!(target, DialectType::DuckDB) {
37321                    // DuckDB: date + INTERVAL val MONTH
37322                    let interval = Expression::Interval(Box::new(crate::expressions::Interval {
37323                        this: Some(val),
37324                        unit: Some(crate::expressions::IntervalUnitSpec::Simple {
37325                            unit: crate::expressions::IntervalUnit::Month,
37326                            use_plural: false,
37327                        }),
37328                    }));
37329                    return Ok(Expression::Add(Box::new(
37330                        crate::expressions::BinaryOp::new(date, interval),
37331                    )));
37332                }
37333
37334                if matches!(target, DialectType::Snowflake) {
37335                    // Snowflake: keep ADD_MONTHS when source is also Snowflake, else DATEADD
37336                    if matches!(source, DialectType::Snowflake) {
37337                        return Ok(Expression::Function(Box::new(Function::new(
37338                            "ADD_MONTHS".to_string(),
37339                            vec![date, val],
37340                        ))));
37341                    }
37342                    return Ok(Expression::Function(Box::new(Function::new(
37343                        "DATEADD".to_string(),
37344                        vec![Expression::Identifier(Identifier::new("MONTH")), val, date],
37345                    ))));
37346                }
37347
37348                if matches!(target, DialectType::Spark | DialectType::Databricks) {
37349                    // Spark: ADD_MONTHS(date, val) - keep as is
37350                    return Ok(Expression::Function(Box::new(Function::new(
37351                        "ADD_MONTHS".to_string(),
37352                        vec![date, val],
37353                    ))));
37354                }
37355
37356                if matches!(target, DialectType::Hive) {
37357                    return Ok(Expression::Function(Box::new(Function::new(
37358                        "ADD_MONTHS".to_string(),
37359                        vec![date, val],
37360                    ))));
37361                }
37362
37363                if matches!(
37364                    target,
37365                    DialectType::Presto | DialectType::Trino | DialectType::Athena
37366                ) {
37367                    // Presto: DATE_ADD('MONTH', val, date)
37368                    return Ok(Expression::Function(Box::new(Function::new(
37369                        "DATE_ADD".to_string(),
37370                        vec![
37371                            Expression::Literal(Box::new(Literal::String("MONTH".to_string()))),
37372                            val,
37373                            date,
37374                        ],
37375                    ))));
37376                }
37377
37378                // Default: keep ADD_MONTHS
37379                Ok(Expression::Function(Box::new(Function::new(
37380                    "ADD_MONTHS".to_string(),
37381                    vec![date, val],
37382                ))))
37383            }
37384
37385            // SAFE_DIVIDE(x, y) -> target-specific form directly
37386            "SAFE_DIVIDE" if args.len() == 2 => {
37387                let x = args.remove(0);
37388                let y = args.remove(0);
37389                // Wrap x and y in parens if they're complex expressions
37390                let y_ref = match &y {
37391                    Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
37392                        y.clone()
37393                    }
37394                    _ => Expression::Paren(Box::new(Paren {
37395                        this: y.clone(),
37396                        trailing_comments: vec![],
37397                    })),
37398                };
37399                let x_ref = match &x {
37400                    Expression::Column(_) | Expression::Literal(_) | Expression::Identifier(_) => {
37401                        x.clone()
37402                    }
37403                    _ => Expression::Paren(Box::new(Paren {
37404                        this: x.clone(),
37405                        trailing_comments: vec![],
37406                    })),
37407                };
37408                let condition = Expression::Neq(Box::new(crate::expressions::BinaryOp::new(
37409                    y_ref.clone(),
37410                    Expression::number(0),
37411                )));
37412                let div_expr = Expression::Div(Box::new(crate::expressions::BinaryOp::new(
37413                    x_ref.clone(),
37414                    y_ref.clone(),
37415                )));
37416
37417                match target {
37418                    DialectType::Spark | DialectType::Databricks => Ok(Expression::Function(
37419                        Box::new(Function::new("TRY_DIVIDE".to_string(), vec![x, y])),
37420                    )),
37421                    DialectType::DuckDB | DialectType::PostgreSQL => {
37422                        // CASE WHEN y <> 0 THEN x / y ELSE NULL END
37423                        let result_div = if matches!(target, DialectType::PostgreSQL) {
37424                            let cast_x = Expression::Cast(Box::new(Cast {
37425                                this: x_ref,
37426                                to: DataType::Custom {
37427                                    name: "DOUBLE PRECISION".to_string(),
37428                                },
37429                                trailing_comments: vec![],
37430                                double_colon_syntax: false,
37431                                format: None,
37432                                default: None,
37433                                inferred_type: None,
37434                            }));
37435                            Expression::Div(Box::new(crate::expressions::BinaryOp::new(
37436                                cast_x, y_ref,
37437                            )))
37438                        } else {
37439                            div_expr
37440                        };
37441                        Ok(Expression::Case(Box::new(crate::expressions::Case {
37442                            operand: None,
37443                            whens: vec![(condition, result_div)],
37444                            else_: Some(Expression::Null(crate::expressions::Null)),
37445                            comments: Vec::new(),
37446                            inferred_type: None,
37447                        })))
37448                    }
37449                    DialectType::Snowflake => {
37450                        // IFF(y <> 0, x / y, NULL)
37451                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37452                            condition,
37453                            true_value: div_expr,
37454                            false_value: Some(Expression::Null(crate::expressions::Null)),
37455                            original_name: Some("IFF".to_string()),
37456                            inferred_type: None,
37457                        })))
37458                    }
37459                    DialectType::Presto | DialectType::Trino => {
37460                        // IF(y <> 0, CAST(x AS DOUBLE) / y, NULL)
37461                        let cast_x = Expression::Cast(Box::new(Cast {
37462                            this: x_ref,
37463                            to: DataType::Double {
37464                                precision: None,
37465                                scale: None,
37466                            },
37467                            trailing_comments: vec![],
37468                            double_colon_syntax: false,
37469                            format: None,
37470                            default: None,
37471                            inferred_type: None,
37472                        }));
37473                        let cast_div = Expression::Div(Box::new(
37474                            crate::expressions::BinaryOp::new(cast_x, y_ref),
37475                        ));
37476                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37477                            condition,
37478                            true_value: cast_div,
37479                            false_value: Some(Expression::Null(crate::expressions::Null)),
37480                            original_name: None,
37481                            inferred_type: None,
37482                        })))
37483                    }
37484                    _ => {
37485                        // IF(y <> 0, x / y, NULL)
37486                        Ok(Expression::IfFunc(Box::new(crate::expressions::IfFunc {
37487                            condition,
37488                            true_value: div_expr,
37489                            false_value: Some(Expression::Null(crate::expressions::Null)),
37490                            original_name: None,
37491                            inferred_type: None,
37492                        })))
37493                    }
37494                }
37495            }
37496
37497            // GENERATE_UUID() -> UUID() with CAST to string
37498            "GENERATE_UUID" => {
37499                let uuid_expr = Expression::Uuid(Box::new(crate::expressions::Uuid {
37500                    this: None,
37501                    name: None,
37502                    is_string: None,
37503                }));
37504                // Most targets need CAST(UUID() AS TEXT/VARCHAR/STRING)
37505                let cast_type = match target {
37506                    DialectType::DuckDB => Some(DataType::Text),
37507                    DialectType::Presto | DialectType::Trino => Some(DataType::VarChar {
37508                        length: None,
37509                        parenthesized_length: false,
37510                    }),
37511                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
37512                        Some(DataType::String { length: None })
37513                    }
37514                    _ => None,
37515                };
37516                if let Some(dt) = cast_type {
37517                    Ok(Expression::Cast(Box::new(Cast {
37518                        this: uuid_expr,
37519                        to: dt,
37520                        trailing_comments: vec![],
37521                        double_colon_syntax: false,
37522                        format: None,
37523                        default: None,
37524                        inferred_type: None,
37525                    })))
37526                } else {
37527                    Ok(uuid_expr)
37528                }
37529            }
37530
37531            // COUNTIF(x) -> CountIf expression
37532            "COUNTIF" if args.len() == 1 => {
37533                let arg = args.remove(0);
37534                Ok(Expression::CountIf(Box::new(crate::expressions::AggFunc {
37535                    this: arg,
37536                    distinct: false,
37537                    filter: None,
37538                    order_by: vec![],
37539                    name: None,
37540                    ignore_nulls: None,
37541                    having_max: None,
37542                    limit: None,
37543                    inferred_type: None,
37544                })))
37545            }
37546
37547            // EDIT_DISTANCE(col1, col2, ...) -> Levenshtein expression
37548            "EDIT_DISTANCE" => {
37549                // Strip named arguments (max_distance => N) and pass as positional
37550                let mut positional_args: Vec<Expression> = vec![];
37551                for arg in args {
37552                    match arg {
37553                        Expression::NamedArgument(na) => {
37554                            positional_args.push(na.value);
37555                        }
37556                        other => positional_args.push(other),
37557                    }
37558                }
37559                if positional_args.len() >= 2 {
37560                    let col1 = positional_args.remove(0);
37561                    let col2 = positional_args.remove(0);
37562                    let levenshtein = crate::expressions::BinaryFunc {
37563                        this: col1,
37564                        expression: col2,
37565                        original_name: None,
37566                        inferred_type: None,
37567                    };
37568                    // Pass extra args through a function wrapper with all args
37569                    if !positional_args.is_empty() {
37570                        let max_dist = positional_args.remove(0);
37571                        // DuckDB: CASE WHEN LEVENSHTEIN(a, b) IS NULL OR max IS NULL THEN NULL ELSE LEAST(LEVENSHTEIN(a, b), max) END
37572                        if matches!(target, DialectType::DuckDB) {
37573                            let lev = Expression::Function(Box::new(Function::new(
37574                                "LEVENSHTEIN".to_string(),
37575                                vec![levenshtein.this, levenshtein.expression],
37576                            )));
37577                            let lev_is_null =
37578                                Expression::IsNull(Box::new(crate::expressions::IsNull {
37579                                    this: lev.clone(),
37580                                    not: false,
37581                                    postfix_form: false,
37582                                }));
37583                            let max_is_null =
37584                                Expression::IsNull(Box::new(crate::expressions::IsNull {
37585                                    this: max_dist.clone(),
37586                                    not: false,
37587                                    postfix_form: false,
37588                                }));
37589                            let null_check =
37590                                Expression::Or(Box::new(crate::expressions::BinaryOp {
37591                                    left: lev_is_null,
37592                                    right: max_is_null,
37593                                    left_comments: Vec::new(),
37594                                    operator_comments: Vec::new(),
37595                                    trailing_comments: Vec::new(),
37596                                    inferred_type: None,
37597                                }));
37598                            let least =
37599                                Expression::Least(Box::new(crate::expressions::VarArgFunc {
37600                                    expressions: vec![lev, max_dist],
37601                                    original_name: None,
37602                                    inferred_type: None,
37603                                }));
37604                            return Ok(Expression::Case(Box::new(crate::expressions::Case {
37605                                operand: None,
37606                                whens: vec![(
37607                                    null_check,
37608                                    Expression::Null(crate::expressions::Null),
37609                                )],
37610                                else_: Some(least),
37611                                comments: Vec::new(),
37612                                inferred_type: None,
37613                            })));
37614                        }
37615                        let mut all_args = vec![levenshtein.this, levenshtein.expression, max_dist];
37616                        all_args.extend(positional_args);
37617                        // PostgreSQL: use LEVENSHTEIN_LESS_EQUAL when max_distance is provided
37618                        let func_name = if matches!(target, DialectType::PostgreSQL) {
37619                            "LEVENSHTEIN_LESS_EQUAL"
37620                        } else {
37621                            "LEVENSHTEIN"
37622                        };
37623                        return Ok(Expression::Function(Box::new(Function::new(
37624                            func_name.to_string(),
37625                            all_args,
37626                        ))));
37627                    }
37628                    Ok(Expression::Levenshtein(Box::new(levenshtein)))
37629                } else {
37630                    Ok(Expression::Function(Box::new(Function::new(
37631                        "EDIT_DISTANCE".to_string(),
37632                        positional_args,
37633                    ))))
37634                }
37635            }
37636
37637            // TIMESTAMP_SECONDS(x) -> UnixToTime with scale 0
37638            "TIMESTAMP_SECONDS" if args.len() == 1 => {
37639                let arg = args.remove(0);
37640                Ok(Expression::UnixToTime(Box::new(
37641                    crate::expressions::UnixToTime {
37642                        this: Box::new(arg),
37643                        scale: Some(0),
37644                        zone: None,
37645                        hours: None,
37646                        minutes: None,
37647                        format: None,
37648                        target_type: None,
37649                    },
37650                )))
37651            }
37652
37653            // TIMESTAMP_MILLIS(x) -> UnixToTime with scale 3
37654            "TIMESTAMP_MILLIS" if args.len() == 1 => {
37655                let arg = args.remove(0);
37656                Ok(Expression::UnixToTime(Box::new(
37657                    crate::expressions::UnixToTime {
37658                        this: Box::new(arg),
37659                        scale: Some(3),
37660                        zone: None,
37661                        hours: None,
37662                        minutes: None,
37663                        format: None,
37664                        target_type: None,
37665                    },
37666                )))
37667            }
37668
37669            // TIMESTAMP_MICROS(x) -> UnixToTime with scale 6
37670            "TIMESTAMP_MICROS" if args.len() == 1 => {
37671                let arg = args.remove(0);
37672                Ok(Expression::UnixToTime(Box::new(
37673                    crate::expressions::UnixToTime {
37674                        this: Box::new(arg),
37675                        scale: Some(6),
37676                        zone: None,
37677                        hours: None,
37678                        minutes: None,
37679                        format: None,
37680                        target_type: None,
37681                    },
37682                )))
37683            }
37684
37685            // DIV(x, y) -> IntDiv expression
37686            "DIV" if args.len() == 2 => {
37687                let x = args.remove(0);
37688                let y = args.remove(0);
37689                Ok(Expression::IntDiv(Box::new(
37690                    crate::expressions::BinaryFunc {
37691                        this: x,
37692                        expression: y,
37693                        original_name: None,
37694                        inferred_type: None,
37695                    },
37696                )))
37697            }
37698
37699            // TO_HEX(x) -> target-specific form
37700            "TO_HEX" if args.len() == 1 => {
37701                let arg = args.remove(0);
37702                // Check if inner function already returns hex string in certain targets
37703                let inner_returns_hex = matches!(&arg, Expression::Function(f) if matches!(f.name.as_str(), "MD5" | "SHA1" | "SHA256" | "SHA512"));
37704                if matches!(target, DialectType::BigQuery) {
37705                    // BQ->BQ: keep as TO_HEX
37706                    Ok(Expression::Function(Box::new(Function::new(
37707                        "TO_HEX".to_string(),
37708                        vec![arg],
37709                    ))))
37710                } else if matches!(target, DialectType::DuckDB) && inner_returns_hex {
37711                    // DuckDB: MD5/SHA already return hex strings, so TO_HEX is redundant
37712                    Ok(arg)
37713                } else if matches!(target, DialectType::Snowflake) && inner_returns_hex {
37714                    // Snowflake: TO_HEX(SHA1(x)) -> TO_CHAR(SHA1_BINARY(x))
37715                    // TO_HEX(MD5(x)) -> TO_CHAR(MD5_BINARY(x))
37716                    // TO_HEX(SHA256(x)) -> TO_CHAR(SHA2_BINARY(x, 256))
37717                    // TO_HEX(SHA512(x)) -> TO_CHAR(SHA2_BINARY(x, 512))
37718                    if let Expression::Function(ref inner_f) = arg {
37719                        let inner_args = inner_f.args.clone();
37720                        let binary_func = match inner_f.name.to_ascii_uppercase().as_str() {
37721                            "SHA1" => Expression::Function(Box::new(Function::new(
37722                                "SHA1_BINARY".to_string(),
37723                                inner_args,
37724                            ))),
37725                            "MD5" => Expression::Function(Box::new(Function::new(
37726                                "MD5_BINARY".to_string(),
37727                                inner_args,
37728                            ))),
37729                            "SHA256" => {
37730                                let mut a = inner_args;
37731                                a.push(Expression::number(256));
37732                                Expression::Function(Box::new(Function::new(
37733                                    "SHA2_BINARY".to_string(),
37734                                    a,
37735                                )))
37736                            }
37737                            "SHA512" => {
37738                                let mut a = inner_args;
37739                                a.push(Expression::number(512));
37740                                Expression::Function(Box::new(Function::new(
37741                                    "SHA2_BINARY".to_string(),
37742                                    a,
37743                                )))
37744                            }
37745                            _ => arg.clone(),
37746                        };
37747                        Ok(Expression::Function(Box::new(Function::new(
37748                            "TO_CHAR".to_string(),
37749                            vec![binary_func],
37750                        ))))
37751                    } else {
37752                        let inner = Expression::Function(Box::new(Function::new(
37753                            "HEX".to_string(),
37754                            vec![arg],
37755                        )));
37756                        Ok(Expression::Lower(Box::new(
37757                            crate::expressions::UnaryFunc::new(inner),
37758                        )))
37759                    }
37760                } else if matches!(target, DialectType::Presto | DialectType::Trino) {
37761                    let inner = Expression::Function(Box::new(Function::new(
37762                        "TO_HEX".to_string(),
37763                        vec![arg],
37764                    )));
37765                    Ok(Expression::Lower(Box::new(
37766                        crate::expressions::UnaryFunc::new(inner),
37767                    )))
37768                } else {
37769                    let inner =
37770                        Expression::Function(Box::new(Function::new("HEX".to_string(), vec![arg])));
37771                    Ok(Expression::Lower(Box::new(
37772                        crate::expressions::UnaryFunc::new(inner),
37773                    )))
37774                }
37775            }
37776
37777            // LAST_DAY(date, unit) -> strip unit for most targets, or transform for PostgreSQL
37778            "LAST_DAY" if args.len() == 2 => {
37779                let date = args.remove(0);
37780                let _unit = args.remove(0); // Strip the unit (MONTH is default)
37781                Ok(Expression::Function(Box::new(Function::new(
37782                    "LAST_DAY".to_string(),
37783                    vec![date],
37784                ))))
37785            }
37786
37787            // GENERATE_ARRAY(start, end, step?) -> GenerateSeries expression
37788            "GENERATE_ARRAY" => {
37789                let start = args.get(0).cloned();
37790                let end = args.get(1).cloned();
37791                let step = args.get(2).cloned();
37792                Ok(Expression::GenerateSeries(Box::new(
37793                    crate::expressions::GenerateSeries {
37794                        start: start.map(Box::new),
37795                        end: end.map(Box::new),
37796                        step: step.map(Box::new),
37797                        is_end_exclusive: None,
37798                    },
37799                )))
37800            }
37801
37802            // GENERATE_TIMESTAMP_ARRAY(start, end, step) -> GenerateSeries expression
37803            "GENERATE_TIMESTAMP_ARRAY" => {
37804                let start = args.get(0).cloned();
37805                let end = args.get(1).cloned();
37806                let step = args.get(2).cloned();
37807
37808                if matches!(target, DialectType::DuckDB) {
37809                    // DuckDB: GENERATE_SERIES(CAST(start AS TIMESTAMP), CAST(end AS TIMESTAMP), step)
37810                    // Only cast string literals - leave columns/expressions as-is
37811                    let maybe_cast_ts = |expr: Expression| -> Expression {
37812                        if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
37813                        {
37814                            Expression::Cast(Box::new(Cast {
37815                                this: expr,
37816                                to: DataType::Timestamp {
37817                                    precision: None,
37818                                    timezone: false,
37819                                },
37820                                trailing_comments: vec![],
37821                                double_colon_syntax: false,
37822                                format: None,
37823                                default: None,
37824                                inferred_type: None,
37825                            }))
37826                        } else {
37827                            expr
37828                        }
37829                    };
37830                    let cast_start = start.map(maybe_cast_ts);
37831                    let cast_end = end.map(maybe_cast_ts);
37832                    Ok(Expression::GenerateSeries(Box::new(
37833                        crate::expressions::GenerateSeries {
37834                            start: cast_start.map(Box::new),
37835                            end: cast_end.map(Box::new),
37836                            step: step.map(Box::new),
37837                            is_end_exclusive: None,
37838                        },
37839                    )))
37840                } else {
37841                    Ok(Expression::GenerateSeries(Box::new(
37842                        crate::expressions::GenerateSeries {
37843                            start: start.map(Box::new),
37844                            end: end.map(Box::new),
37845                            step: step.map(Box::new),
37846                            is_end_exclusive: None,
37847                        },
37848                    )))
37849                }
37850            }
37851
37852            // TO_JSON(x) -> target-specific (from Spark/Hive)
37853            "TO_JSON" => {
37854                match target {
37855                    DialectType::Presto | DialectType::Trino => {
37856                        // JSON_FORMAT(CAST(x AS JSON))
37857                        let arg = args
37858                            .into_iter()
37859                            .next()
37860                            .unwrap_or(Expression::Null(crate::expressions::Null));
37861                        let cast_json = Expression::Cast(Box::new(Cast {
37862                            this: arg,
37863                            to: DataType::Custom {
37864                                name: "JSON".to_string(),
37865                            },
37866                            trailing_comments: vec![],
37867                            double_colon_syntax: false,
37868                            format: None,
37869                            default: None,
37870                            inferred_type: None,
37871                        }));
37872                        Ok(Expression::Function(Box::new(Function::new(
37873                            "JSON_FORMAT".to_string(),
37874                            vec![cast_json],
37875                        ))))
37876                    }
37877                    DialectType::BigQuery => Ok(Expression::Function(Box::new(Function::new(
37878                        "TO_JSON_STRING".to_string(),
37879                        args,
37880                    )))),
37881                    DialectType::DuckDB => {
37882                        // CAST(TO_JSON(x) AS TEXT)
37883                        let arg = args
37884                            .into_iter()
37885                            .next()
37886                            .unwrap_or(Expression::Null(crate::expressions::Null));
37887                        let to_json = Expression::Function(Box::new(Function::new(
37888                            "TO_JSON".to_string(),
37889                            vec![arg],
37890                        )));
37891                        Ok(Expression::Cast(Box::new(Cast {
37892                            this: to_json,
37893                            to: DataType::Text,
37894                            trailing_comments: vec![],
37895                            double_colon_syntax: false,
37896                            format: None,
37897                            default: None,
37898                            inferred_type: None,
37899                        })))
37900                    }
37901                    _ => Ok(Expression::Function(Box::new(Function::new(
37902                        "TO_JSON".to_string(),
37903                        args,
37904                    )))),
37905                }
37906            }
37907
37908            // TO_JSON_STRING(x) -> target-specific
37909            "TO_JSON_STRING" => {
37910                match target {
37911                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => Ok(
37912                        Expression::Function(Box::new(Function::new("TO_JSON".to_string(), args))),
37913                    ),
37914                    DialectType::Presto | DialectType::Trino => {
37915                        // JSON_FORMAT(CAST(x AS JSON))
37916                        let arg = args
37917                            .into_iter()
37918                            .next()
37919                            .unwrap_or(Expression::Null(crate::expressions::Null));
37920                        let cast_json = Expression::Cast(Box::new(Cast {
37921                            this: arg,
37922                            to: DataType::Custom {
37923                                name: "JSON".to_string(),
37924                            },
37925                            trailing_comments: vec![],
37926                            double_colon_syntax: false,
37927                            format: None,
37928                            default: None,
37929                            inferred_type: None,
37930                        }));
37931                        Ok(Expression::Function(Box::new(Function::new(
37932                            "JSON_FORMAT".to_string(),
37933                            vec![cast_json],
37934                        ))))
37935                    }
37936                    DialectType::DuckDB => {
37937                        // CAST(TO_JSON(x) AS TEXT)
37938                        let arg = args
37939                            .into_iter()
37940                            .next()
37941                            .unwrap_or(Expression::Null(crate::expressions::Null));
37942                        let to_json = Expression::Function(Box::new(Function::new(
37943                            "TO_JSON".to_string(),
37944                            vec![arg],
37945                        )));
37946                        Ok(Expression::Cast(Box::new(Cast {
37947                            this: to_json,
37948                            to: DataType::Text,
37949                            trailing_comments: vec![],
37950                            double_colon_syntax: false,
37951                            format: None,
37952                            default: None,
37953                            inferred_type: None,
37954                        })))
37955                    }
37956                    DialectType::Snowflake => {
37957                        // TO_JSON(x)
37958                        Ok(Expression::Function(Box::new(Function::new(
37959                            "TO_JSON".to_string(),
37960                            args,
37961                        ))))
37962                    }
37963                    _ => Ok(Expression::Function(Box::new(Function::new(
37964                        "TO_JSON_STRING".to_string(),
37965                        args,
37966                    )))),
37967                }
37968            }
37969
37970            // SAFE_ADD(x, y) -> SafeAdd expression
37971            "SAFE_ADD" if args.len() == 2 => {
37972                let x = args.remove(0);
37973                let y = args.remove(0);
37974                Ok(Expression::SafeAdd(Box::new(crate::expressions::SafeAdd {
37975                    this: Box::new(x),
37976                    expression: Box::new(y),
37977                })))
37978            }
37979
37980            // SAFE_SUBTRACT(x, y) -> SafeSubtract expression
37981            "SAFE_SUBTRACT" if args.len() == 2 => {
37982                let x = args.remove(0);
37983                let y = args.remove(0);
37984                Ok(Expression::SafeSubtract(Box::new(
37985                    crate::expressions::SafeSubtract {
37986                        this: Box::new(x),
37987                        expression: Box::new(y),
37988                    },
37989                )))
37990            }
37991
37992            // SAFE_MULTIPLY(x, y) -> SafeMultiply expression
37993            "SAFE_MULTIPLY" if args.len() == 2 => {
37994                let x = args.remove(0);
37995                let y = args.remove(0);
37996                Ok(Expression::SafeMultiply(Box::new(
37997                    crate::expressions::SafeMultiply {
37998                        this: Box::new(x),
37999                        expression: Box::new(y),
38000                    },
38001                )))
38002            }
38003
38004            // REGEXP_CONTAINS(str, pattern) -> RegexpLike expression
38005            "REGEXP_CONTAINS" if args.len() == 2 => {
38006                let str_expr = args.remove(0);
38007                let pattern = args.remove(0);
38008                Ok(Expression::RegexpLike(Box::new(
38009                    crate::expressions::RegexpFunc {
38010                        this: str_expr,
38011                        pattern,
38012                        flags: None,
38013                    },
38014                )))
38015            }
38016
38017            // CONTAINS_SUBSTR(a, b) -> CONTAINS(LOWER(a), LOWER(b))
38018            "CONTAINS_SUBSTR" if args.len() == 2 => {
38019                let a = args.remove(0);
38020                let b = args.remove(0);
38021                let lower_a = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(a)));
38022                let lower_b = Expression::Lower(Box::new(crate::expressions::UnaryFunc::new(b)));
38023                Ok(Expression::Function(Box::new(Function::new(
38024                    "CONTAINS".to_string(),
38025                    vec![lower_a, lower_b],
38026                ))))
38027            }
38028
38029            // INT64(x) -> CAST(x AS BIGINT)
38030            "INT64" if args.len() == 1 => {
38031                let arg = args.remove(0);
38032                Ok(Expression::Cast(Box::new(Cast {
38033                    this: arg,
38034                    to: DataType::BigInt { length: None },
38035                    trailing_comments: vec![],
38036                    double_colon_syntax: false,
38037                    format: None,
38038                    default: None,
38039                    inferred_type: None,
38040                })))
38041            }
38042
38043            // INSTR(str, substr) -> target-specific
38044            "INSTR" if args.len() >= 2 => {
38045                let str_expr = args.remove(0);
38046                let substr = args.remove(0);
38047                if matches!(target, DialectType::Snowflake) {
38048                    // CHARINDEX(substr, str)
38049                    Ok(Expression::Function(Box::new(Function::new(
38050                        "CHARINDEX".to_string(),
38051                        vec![substr, str_expr],
38052                    ))))
38053                } else if matches!(target, DialectType::BigQuery) {
38054                    // Keep as INSTR
38055                    Ok(Expression::Function(Box::new(Function::new(
38056                        "INSTR".to_string(),
38057                        vec![str_expr, substr],
38058                    ))))
38059                } else {
38060                    // Default: keep as INSTR
38061                    Ok(Expression::Function(Box::new(Function::new(
38062                        "INSTR".to_string(),
38063                        vec![str_expr, substr],
38064                    ))))
38065                }
38066            }
38067
38068            // BigQuery DATE_TRUNC(expr, unit) -> DATE_TRUNC('unit', expr) for standard SQL
38069            "DATE_TRUNC" if args.len() == 2 => {
38070                let expr = args.remove(0);
38071                let unit_expr = args.remove(0);
38072                let unit_str = get_unit_str(&unit_expr);
38073
38074                match target {
38075                    DialectType::DuckDB
38076                    | DialectType::Snowflake
38077                    | DialectType::PostgreSQL
38078                    | DialectType::Presto
38079                    | DialectType::Trino
38080                    | DialectType::Databricks
38081                    | DialectType::Spark
38082                    | DialectType::Redshift
38083                    | DialectType::ClickHouse
38084                    | DialectType::TSQL => {
38085                        // Standard: DATE_TRUNC('UNIT', expr)
38086                        Ok(Expression::Function(Box::new(Function::new(
38087                            "DATE_TRUNC".to_string(),
38088                            vec![
38089                                Expression::Literal(Box::new(Literal::String(unit_str))),
38090                                expr,
38091                            ],
38092                        ))))
38093                    }
38094                    _ => {
38095                        // Keep BigQuery arg order: DATE_TRUNC(expr, unit)
38096                        Ok(Expression::Function(Box::new(Function::new(
38097                            "DATE_TRUNC".to_string(),
38098                            vec![expr, unit_expr],
38099                        ))))
38100                    }
38101                }
38102            }
38103
38104            // TIMESTAMP_TRUNC / DATETIME_TRUNC -> target-specific
38105            "TIMESTAMP_TRUNC" | "DATETIME_TRUNC" if args.len() >= 2 => {
38106                // TIMESTAMP_TRUNC(ts, unit) or TIMESTAMP_TRUNC(ts, unit, timezone)
38107                let ts = args.remove(0);
38108                let unit_expr = args.remove(0);
38109                let tz = if !args.is_empty() {
38110                    Some(args.remove(0))
38111                } else {
38112                    None
38113                };
38114                let unit_str = get_unit_str(&unit_expr);
38115
38116                match target {
38117                    DialectType::DuckDB => {
38118                        // DuckDB: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
38119                        // With timezone: DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz' (for DAY granularity)
38120                        // Without timezone for MINUTE+ granularity: just DATE_TRUNC
38121                        let is_coarse = matches!(
38122                            unit_str.as_str(),
38123                            "DAY" | "WEEK" | "MONTH" | "QUARTER" | "YEAR"
38124                        );
38125                        // For DATETIME_TRUNC, cast string args to TIMESTAMP
38126                        let cast_ts = if name == "DATETIME_TRUNC" {
38127                            match ts {
38128                                Expression::Literal(ref lit)
38129                                    if matches!(lit.as_ref(), Literal::String(ref _s)) =>
38130                                {
38131                                    Expression::Cast(Box::new(Cast {
38132                                        this: ts,
38133                                        to: DataType::Timestamp {
38134                                            precision: None,
38135                                            timezone: false,
38136                                        },
38137                                        trailing_comments: vec![],
38138                                        double_colon_syntax: false,
38139                                        format: None,
38140                                        default: None,
38141                                        inferred_type: None,
38142                                    }))
38143                                }
38144                                _ => Self::maybe_cast_ts_to_tz(ts, &name),
38145                            }
38146                        } else {
38147                            Self::maybe_cast_ts_to_tz(ts, &name)
38148                        };
38149
38150                        if let Some(tz_arg) = tz {
38151                            if is_coarse {
38152                                // DATE_TRUNC('UNIT', ts AT TIME ZONE 'tz') AT TIME ZONE 'tz'
38153                                let at_tz = Expression::AtTimeZone(Box::new(
38154                                    crate::expressions::AtTimeZone {
38155                                        this: cast_ts,
38156                                        zone: tz_arg.clone(),
38157                                    },
38158                                ));
38159                                let date_trunc = Expression::Function(Box::new(Function::new(
38160                                    "DATE_TRUNC".to_string(),
38161                                    vec![
38162                                        Expression::Literal(Box::new(Literal::String(unit_str))),
38163                                        at_tz,
38164                                    ],
38165                                )));
38166                                Ok(Expression::AtTimeZone(Box::new(
38167                                    crate::expressions::AtTimeZone {
38168                                        this: date_trunc,
38169                                        zone: tz_arg,
38170                                    },
38171                                )))
38172                            } else {
38173                                // For MINUTE/HOUR: no AT TIME ZONE wrapper, just DATE_TRUNC('UNIT', ts)
38174                                Ok(Expression::Function(Box::new(Function::new(
38175                                    "DATE_TRUNC".to_string(),
38176                                    vec![
38177                                        Expression::Literal(Box::new(Literal::String(unit_str))),
38178                                        cast_ts,
38179                                    ],
38180                                ))))
38181                            }
38182                        } else {
38183                            // No timezone: DATE_TRUNC('UNIT', CAST(ts AS TIMESTAMPTZ))
38184                            Ok(Expression::Function(Box::new(Function::new(
38185                                "DATE_TRUNC".to_string(),
38186                                vec![
38187                                    Expression::Literal(Box::new(Literal::String(unit_str))),
38188                                    cast_ts,
38189                                ],
38190                            ))))
38191                        }
38192                    }
38193                    DialectType::Databricks | DialectType::Spark => {
38194                        // Databricks/Spark: DATE_TRUNC('UNIT', ts)
38195                        Ok(Expression::Function(Box::new(Function::new(
38196                            "DATE_TRUNC".to_string(),
38197                            vec![Expression::Literal(Box::new(Literal::String(unit_str))), ts],
38198                        ))))
38199                    }
38200                    _ => {
38201                        // Default: keep as TIMESTAMP_TRUNC('UNIT', ts, [tz])
38202                        let unit = Expression::Literal(Box::new(Literal::String(unit_str)));
38203                        let mut date_trunc_args = vec![unit, ts];
38204                        if let Some(tz_arg) = tz {
38205                            date_trunc_args.push(tz_arg);
38206                        }
38207                        Ok(Expression::Function(Box::new(Function::new(
38208                            "TIMESTAMP_TRUNC".to_string(),
38209                            date_trunc_args,
38210                        ))))
38211                    }
38212                }
38213            }
38214
38215            // TIME(h, m, s) -> target-specific, TIME('string') -> CAST('string' AS TIME)
38216            "TIME" => {
38217                if args.len() == 3 {
38218                    // TIME(h, m, s) constructor
38219                    match target {
38220                        DialectType::TSQL => {
38221                            // TIMEFROMPARTS(h, m, s, 0, 0)
38222                            args.push(Expression::number(0));
38223                            args.push(Expression::number(0));
38224                            Ok(Expression::Function(Box::new(Function::new(
38225                                "TIMEFROMPARTS".to_string(),
38226                                args,
38227                            ))))
38228                        }
38229                        DialectType::MySQL => Ok(Expression::Function(Box::new(Function::new(
38230                            "MAKETIME".to_string(),
38231                            args,
38232                        )))),
38233                        DialectType::PostgreSQL => Ok(Expression::Function(Box::new(
38234                            Function::new("MAKE_TIME".to_string(), args),
38235                        ))),
38236                        _ => Ok(Expression::Function(Box::new(Function::new(
38237                            "TIME".to_string(),
38238                            args,
38239                        )))),
38240                    }
38241                } else if args.len() == 1 {
38242                    let arg = args.remove(0);
38243                    if matches!(target, DialectType::Spark) {
38244                        // Spark: CAST(x AS TIMESTAMP) (yes, TIMESTAMP not TIME)
38245                        Ok(Expression::Cast(Box::new(Cast {
38246                            this: arg,
38247                            to: DataType::Timestamp {
38248                                timezone: false,
38249                                precision: None,
38250                            },
38251                            trailing_comments: vec![],
38252                            double_colon_syntax: false,
38253                            format: None,
38254                            default: None,
38255                            inferred_type: None,
38256                        })))
38257                    } else {
38258                        // Most targets: CAST(x AS TIME)
38259                        Ok(Expression::Cast(Box::new(Cast {
38260                            this: arg,
38261                            to: DataType::Time {
38262                                precision: None,
38263                                timezone: false,
38264                            },
38265                            trailing_comments: vec![],
38266                            double_colon_syntax: false,
38267                            format: None,
38268                            default: None,
38269                            inferred_type: None,
38270                        })))
38271                    }
38272                } else if args.len() == 2 {
38273                    // TIME(expr, timezone) -> CAST(CAST(expr AS TIMESTAMPTZ) AT TIME ZONE tz AS TIME)
38274                    let expr = args.remove(0);
38275                    let tz = args.remove(0);
38276                    let cast_tstz = Expression::Cast(Box::new(Cast {
38277                        this: expr,
38278                        to: DataType::Timestamp {
38279                            timezone: true,
38280                            precision: None,
38281                        },
38282                        trailing_comments: vec![],
38283                        double_colon_syntax: false,
38284                        format: None,
38285                        default: None,
38286                        inferred_type: None,
38287                    }));
38288                    let at_tz = Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38289                        this: cast_tstz,
38290                        zone: tz,
38291                    }));
38292                    Ok(Expression::Cast(Box::new(Cast {
38293                        this: at_tz,
38294                        to: DataType::Time {
38295                            precision: None,
38296                            timezone: false,
38297                        },
38298                        trailing_comments: vec![],
38299                        double_colon_syntax: false,
38300                        format: None,
38301                        default: None,
38302                        inferred_type: None,
38303                    })))
38304                } else {
38305                    Ok(Expression::Function(Box::new(Function::new(
38306                        "TIME".to_string(),
38307                        args,
38308                    ))))
38309                }
38310            }
38311
38312            // DATETIME('string') -> CAST('string' AS TIMESTAMP)
38313            // DATETIME('date', TIME 'time') -> CAST(CAST('date' AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
38314            // DATETIME('string', 'timezone') -> CAST(CAST('string' AS TIMESTAMPTZ) AT TIME ZONE tz AS TIMESTAMP)
38315            // DATETIME(y, m, d, h, min, s) -> target-specific
38316            "DATETIME" => {
38317                // For BigQuery target: keep DATETIME function but convert TIME literal to CAST
38318                if matches!(target, DialectType::BigQuery) {
38319                    if args.len() == 2 {
38320                        let has_time_literal = matches!(&args[1], Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
38321                        if has_time_literal {
38322                            let first = args.remove(0);
38323                            let second = args.remove(0);
38324                            let time_as_cast = match second {
38325                                Expression::Literal(lit)
38326                                    if matches!(lit.as_ref(), Literal::Time(_)) =>
38327                                {
38328                                    let Literal::Time(s) = lit.as_ref() else {
38329                                        unreachable!()
38330                                    };
38331                                    Expression::Cast(Box::new(Cast {
38332                                        this: Expression::Literal(Box::new(Literal::String(
38333                                            s.clone(),
38334                                        ))),
38335                                        to: DataType::Time {
38336                                            precision: None,
38337                                            timezone: false,
38338                                        },
38339                                        trailing_comments: vec![],
38340                                        double_colon_syntax: false,
38341                                        format: None,
38342                                        default: None,
38343                                        inferred_type: None,
38344                                    }))
38345                                }
38346                                other => other,
38347                            };
38348                            return Ok(Expression::Function(Box::new(Function::new(
38349                                "DATETIME".to_string(),
38350                                vec![first, time_as_cast],
38351                            ))));
38352                        }
38353                    }
38354                    return Ok(Expression::Function(Box::new(Function::new(
38355                        "DATETIME".to_string(),
38356                        args,
38357                    ))));
38358                }
38359
38360                if args.len() == 1 {
38361                    let arg = args.remove(0);
38362                    Ok(Expression::Cast(Box::new(Cast {
38363                        this: arg,
38364                        to: DataType::Timestamp {
38365                            timezone: false,
38366                            precision: None,
38367                        },
38368                        trailing_comments: vec![],
38369                        double_colon_syntax: false,
38370                        format: None,
38371                        default: None,
38372                        inferred_type: None,
38373                    })))
38374                } else if args.len() == 2 {
38375                    let first = args.remove(0);
38376                    let second = args.remove(0);
38377                    // Check if second arg is a TIME literal
38378                    let is_time_literal = matches!(&second, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Time(_)));
38379                    if is_time_literal {
38380                        // DATETIME('date', TIME 'time') -> CAST(CAST(date AS DATE) + CAST('time' AS TIME) AS TIMESTAMP)
38381                        let cast_date = Expression::Cast(Box::new(Cast {
38382                            this: first,
38383                            to: DataType::Date,
38384                            trailing_comments: vec![],
38385                            double_colon_syntax: false,
38386                            format: None,
38387                            default: None,
38388                            inferred_type: None,
38389                        }));
38390                        // Convert TIME 'x' literal to string 'x' so CAST produces CAST('x' AS TIME) not CAST(TIME 'x' AS TIME)
38391                        let time_as_string = match second {
38392                            Expression::Literal(lit)
38393                                if matches!(lit.as_ref(), Literal::Time(_)) =>
38394                            {
38395                                let Literal::Time(s) = lit.as_ref() else {
38396                                    unreachable!()
38397                                };
38398                                Expression::Literal(Box::new(Literal::String(s.clone())))
38399                            }
38400                            other => other,
38401                        };
38402                        let cast_time = Expression::Cast(Box::new(Cast {
38403                            this: time_as_string,
38404                            to: DataType::Time {
38405                                precision: None,
38406                                timezone: false,
38407                            },
38408                            trailing_comments: vec![],
38409                            double_colon_syntax: false,
38410                            format: None,
38411                            default: None,
38412                            inferred_type: None,
38413                        }));
38414                        let add_expr =
38415                            Expression::Add(Box::new(BinaryOp::new(cast_date, cast_time)));
38416                        Ok(Expression::Cast(Box::new(Cast {
38417                            this: add_expr,
38418                            to: DataType::Timestamp {
38419                                timezone: false,
38420                                precision: None,
38421                            },
38422                            trailing_comments: vec![],
38423                            double_colon_syntax: false,
38424                            format: None,
38425                            default: None,
38426                            inferred_type: None,
38427                        })))
38428                    } else {
38429                        // DATETIME('string', 'timezone')
38430                        let cast_tstz = Expression::Cast(Box::new(Cast {
38431                            this: first,
38432                            to: DataType::Timestamp {
38433                                timezone: true,
38434                                precision: None,
38435                            },
38436                            trailing_comments: vec![],
38437                            double_colon_syntax: false,
38438                            format: None,
38439                            default: None,
38440                            inferred_type: None,
38441                        }));
38442                        let at_tz =
38443                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38444                                this: cast_tstz,
38445                                zone: second,
38446                            }));
38447                        Ok(Expression::Cast(Box::new(Cast {
38448                            this: at_tz,
38449                            to: DataType::Timestamp {
38450                                timezone: false,
38451                                precision: None,
38452                            },
38453                            trailing_comments: vec![],
38454                            double_colon_syntax: false,
38455                            format: None,
38456                            default: None,
38457                            inferred_type: None,
38458                        })))
38459                    }
38460                } else if args.len() >= 3 {
38461                    // DATETIME(y, m, d, h, min, s) -> TIMESTAMP_FROM_PARTS for Snowflake
38462                    // For other targets, use MAKE_TIMESTAMP or similar
38463                    if matches!(target, DialectType::Snowflake) {
38464                        Ok(Expression::Function(Box::new(Function::new(
38465                            "TIMESTAMP_FROM_PARTS".to_string(),
38466                            args,
38467                        ))))
38468                    } else {
38469                        Ok(Expression::Function(Box::new(Function::new(
38470                            "DATETIME".to_string(),
38471                            args,
38472                        ))))
38473                    }
38474                } else {
38475                    Ok(Expression::Function(Box::new(Function::new(
38476                        "DATETIME".to_string(),
38477                        args,
38478                    ))))
38479                }
38480            }
38481
38482            // TIMESTAMP(x) -> CAST(x AS TIMESTAMP WITH TIME ZONE) for Presto
38483            // TIMESTAMP(x, tz) -> CAST(x AS TIMESTAMP) AT TIME ZONE tz for DuckDB
38484            "TIMESTAMP" => {
38485                if args.len() == 1 {
38486                    let arg = args.remove(0);
38487                    Ok(Expression::Cast(Box::new(Cast {
38488                        this: arg,
38489                        to: DataType::Timestamp {
38490                            timezone: true,
38491                            precision: None,
38492                        },
38493                        trailing_comments: vec![],
38494                        double_colon_syntax: false,
38495                        format: None,
38496                        default: None,
38497                        inferred_type: None,
38498                    })))
38499                } else if args.len() == 2 {
38500                    let arg = args.remove(0);
38501                    let tz = args.remove(0);
38502                    let cast_ts = Expression::Cast(Box::new(Cast {
38503                        this: arg,
38504                        to: DataType::Timestamp {
38505                            timezone: false,
38506                            precision: None,
38507                        },
38508                        trailing_comments: vec![],
38509                        double_colon_syntax: false,
38510                        format: None,
38511                        default: None,
38512                        inferred_type: None,
38513                    }));
38514                    if matches!(target, DialectType::Snowflake) {
38515                        // CONVERT_TIMEZONE('tz', CAST(x AS TIMESTAMP))
38516                        Ok(Expression::Function(Box::new(Function::new(
38517                            "CONVERT_TIMEZONE".to_string(),
38518                            vec![tz, cast_ts],
38519                        ))))
38520                    } else {
38521                        Ok(Expression::AtTimeZone(Box::new(
38522                            crate::expressions::AtTimeZone {
38523                                this: cast_ts,
38524                                zone: tz,
38525                            },
38526                        )))
38527                    }
38528                } else {
38529                    Ok(Expression::Function(Box::new(Function::new(
38530                        "TIMESTAMP".to_string(),
38531                        args,
38532                    ))))
38533                }
38534            }
38535
38536            // STRING(x) -> CAST(x AS VARCHAR/TEXT)
38537            // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS VARCHAR/TEXT)
38538            "STRING" => {
38539                if args.len() == 1 {
38540                    let arg = args.remove(0);
38541                    let cast_type = match target {
38542                        DialectType::DuckDB => DataType::Text,
38543                        _ => DataType::VarChar {
38544                            length: None,
38545                            parenthesized_length: false,
38546                        },
38547                    };
38548                    Ok(Expression::Cast(Box::new(Cast {
38549                        this: arg,
38550                        to: cast_type,
38551                        trailing_comments: vec![],
38552                        double_colon_syntax: false,
38553                        format: None,
38554                        default: None,
38555                        inferred_type: None,
38556                    })))
38557                } else if args.len() == 2 {
38558                    let arg = args.remove(0);
38559                    let tz = args.remove(0);
38560                    let cast_type = match target {
38561                        DialectType::DuckDB => DataType::Text,
38562                        _ => DataType::VarChar {
38563                            length: None,
38564                            parenthesized_length: false,
38565                        },
38566                    };
38567                    if matches!(target, DialectType::Snowflake) {
38568                        // STRING(x, tz) -> CAST(CONVERT_TIMEZONE('UTC', tz, x) AS VARCHAR)
38569                        let convert_tz = Expression::Function(Box::new(Function::new(
38570                            "CONVERT_TIMEZONE".to_string(),
38571                            vec![
38572                                Expression::Literal(Box::new(Literal::String("UTC".to_string()))),
38573                                tz,
38574                                arg,
38575                            ],
38576                        )));
38577                        Ok(Expression::Cast(Box::new(Cast {
38578                            this: convert_tz,
38579                            to: cast_type,
38580                            trailing_comments: vec![],
38581                            double_colon_syntax: false,
38582                            format: None,
38583                            default: None,
38584                            inferred_type: None,
38585                        })))
38586                    } else {
38587                        // STRING(x, tz) -> CAST(CAST(x AS TIMESTAMP) AT TIME ZONE 'UTC' AT TIME ZONE tz AS TEXT/VARCHAR)
38588                        let cast_ts = Expression::Cast(Box::new(Cast {
38589                            this: arg,
38590                            to: DataType::Timestamp {
38591                                timezone: false,
38592                                precision: None,
38593                            },
38594                            trailing_comments: vec![],
38595                            double_colon_syntax: false,
38596                            format: None,
38597                            default: None,
38598                            inferred_type: None,
38599                        }));
38600                        let at_utc =
38601                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38602                                this: cast_ts,
38603                                zone: Expression::Literal(Box::new(Literal::String(
38604                                    "UTC".to_string(),
38605                                ))),
38606                            }));
38607                        let at_tz =
38608                            Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
38609                                this: at_utc,
38610                                zone: tz,
38611                            }));
38612                        Ok(Expression::Cast(Box::new(Cast {
38613                            this: at_tz,
38614                            to: cast_type,
38615                            trailing_comments: vec![],
38616                            double_colon_syntax: false,
38617                            format: None,
38618                            default: None,
38619                            inferred_type: None,
38620                        })))
38621                    }
38622                } else {
38623                    Ok(Expression::Function(Box::new(Function::new(
38624                        "STRING".to_string(),
38625                        args,
38626                    ))))
38627                }
38628            }
38629
38630            // UNIX_SECONDS, UNIX_MILLIS, UNIX_MICROS as functions (not expressions)
38631            "UNIX_SECONDS" if args.len() == 1 => {
38632                let ts = args.remove(0);
38633                match target {
38634                    DialectType::DuckDB => {
38635                        // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
38636                        let cast_ts = Self::ensure_cast_timestamptz(ts);
38637                        let epoch = Expression::Function(Box::new(Function::new(
38638                            "EPOCH".to_string(),
38639                            vec![cast_ts],
38640                        )));
38641                        Ok(Expression::Cast(Box::new(Cast {
38642                            this: epoch,
38643                            to: DataType::BigInt { length: None },
38644                            trailing_comments: vec![],
38645                            double_colon_syntax: false,
38646                            format: None,
38647                            default: None,
38648                            inferred_type: None,
38649                        })))
38650                    }
38651                    DialectType::Snowflake => {
38652                        // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
38653                        let epoch = Expression::Cast(Box::new(Cast {
38654                            this: Expression::Literal(Box::new(Literal::String(
38655                                "1970-01-01 00:00:00+00".to_string(),
38656                            ))),
38657                            to: DataType::Timestamp {
38658                                timezone: true,
38659                                precision: None,
38660                            },
38661                            trailing_comments: vec![],
38662                            double_colon_syntax: false,
38663                            format: None,
38664                            default: None,
38665                            inferred_type: None,
38666                        }));
38667                        Ok(Expression::TimestampDiff(Box::new(
38668                            crate::expressions::TimestampDiff {
38669                                this: Box::new(epoch),
38670                                expression: Box::new(ts),
38671                                unit: Some("SECONDS".to_string()),
38672                            },
38673                        )))
38674                    }
38675                    _ => Ok(Expression::Function(Box::new(Function::new(
38676                        "UNIX_SECONDS".to_string(),
38677                        vec![ts],
38678                    )))),
38679                }
38680            }
38681
38682            "UNIX_MILLIS" if args.len() == 1 => {
38683                let ts = args.remove(0);
38684                match target {
38685                    DialectType::DuckDB => {
38686                        // EPOCH_MS(CAST(ts AS TIMESTAMPTZ))
38687                        let cast_ts = Self::ensure_cast_timestamptz(ts);
38688                        Ok(Expression::Function(Box::new(Function::new(
38689                            "EPOCH_MS".to_string(),
38690                            vec![cast_ts],
38691                        ))))
38692                    }
38693                    _ => Ok(Expression::Function(Box::new(Function::new(
38694                        "UNIX_MILLIS".to_string(),
38695                        vec![ts],
38696                    )))),
38697                }
38698            }
38699
38700            "UNIX_MICROS" if args.len() == 1 => {
38701                let ts = args.remove(0);
38702                match target {
38703                    DialectType::DuckDB => {
38704                        // EPOCH_US(CAST(ts AS TIMESTAMPTZ))
38705                        let cast_ts = Self::ensure_cast_timestamptz(ts);
38706                        Ok(Expression::Function(Box::new(Function::new(
38707                            "EPOCH_US".to_string(),
38708                            vec![cast_ts],
38709                        ))))
38710                    }
38711                    _ => Ok(Expression::Function(Box::new(Function::new(
38712                        "UNIX_MICROS".to_string(),
38713                        vec![ts],
38714                    )))),
38715                }
38716            }
38717
38718            // ARRAY_CONCAT / LIST_CONCAT -> target-specific
38719            "ARRAY_CONCAT" | "LIST_CONCAT" => {
38720                match target {
38721                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
38722                        // CONCAT(arr1, arr2, ...)
38723                        Ok(Expression::Function(Box::new(Function::new(
38724                            "CONCAT".to_string(),
38725                            args,
38726                        ))))
38727                    }
38728                    DialectType::Presto | DialectType::Trino => {
38729                        // CONCAT(arr1, arr2, ...)
38730                        Ok(Expression::Function(Box::new(Function::new(
38731                            "CONCAT".to_string(),
38732                            args,
38733                        ))))
38734                    }
38735                    DialectType::Snowflake => {
38736                        // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
38737                        if args.len() == 1 {
38738                            // ARRAY_CAT requires 2 args, add empty array as []
38739                            let empty_arr = Expression::ArrayFunc(Box::new(
38740                                crate::expressions::ArrayConstructor {
38741                                    expressions: vec![],
38742                                    bracket_notation: true,
38743                                    use_list_keyword: false,
38744                                },
38745                            ));
38746                            let mut new_args = args;
38747                            new_args.push(empty_arr);
38748                            Ok(Expression::Function(Box::new(Function::new(
38749                                "ARRAY_CAT".to_string(),
38750                                new_args,
38751                            ))))
38752                        } else if args.is_empty() {
38753                            Ok(Expression::Function(Box::new(Function::new(
38754                                "ARRAY_CAT".to_string(),
38755                                args,
38756                            ))))
38757                        } else {
38758                            let mut it = args.into_iter().rev();
38759                            let mut result = it.next().unwrap();
38760                            for arr in it {
38761                                result = Expression::Function(Box::new(Function::new(
38762                                    "ARRAY_CAT".to_string(),
38763                                    vec![arr, result],
38764                                )));
38765                            }
38766                            Ok(result)
38767                        }
38768                    }
38769                    DialectType::PostgreSQL => {
38770                        // ARRAY_CAT(arr1, ARRAY_CAT(arr2, arr3))
38771                        if args.len() <= 1 {
38772                            Ok(Expression::Function(Box::new(Function::new(
38773                                "ARRAY_CAT".to_string(),
38774                                args,
38775                            ))))
38776                        } else {
38777                            let mut it = args.into_iter().rev();
38778                            let mut result = it.next().unwrap();
38779                            for arr in it {
38780                                result = Expression::Function(Box::new(Function::new(
38781                                    "ARRAY_CAT".to_string(),
38782                                    vec![arr, result],
38783                                )));
38784                            }
38785                            Ok(result)
38786                        }
38787                    }
38788                    DialectType::Redshift => {
38789                        // ARRAY_CONCAT(arr1, ARRAY_CONCAT(arr2, arr3))
38790                        if args.len() <= 2 {
38791                            Ok(Expression::Function(Box::new(Function::new(
38792                                "ARRAY_CONCAT".to_string(),
38793                                args,
38794                            ))))
38795                        } else {
38796                            let mut it = args.into_iter().rev();
38797                            let mut result = it.next().unwrap();
38798                            for arr in it {
38799                                result = Expression::Function(Box::new(Function::new(
38800                                    "ARRAY_CONCAT".to_string(),
38801                                    vec![arr, result],
38802                                )));
38803                            }
38804                            Ok(result)
38805                        }
38806                    }
38807                    DialectType::DuckDB => {
38808                        // LIST_CONCAT supports multiple args natively in DuckDB
38809                        Ok(Expression::Function(Box::new(Function::new(
38810                            "LIST_CONCAT".to_string(),
38811                            args,
38812                        ))))
38813                    }
38814                    _ => Ok(Expression::Function(Box::new(Function::new(
38815                        "ARRAY_CONCAT".to_string(),
38816                        args,
38817                    )))),
38818                }
38819            }
38820
38821            // ARRAY_CONCAT_AGG -> Snowflake: ARRAY_FLATTEN(ARRAY_AGG(x))
38822            "ARRAY_CONCAT_AGG" if args.len() == 1 => {
38823                let arg = args.remove(0);
38824                match target {
38825                    DialectType::Snowflake => {
38826                        let array_agg =
38827                            Expression::ArrayAgg(Box::new(crate::expressions::AggFunc {
38828                                this: arg,
38829                                distinct: false,
38830                                filter: None,
38831                                order_by: vec![],
38832                                name: None,
38833                                ignore_nulls: None,
38834                                having_max: None,
38835                                limit: None,
38836                                inferred_type: None,
38837                            }));
38838                        Ok(Expression::Function(Box::new(Function::new(
38839                            "ARRAY_FLATTEN".to_string(),
38840                            vec![array_agg],
38841                        ))))
38842                    }
38843                    _ => Ok(Expression::Function(Box::new(Function::new(
38844                        "ARRAY_CONCAT_AGG".to_string(),
38845                        vec![arg],
38846                    )))),
38847                }
38848            }
38849
38850            // MD5/SHA1/SHA256/SHA512 -> target-specific hash functions
38851            "MD5" if args.len() == 1 => {
38852                let arg = args.remove(0);
38853                match target {
38854                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
38855                        // UNHEX(MD5(x))
38856                        let md5 = Expression::Function(Box::new(Function::new(
38857                            "MD5".to_string(),
38858                            vec![arg],
38859                        )));
38860                        Ok(Expression::Function(Box::new(Function::new(
38861                            "UNHEX".to_string(),
38862                            vec![md5],
38863                        ))))
38864                    }
38865                    DialectType::Snowflake => {
38866                        // MD5_BINARY(x)
38867                        Ok(Expression::Function(Box::new(Function::new(
38868                            "MD5_BINARY".to_string(),
38869                            vec![arg],
38870                        ))))
38871                    }
38872                    _ => Ok(Expression::Function(Box::new(Function::new(
38873                        "MD5".to_string(),
38874                        vec![arg],
38875                    )))),
38876                }
38877            }
38878
38879            "SHA1" if args.len() == 1 => {
38880                let arg = args.remove(0);
38881                match target {
38882                    DialectType::DuckDB => {
38883                        // UNHEX(SHA1(x))
38884                        let sha1 = Expression::Function(Box::new(Function::new(
38885                            "SHA1".to_string(),
38886                            vec![arg],
38887                        )));
38888                        Ok(Expression::Function(Box::new(Function::new(
38889                            "UNHEX".to_string(),
38890                            vec![sha1],
38891                        ))))
38892                    }
38893                    _ => Ok(Expression::Function(Box::new(Function::new(
38894                        "SHA1".to_string(),
38895                        vec![arg],
38896                    )))),
38897                }
38898            }
38899
38900            "SHA256" if args.len() == 1 => {
38901                let arg = args.remove(0);
38902                match target {
38903                    DialectType::DuckDB => {
38904                        // UNHEX(SHA256(x))
38905                        let sha = Expression::Function(Box::new(Function::new(
38906                            "SHA256".to_string(),
38907                            vec![arg],
38908                        )));
38909                        Ok(Expression::Function(Box::new(Function::new(
38910                            "UNHEX".to_string(),
38911                            vec![sha],
38912                        ))))
38913                    }
38914                    DialectType::Snowflake => {
38915                        // SHA2_BINARY(x, 256)
38916                        Ok(Expression::Function(Box::new(Function::new(
38917                            "SHA2_BINARY".to_string(),
38918                            vec![arg, Expression::number(256)],
38919                        ))))
38920                    }
38921                    DialectType::Redshift | DialectType::Spark => {
38922                        // SHA2(x, 256)
38923                        Ok(Expression::Function(Box::new(Function::new(
38924                            "SHA2".to_string(),
38925                            vec![arg, Expression::number(256)],
38926                        ))))
38927                    }
38928                    _ => Ok(Expression::Function(Box::new(Function::new(
38929                        "SHA256".to_string(),
38930                        vec![arg],
38931                    )))),
38932                }
38933            }
38934
38935            "SHA512" if args.len() == 1 => {
38936                let arg = args.remove(0);
38937                match target {
38938                    DialectType::Snowflake => {
38939                        // SHA2_BINARY(x, 512)
38940                        Ok(Expression::Function(Box::new(Function::new(
38941                            "SHA2_BINARY".to_string(),
38942                            vec![arg, Expression::number(512)],
38943                        ))))
38944                    }
38945                    DialectType::Redshift | DialectType::Spark => {
38946                        // SHA2(x, 512)
38947                        Ok(Expression::Function(Box::new(Function::new(
38948                            "SHA2".to_string(),
38949                            vec![arg, Expression::number(512)],
38950                        ))))
38951                    }
38952                    _ => Ok(Expression::Function(Box::new(Function::new(
38953                        "SHA512".to_string(),
38954                        vec![arg],
38955                    )))),
38956                }
38957            }
38958
38959            // REGEXP_EXTRACT_ALL(str, pattern) -> add default group arg
38960            "REGEXP_EXTRACT_ALL" if args.len() == 2 => {
38961                let str_expr = args.remove(0);
38962                let pattern = args.remove(0);
38963
38964                // Check if pattern contains capturing groups (parentheses)
38965                let has_groups = match &pattern {
38966                    Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
38967                        let Literal::String(s) = lit.as_ref() else {
38968                            unreachable!()
38969                        };
38970                        s.contains('(') && s.contains(')')
38971                    }
38972                    _ => false,
38973                };
38974
38975                match target {
38976                    DialectType::DuckDB => {
38977                        let group = if has_groups {
38978                            Expression::number(1)
38979                        } else {
38980                            Expression::number(0)
38981                        };
38982                        Ok(Expression::Function(Box::new(Function::new(
38983                            "REGEXP_EXTRACT_ALL".to_string(),
38984                            vec![str_expr, pattern, group],
38985                        ))))
38986                    }
38987                    DialectType::Spark | DialectType::Databricks => {
38988                        // Spark's default group_index is 1 (same as BigQuery), so omit for capturing groups
38989                        if has_groups {
38990                            Ok(Expression::Function(Box::new(Function::new(
38991                                "REGEXP_EXTRACT_ALL".to_string(),
38992                                vec![str_expr, pattern],
38993                            ))))
38994                        } else {
38995                            Ok(Expression::Function(Box::new(Function::new(
38996                                "REGEXP_EXTRACT_ALL".to_string(),
38997                                vec![str_expr, pattern, Expression::number(0)],
38998                            ))))
38999                        }
39000                    }
39001                    DialectType::Presto | DialectType::Trino => {
39002                        if has_groups {
39003                            Ok(Expression::Function(Box::new(Function::new(
39004                                "REGEXP_EXTRACT_ALL".to_string(),
39005                                vec![str_expr, pattern, Expression::number(1)],
39006                            ))))
39007                        } else {
39008                            Ok(Expression::Function(Box::new(Function::new(
39009                                "REGEXP_EXTRACT_ALL".to_string(),
39010                                vec![str_expr, pattern],
39011                            ))))
39012                        }
39013                    }
39014                    DialectType::Snowflake => {
39015                        if has_groups {
39016                            // REGEXP_EXTRACT_ALL(str, pattern, 1, 1, 'c', 1)
39017                            Ok(Expression::Function(Box::new(Function::new(
39018                                "REGEXP_EXTRACT_ALL".to_string(),
39019                                vec![
39020                                    str_expr,
39021                                    pattern,
39022                                    Expression::number(1),
39023                                    Expression::number(1),
39024                                    Expression::Literal(Box::new(Literal::String("c".to_string()))),
39025                                    Expression::number(1),
39026                                ],
39027                            ))))
39028                        } else {
39029                            Ok(Expression::Function(Box::new(Function::new(
39030                                "REGEXP_EXTRACT_ALL".to_string(),
39031                                vec![str_expr, pattern],
39032                            ))))
39033                        }
39034                    }
39035                    _ => Ok(Expression::Function(Box::new(Function::new(
39036                        "REGEXP_EXTRACT_ALL".to_string(),
39037                        vec![str_expr, pattern],
39038                    )))),
39039                }
39040            }
39041
39042            // MOD(x, y) -> x % y for dialects that prefer or require the infix operator.
39043            "MOD" if args.len() == 2 => {
39044                match target {
39045                    DialectType::PostgreSQL
39046                    | DialectType::DuckDB
39047                    | DialectType::Presto
39048                    | DialectType::Trino
39049                    | DialectType::Athena
39050                    | DialectType::Snowflake
39051                    | DialectType::TSQL
39052                    | DialectType::Fabric => {
39053                        let x = args.remove(0);
39054                        let y = args.remove(0);
39055                        // Wrap complex expressions in parens to preserve precedence
39056                        let needs_paren = |e: &Expression| {
39057                            matches!(
39058                                e,
39059                                Expression::Add(_)
39060                                    | Expression::Sub(_)
39061                                    | Expression::Mul(_)
39062                                    | Expression::Div(_)
39063                                    | Expression::Mod(_)
39064                                    | Expression::ModFunc(_)
39065                            )
39066                        };
39067                        let x = if needs_paren(&x) {
39068                            Expression::Paren(Box::new(crate::expressions::Paren {
39069                                this: x,
39070                                trailing_comments: vec![],
39071                            }))
39072                        } else {
39073                            x
39074                        };
39075                        let y = if needs_paren(&y) {
39076                            Expression::Paren(Box::new(crate::expressions::Paren {
39077                                this: y,
39078                                trailing_comments: vec![],
39079                            }))
39080                        } else {
39081                            y
39082                        };
39083                        Ok(Expression::Mod(Box::new(
39084                            crate::expressions::BinaryOp::new(x, y),
39085                        )))
39086                    }
39087                    DialectType::Hive | DialectType::Spark | DialectType::Databricks => {
39088                        // Hive/Spark: a % b
39089                        let x = args.remove(0);
39090                        let y = args.remove(0);
39091                        let needs_paren = |e: &Expression| {
39092                            matches!(
39093                                e,
39094                                Expression::Add(_)
39095                                    | Expression::Sub(_)
39096                                    | Expression::Mul(_)
39097                                    | Expression::Div(_)
39098                                    | Expression::Mod(_)
39099                                    | Expression::ModFunc(_)
39100                            )
39101                        };
39102                        let x = if needs_paren(&x) {
39103                            Expression::Paren(Box::new(crate::expressions::Paren {
39104                                this: x,
39105                                trailing_comments: vec![],
39106                            }))
39107                        } else {
39108                            x
39109                        };
39110                        let y = if needs_paren(&y) {
39111                            Expression::Paren(Box::new(crate::expressions::Paren {
39112                                this: y,
39113                                trailing_comments: vec![],
39114                            }))
39115                        } else {
39116                            y
39117                        };
39118                        Ok(Expression::Mod(Box::new(
39119                            crate::expressions::BinaryOp::new(x, y),
39120                        )))
39121                    }
39122                    _ => Ok(Expression::Function(Box::new(Function::new(
39123                        "MOD".to_string(),
39124                        args,
39125                    )))),
39126                }
39127            }
39128
39129            // ARRAY_FILTER(arr, lambda) -> FILTER for Hive/Spark/Presto, ARRAY_FILTER for StarRocks
39130            "ARRAY_FILTER" if args.len() == 2 => {
39131                let name = match target {
39132                    DialectType::DuckDB => "LIST_FILTER",
39133                    DialectType::StarRocks => "ARRAY_FILTER",
39134                    _ => "FILTER",
39135                };
39136                Ok(Expression::Function(Box::new(Function::new(
39137                    name.to_string(),
39138                    args,
39139                ))))
39140            }
39141            // FILTER(arr, lambda) -> ARRAY_FILTER for StarRocks, LIST_FILTER for DuckDB
39142            "FILTER" if args.len() == 2 => {
39143                let name = match target {
39144                    DialectType::DuckDB => "LIST_FILTER",
39145                    DialectType::StarRocks => "ARRAY_FILTER",
39146                    _ => "FILTER",
39147                };
39148                Ok(Expression::Function(Box::new(Function::new(
39149                    name.to_string(),
39150                    args,
39151                ))))
39152            }
39153            // REDUCE(arr, init, lambda1, lambda2) -> AGGREGATE for Spark
39154            "REDUCE" if args.len() >= 3 => {
39155                let name = match target {
39156                    DialectType::Spark | DialectType::Databricks => "AGGREGATE",
39157                    _ => "REDUCE",
39158                };
39159                Ok(Expression::Function(Box::new(Function::new(
39160                    name.to_string(),
39161                    args,
39162                ))))
39163            }
39164            // ARRAY_REVERSE(x) -> arrayReverse for ClickHouse (handled by generator)
39165            "ARRAY_REVERSE" if args.len() == 1 => Ok(Expression::Function(Box::new(
39166                Function::new("ARRAY_REVERSE".to_string(), args),
39167            ))),
39168
39169            // CONCAT(a, b, ...) -> a || b || ... for DuckDB with 3+ args
39170            "CONCAT" if args.len() > 2 => match target {
39171                DialectType::DuckDB => {
39172                    let mut it = args.into_iter();
39173                    let mut result = it.next().unwrap();
39174                    for arg in it {
39175                        result = Expression::DPipe(Box::new(crate::expressions::DPipe {
39176                            this: Box::new(result),
39177                            expression: Box::new(arg),
39178                            safe: None,
39179                        }));
39180                    }
39181                    Ok(result)
39182                }
39183                _ => Ok(Expression::Function(Box::new(Function::new(
39184                    "CONCAT".to_string(),
39185                    args,
39186                )))),
39187            },
39188
39189            // GENERATE_DATE_ARRAY(start, end[, step]) -> target-specific
39190            "GENERATE_DATE_ARRAY" => {
39191                if matches!(target, DialectType::BigQuery) {
39192                    // BQ->BQ: add default interval if not present
39193                    if args.len() == 2 {
39194                        let start = args.remove(0);
39195                        let end = args.remove(0);
39196                        let default_interval =
39197                            Expression::Interval(Box::new(crate::expressions::Interval {
39198                                this: Some(Expression::Literal(Box::new(Literal::String(
39199                                    "1".to_string(),
39200                                )))),
39201                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39202                                    unit: crate::expressions::IntervalUnit::Day,
39203                                    use_plural: false,
39204                                }),
39205                            }));
39206                        Ok(Expression::Function(Box::new(Function::new(
39207                            "GENERATE_DATE_ARRAY".to_string(),
39208                            vec![start, end, default_interval],
39209                        ))))
39210                    } else {
39211                        Ok(Expression::Function(Box::new(Function::new(
39212                            "GENERATE_DATE_ARRAY".to_string(),
39213                            args,
39214                        ))))
39215                    }
39216                } else if matches!(target, DialectType::DuckDB) {
39217                    // DuckDB: CAST(GENERATE_SERIES(CAST(start AS DATE), CAST(end AS DATE), step) AS DATE[])
39218                    let start = args.get(0).cloned();
39219                    let end = args.get(1).cloned();
39220                    let step = args.get(2).cloned().or_else(|| {
39221                        Some(Expression::Interval(Box::new(
39222                            crate::expressions::Interval {
39223                                this: Some(Expression::Literal(Box::new(Literal::String(
39224                                    "1".to_string(),
39225                                )))),
39226                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39227                                    unit: crate::expressions::IntervalUnit::Day,
39228                                    use_plural: false,
39229                                }),
39230                            },
39231                        )))
39232                    });
39233
39234                    // Wrap start/end in CAST(... AS DATE) only for string literals
39235                    let maybe_cast_date = |expr: Expression| -> Expression {
39236                        if matches!(&expr, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)))
39237                        {
39238                            Expression::Cast(Box::new(Cast {
39239                                this: expr,
39240                                to: DataType::Date,
39241                                trailing_comments: vec![],
39242                                double_colon_syntax: false,
39243                                format: None,
39244                                default: None,
39245                                inferred_type: None,
39246                            }))
39247                        } else {
39248                            expr
39249                        }
39250                    };
39251                    let cast_start = start.map(maybe_cast_date);
39252                    let cast_end = end.map(maybe_cast_date);
39253
39254                    let gen_series =
39255                        Expression::GenerateSeries(Box::new(crate::expressions::GenerateSeries {
39256                            start: cast_start.map(Box::new),
39257                            end: cast_end.map(Box::new),
39258                            step: step.map(Box::new),
39259                            is_end_exclusive: None,
39260                        }));
39261
39262                    // Wrap in CAST(... AS DATE[])
39263                    Ok(Expression::Cast(Box::new(Cast {
39264                        this: gen_series,
39265                        to: DataType::Array {
39266                            element_type: Box::new(DataType::Date),
39267                            dimension: None,
39268                        },
39269                        trailing_comments: vec![],
39270                        double_colon_syntax: false,
39271                        format: None,
39272                        default: None,
39273                        inferred_type: None,
39274                    })))
39275                } else if matches!(target, DialectType::Snowflake) {
39276                    // Snowflake: keep as GENERATE_DATE_ARRAY function for later transform
39277                    // (transform_generate_date_array_snowflake will convert to ARRAY_GENERATE_RANGE + DATEADD)
39278                    if args.len() == 2 {
39279                        let start = args.remove(0);
39280                        let end = args.remove(0);
39281                        let default_interval =
39282                            Expression::Interval(Box::new(crate::expressions::Interval {
39283                                this: Some(Expression::Literal(Box::new(Literal::String(
39284                                    "1".to_string(),
39285                                )))),
39286                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39287                                    unit: crate::expressions::IntervalUnit::Day,
39288                                    use_plural: false,
39289                                }),
39290                            }));
39291                        Ok(Expression::Function(Box::new(Function::new(
39292                            "GENERATE_DATE_ARRAY".to_string(),
39293                            vec![start, end, default_interval],
39294                        ))))
39295                    } else {
39296                        Ok(Expression::Function(Box::new(Function::new(
39297                            "GENERATE_DATE_ARRAY".to_string(),
39298                            args,
39299                        ))))
39300                    }
39301                } else {
39302                    // Convert to GenerateSeries for other targets
39303                    let start = args.get(0).cloned();
39304                    let end = args.get(1).cloned();
39305                    let step = args.get(2).cloned().or_else(|| {
39306                        Some(Expression::Interval(Box::new(
39307                            crate::expressions::Interval {
39308                                this: Some(Expression::Literal(Box::new(Literal::String(
39309                                    "1".to_string(),
39310                                )))),
39311                                unit: Some(crate::expressions::IntervalUnitSpec::Simple {
39312                                    unit: crate::expressions::IntervalUnit::Day,
39313                                    use_plural: false,
39314                                }),
39315                            },
39316                        )))
39317                    });
39318                    Ok(Expression::GenerateSeries(Box::new(
39319                        crate::expressions::GenerateSeries {
39320                            start: start.map(Box::new),
39321                            end: end.map(Box::new),
39322                            step: step.map(Box::new),
39323                            is_end_exclusive: None,
39324                        },
39325                    )))
39326                }
39327            }
39328
39329            // PARSE_DATE(format, str) -> target-specific
39330            "PARSE_DATE" if args.len() == 2 => {
39331                let format = args.remove(0);
39332                let str_expr = args.remove(0);
39333                match target {
39334                    DialectType::DuckDB => {
39335                        // CAST(STRPTIME(str, duck_format) AS DATE)
39336                        let duck_format = Self::bq_format_to_duckdb(&format);
39337                        let strptime = Expression::Function(Box::new(Function::new(
39338                            "STRPTIME".to_string(),
39339                            vec![str_expr, duck_format],
39340                        )));
39341                        Ok(Expression::Cast(Box::new(Cast {
39342                            this: strptime,
39343                            to: DataType::Date,
39344                            trailing_comments: vec![],
39345                            double_colon_syntax: false,
39346                            format: None,
39347                            default: None,
39348                            inferred_type: None,
39349                        })))
39350                    }
39351                    DialectType::Snowflake => {
39352                        // _POLYGLOT_DATE(str, snowflake_format)
39353                        // Use marker so Snowflake target transform keeps it as DATE() instead of TO_DATE()
39354                        let sf_format = Self::bq_format_to_snowflake(&format);
39355                        Ok(Expression::Function(Box::new(Function::new(
39356                            "_POLYGLOT_DATE".to_string(),
39357                            vec![str_expr, sf_format],
39358                        ))))
39359                    }
39360                    _ => Ok(Expression::Function(Box::new(Function::new(
39361                        "PARSE_DATE".to_string(),
39362                        vec![format, str_expr],
39363                    )))),
39364                }
39365            }
39366
39367            // PARSE_DATETIME(format, str) -> target-specific
39368            "PARSE_DATETIME" if args.len() == 2 => {
39369                let format = args.remove(0);
39370                let str_expr = args.remove(0);
39371                let c_format = Self::bq_format_to_duckdb(&format);
39372                match target {
39373                    DialectType::DuckDB => {
39374                        // DuckDB STRPTIME needs a date-bearing format for DATETIME parsing.
39375                        let str_with_year = Expression::Concat(Box::new(BinaryOp::new(
39376                            Expression::string("1970 "),
39377                            str_expr,
39378                        )));
39379                        let format_with_year = Expression::Concat(Box::new(BinaryOp::new(
39380                            Expression::string("%Y "),
39381                            c_format,
39382                        )));
39383                        Ok(Expression::Function(Box::new(Function::new(
39384                            "STRPTIME".to_string(),
39385                            vec![str_with_year, format_with_year],
39386                        ))))
39387                    }
39388                    DialectType::Snowflake => {
39389                        // SQLGlot emits PARSE_DATETIME(value, format) with expanded C-style format tokens.
39390                        Ok(Expression::Function(Box::new(Function::new(
39391                            "PARSE_DATETIME".to_string(),
39392                            vec![str_expr, c_format],
39393                        ))))
39394                    }
39395                    _ => Ok(Expression::Function(Box::new(Function::new(
39396                        "PARSE_DATETIME".to_string(),
39397                        vec![format, str_expr],
39398                    )))),
39399                }
39400            }
39401
39402            // PARSE_TIMESTAMP(format, str) -> target-specific
39403            "PARSE_TIMESTAMP" if args.len() >= 2 => {
39404                let format = args.remove(0);
39405                let str_expr = args.remove(0);
39406                let tz = if !args.is_empty() {
39407                    Some(args.remove(0))
39408                } else {
39409                    None
39410                };
39411                match target {
39412                    DialectType::DuckDB => {
39413                        let duck_format = Self::bq_format_to_duckdb(&format);
39414                        let strptime = Expression::Function(Box::new(Function::new(
39415                            "STRPTIME".to_string(),
39416                            vec![str_expr, duck_format],
39417                        )));
39418                        Ok(strptime)
39419                    }
39420                    _ => {
39421                        let mut result_args = vec![format, str_expr];
39422                        if let Some(tz_arg) = tz {
39423                            result_args.push(tz_arg);
39424                        }
39425                        Ok(Expression::Function(Box::new(Function::new(
39426                            "PARSE_TIMESTAMP".to_string(),
39427                            result_args,
39428                        ))))
39429                    }
39430                }
39431            }
39432
39433            // FORMAT_DATE(format, date) -> target-specific
39434            "FORMAT_DATE" if args.len() == 2 => {
39435                let format = args.remove(0);
39436                let date_expr = args.remove(0);
39437                match target {
39438                    DialectType::DuckDB => {
39439                        // STRFTIME(CAST(date AS DATE), format)
39440                        let cast_date = Expression::Cast(Box::new(Cast {
39441                            this: date_expr,
39442                            to: DataType::Date,
39443                            trailing_comments: vec![],
39444                            double_colon_syntax: false,
39445                            format: None,
39446                            default: None,
39447                            inferred_type: None,
39448                        }));
39449                        Ok(Expression::Function(Box::new(Function::new(
39450                            "STRFTIME".to_string(),
39451                            vec![cast_date, format],
39452                        ))))
39453                    }
39454                    _ => Ok(Expression::Function(Box::new(Function::new(
39455                        "FORMAT_DATE".to_string(),
39456                        vec![format, date_expr],
39457                    )))),
39458                }
39459            }
39460
39461            // FORMAT_DATETIME(format, datetime) -> target-specific
39462            "FORMAT_DATETIME" if args.len() == 2 => {
39463                let format = args.remove(0);
39464                let dt_expr = args.remove(0);
39465
39466                if matches!(target, DialectType::BigQuery) {
39467                    // BQ->BQ: normalize %H:%M:%S to %T, %x to %D
39468                    let norm_format = Self::bq_format_normalize_bq(&format);
39469                    // Also strip DATETIME keyword from typed literals
39470                    let norm_dt = match dt_expr {
39471                        Expression::Literal(lit)
39472                            if matches!(lit.as_ref(), Literal::Timestamp(_)) =>
39473                        {
39474                            let Literal::Timestamp(s) = lit.as_ref() else {
39475                                unreachable!()
39476                            };
39477                            Expression::Cast(Box::new(Cast {
39478                                this: Expression::Literal(Box::new(Literal::String(s.clone()))),
39479                                to: DataType::Custom {
39480                                    name: "DATETIME".to_string(),
39481                                },
39482                                trailing_comments: vec![],
39483                                double_colon_syntax: false,
39484                                format: None,
39485                                default: None,
39486                                inferred_type: None,
39487                            }))
39488                        }
39489                        other => other,
39490                    };
39491                    return Ok(Expression::Function(Box::new(Function::new(
39492                        "FORMAT_DATETIME".to_string(),
39493                        vec![norm_format, norm_dt],
39494                    ))));
39495                }
39496
39497                match target {
39498                    DialectType::DuckDB => {
39499                        // STRFTIME(CAST(dt AS TIMESTAMP), duckdb_format)
39500                        let cast_dt = Self::ensure_cast_timestamp(dt_expr);
39501                        let duck_format = Self::bq_format_to_duckdb(&format);
39502                        Ok(Expression::Function(Box::new(Function::new(
39503                            "STRFTIME".to_string(),
39504                            vec![cast_dt, duck_format],
39505                        ))))
39506                    }
39507                    _ => Ok(Expression::Function(Box::new(Function::new(
39508                        "FORMAT_DATETIME".to_string(),
39509                        vec![format, dt_expr],
39510                    )))),
39511                }
39512            }
39513
39514            // FORMAT_TIMESTAMP(format, ts) -> target-specific
39515            "FORMAT_TIMESTAMP" if args.len() == 2 => {
39516                let format = args.remove(0);
39517                let ts_expr = args.remove(0);
39518                match target {
39519                    DialectType::DuckDB => {
39520                        // STRFTIME(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), format)
39521                        let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
39522                        let cast_ts = Expression::Cast(Box::new(Cast {
39523                            this: cast_tstz,
39524                            to: DataType::Timestamp {
39525                                timezone: false,
39526                                precision: None,
39527                            },
39528                            trailing_comments: vec![],
39529                            double_colon_syntax: false,
39530                            format: None,
39531                            default: None,
39532                            inferred_type: None,
39533                        }));
39534                        Ok(Expression::Function(Box::new(Function::new(
39535                            "STRFTIME".to_string(),
39536                            vec![cast_ts, format],
39537                        ))))
39538                    }
39539                    DialectType::Snowflake => {
39540                        // TO_CHAR(CAST(CAST(ts AS TIMESTAMPTZ) AS TIMESTAMP), snowflake_format)
39541                        let cast_tstz = Self::ensure_cast_timestamptz(ts_expr);
39542                        let cast_ts = Expression::Cast(Box::new(Cast {
39543                            this: cast_tstz,
39544                            to: DataType::Timestamp {
39545                                timezone: false,
39546                                precision: None,
39547                            },
39548                            trailing_comments: vec![],
39549                            double_colon_syntax: false,
39550                            format: None,
39551                            default: None,
39552                            inferred_type: None,
39553                        }));
39554                        let sf_format = Self::bq_format_to_snowflake(&format);
39555                        Ok(Expression::Function(Box::new(Function::new(
39556                            "TO_CHAR".to_string(),
39557                            vec![cast_ts, sf_format],
39558                        ))))
39559                    }
39560                    _ => Ok(Expression::Function(Box::new(Function::new(
39561                        "FORMAT_TIMESTAMP".to_string(),
39562                        vec![format, ts_expr],
39563                    )))),
39564                }
39565            }
39566
39567            // UNIX_DATE(date) -> DATE_DIFF('DAY', '1970-01-01', date) for DuckDB
39568            "UNIX_DATE" if args.len() == 1 => {
39569                let date = args.remove(0);
39570                match target {
39571                    DialectType::DuckDB => {
39572                        let epoch = Expression::Cast(Box::new(Cast {
39573                            this: Expression::Literal(Box::new(Literal::String(
39574                                "1970-01-01".to_string(),
39575                            ))),
39576                            to: DataType::Date,
39577                            trailing_comments: vec![],
39578                            double_colon_syntax: false,
39579                            format: None,
39580                            default: None,
39581                            inferred_type: None,
39582                        }));
39583                        // DATE_DIFF('DAY', epoch, date) but date might be DATE '...' literal
39584                        // Need to convert DATE literal to CAST
39585                        let norm_date = Self::date_literal_to_cast(date);
39586                        Ok(Expression::Function(Box::new(Function::new(
39587                            "DATE_DIFF".to_string(),
39588                            vec![
39589                                Expression::Literal(Box::new(Literal::String("DAY".to_string()))),
39590                                epoch,
39591                                norm_date,
39592                            ],
39593                        ))))
39594                    }
39595                    _ => Ok(Expression::Function(Box::new(Function::new(
39596                        "UNIX_DATE".to_string(),
39597                        vec![date],
39598                    )))),
39599                }
39600            }
39601
39602            // UNIX_SECONDS(ts) -> target-specific
39603            "UNIX_SECONDS" if args.len() == 1 => {
39604                let ts = args.remove(0);
39605                match target {
39606                    DialectType::DuckDB => {
39607                        // CAST(EPOCH(CAST(ts AS TIMESTAMPTZ)) AS BIGINT)
39608                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
39609                        let epoch = Expression::Function(Box::new(Function::new(
39610                            "EPOCH".to_string(),
39611                            vec![norm_ts],
39612                        )));
39613                        Ok(Expression::Cast(Box::new(Cast {
39614                            this: epoch,
39615                            to: DataType::BigInt { length: None },
39616                            trailing_comments: vec![],
39617                            double_colon_syntax: false,
39618                            format: None,
39619                            default: None,
39620                            inferred_type: None,
39621                        })))
39622                    }
39623                    DialectType::Snowflake => {
39624                        // TIMESTAMPDIFF(SECONDS, CAST('1970-01-01 00:00:00+00' AS TIMESTAMPTZ), ts)
39625                        let epoch = Expression::Cast(Box::new(Cast {
39626                            this: Expression::Literal(Box::new(Literal::String(
39627                                "1970-01-01 00:00:00+00".to_string(),
39628                            ))),
39629                            to: DataType::Timestamp {
39630                                timezone: true,
39631                                precision: None,
39632                            },
39633                            trailing_comments: vec![],
39634                            double_colon_syntax: false,
39635                            format: None,
39636                            default: None,
39637                            inferred_type: None,
39638                        }));
39639                        Ok(Expression::Function(Box::new(Function::new(
39640                            "TIMESTAMPDIFF".to_string(),
39641                            vec![
39642                                Expression::Identifier(Identifier::new("SECONDS".to_string())),
39643                                epoch,
39644                                ts,
39645                            ],
39646                        ))))
39647                    }
39648                    _ => Ok(Expression::Function(Box::new(Function::new(
39649                        "UNIX_SECONDS".to_string(),
39650                        vec![ts],
39651                    )))),
39652                }
39653            }
39654
39655            // UNIX_MILLIS(ts) -> target-specific
39656            "UNIX_MILLIS" if args.len() == 1 => {
39657                let ts = args.remove(0);
39658                match target {
39659                    DialectType::DuckDB => {
39660                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
39661                        Ok(Expression::Function(Box::new(Function::new(
39662                            "EPOCH_MS".to_string(),
39663                            vec![norm_ts],
39664                        ))))
39665                    }
39666                    _ => Ok(Expression::Function(Box::new(Function::new(
39667                        "UNIX_MILLIS".to_string(),
39668                        vec![ts],
39669                    )))),
39670                }
39671            }
39672
39673            // UNIX_MICROS(ts) -> target-specific
39674            "UNIX_MICROS" if args.len() == 1 => {
39675                let ts = args.remove(0);
39676                match target {
39677                    DialectType::DuckDB => {
39678                        let norm_ts = Self::ts_literal_to_cast_tz(ts);
39679                        Ok(Expression::Function(Box::new(Function::new(
39680                            "EPOCH_US".to_string(),
39681                            vec![norm_ts],
39682                        ))))
39683                    }
39684                    _ => Ok(Expression::Function(Box::new(Function::new(
39685                        "UNIX_MICROS".to_string(),
39686                        vec![ts],
39687                    )))),
39688                }
39689            }
39690
39691            // INSTR(str, substr) -> target-specific
39692            "INSTR" => {
39693                if matches!(target, DialectType::BigQuery) {
39694                    // BQ->BQ: keep as INSTR
39695                    Ok(Expression::Function(Box::new(Function::new(
39696                        "INSTR".to_string(),
39697                        args,
39698                    ))))
39699                } else if matches!(target, DialectType::Snowflake) && args.len() == 2 {
39700                    // Snowflake: CHARINDEX(substr, str) - swap args
39701                    let str_expr = args.remove(0);
39702                    let substr = args.remove(0);
39703                    Ok(Expression::Function(Box::new(Function::new(
39704                        "CHARINDEX".to_string(),
39705                        vec![substr, str_expr],
39706                    ))))
39707                } else {
39708                    // Keep as INSTR for other targets
39709                    Ok(Expression::Function(Box::new(Function::new(
39710                        "INSTR".to_string(),
39711                        args,
39712                    ))))
39713                }
39714            }
39715
39716            // CURRENT_TIMESTAMP / CURRENT_DATE handling - parens normalization and timezone
39717            "CURRENT_TIMESTAMP" | "CURRENT_DATE" | "CURRENT_DATETIME" | "CURRENT_TIME" => {
39718                if matches!(target, DialectType::BigQuery) {
39719                    // BQ->BQ: always output with parens (function form), keep any timezone arg
39720                    Ok(Expression::Function(Box::new(Function::new(name, args))))
39721                } else if name == "CURRENT_DATE" && args.len() == 1 {
39722                    // CURRENT_DATE('UTC') - has timezone arg
39723                    let tz_arg = args.remove(0);
39724                    match target {
39725                        DialectType::DuckDB => {
39726                            // CAST(CURRENT_TIMESTAMP AT TIME ZONE 'UTC' AS DATE)
39727                            let ct = Expression::CurrentTimestamp(
39728                                crate::expressions::CurrentTimestamp {
39729                                    precision: None,
39730                                    sysdate: false,
39731                                },
39732                            );
39733                            let at_tz =
39734                                Expression::AtTimeZone(Box::new(crate::expressions::AtTimeZone {
39735                                    this: ct,
39736                                    zone: tz_arg,
39737                                }));
39738                            Ok(Expression::Cast(Box::new(Cast {
39739                                this: at_tz,
39740                                to: DataType::Date,
39741                                trailing_comments: vec![],
39742                                double_colon_syntax: false,
39743                                format: None,
39744                                default: None,
39745                                inferred_type: None,
39746                            })))
39747                        }
39748                        DialectType::Snowflake => {
39749                            // CAST(CONVERT_TIMEZONE('UTC', CURRENT_TIMESTAMP()) AS DATE)
39750                            let ct = Expression::Function(Box::new(Function::new(
39751                                "CURRENT_TIMESTAMP".to_string(),
39752                                vec![],
39753                            )));
39754                            let convert = Expression::Function(Box::new(Function::new(
39755                                "CONVERT_TIMEZONE".to_string(),
39756                                vec![tz_arg, ct],
39757                            )));
39758                            Ok(Expression::Cast(Box::new(Cast {
39759                                this: convert,
39760                                to: DataType::Date,
39761                                trailing_comments: vec![],
39762                                double_colon_syntax: false,
39763                                format: None,
39764                                default: None,
39765                                inferred_type: None,
39766                            })))
39767                        }
39768                        _ => {
39769                            // PostgreSQL, MySQL, etc.: CURRENT_DATE AT TIME ZONE 'UTC'
39770                            let cd = Expression::CurrentDate(crate::expressions::CurrentDate);
39771                            Ok(Expression::AtTimeZone(Box::new(
39772                                crate::expressions::AtTimeZone {
39773                                    this: cd,
39774                                    zone: tz_arg,
39775                                },
39776                            )))
39777                        }
39778                    }
39779                } else if (name == "CURRENT_TIMESTAMP"
39780                    || name == "CURRENT_TIME"
39781                    || name == "CURRENT_DATE")
39782                    && args.is_empty()
39783                    && matches!(
39784                        target,
39785                        DialectType::PostgreSQL
39786                            | DialectType::DuckDB
39787                            | DialectType::Presto
39788                            | DialectType::Trino
39789                    )
39790                {
39791                    // These targets want no-parens CURRENT_TIMESTAMP / CURRENT_DATE / CURRENT_TIME
39792                    if name == "CURRENT_TIMESTAMP" {
39793                        Ok(Expression::CurrentTimestamp(
39794                            crate::expressions::CurrentTimestamp {
39795                                precision: None,
39796                                sysdate: false,
39797                            },
39798                        ))
39799                    } else if name == "CURRENT_DATE" {
39800                        Ok(Expression::CurrentDate(crate::expressions::CurrentDate))
39801                    } else {
39802                        // CURRENT_TIME
39803                        Ok(Expression::CurrentTime(crate::expressions::CurrentTime {
39804                            precision: None,
39805                        }))
39806                    }
39807                } else {
39808                    // All other targets: keep as function (with parens)
39809                    Ok(Expression::Function(Box::new(Function::new(name, args))))
39810                }
39811            }
39812
39813            // JSON_QUERY(json, path) -> target-specific
39814            "JSON_QUERY" if args.len() == 2 => {
39815                match target {
39816                    DialectType::DuckDB | DialectType::SQLite => {
39817                        // json -> path syntax
39818                        let json_expr = args.remove(0);
39819                        let path = args.remove(0);
39820                        Ok(Expression::JsonExtract(Box::new(
39821                            crate::expressions::JsonExtractFunc {
39822                                this: json_expr,
39823                                path,
39824                                returning: None,
39825                                arrow_syntax: true,
39826                                hash_arrow_syntax: false,
39827                                wrapper_option: None,
39828                                quotes_option: None,
39829                                on_scalar_string: false,
39830                                on_error: None,
39831                            },
39832                        )))
39833                    }
39834                    DialectType::Spark | DialectType::Databricks | DialectType::Hive => {
39835                        Ok(Expression::Function(Box::new(Function::new(
39836                            "GET_JSON_OBJECT".to_string(),
39837                            args,
39838                        ))))
39839                    }
39840                    DialectType::PostgreSQL | DialectType::Redshift => Ok(Expression::Function(
39841                        Box::new(Function::new("JSON_EXTRACT_PATH".to_string(), args)),
39842                    )),
39843                    _ => Ok(Expression::Function(Box::new(Function::new(
39844                        "JSON_QUERY".to_string(),
39845                        args,
39846                    )))),
39847                }
39848            }
39849
39850            // JSON_VALUE_ARRAY(json, path) -> target-specific
39851            "JSON_VALUE_ARRAY" if args.len() == 2 => {
39852                match target {
39853                    DialectType::DuckDB => {
39854                        // CAST(json -> path AS TEXT[])
39855                        let json_expr = args.remove(0);
39856                        let path = args.remove(0);
39857                        let arrow = Expression::JsonExtract(Box::new(
39858                            crate::expressions::JsonExtractFunc {
39859                                this: json_expr,
39860                                path,
39861                                returning: None,
39862                                arrow_syntax: true,
39863                                hash_arrow_syntax: false,
39864                                wrapper_option: None,
39865                                quotes_option: None,
39866                                on_scalar_string: false,
39867                                on_error: None,
39868                            },
39869                        ));
39870                        Ok(Expression::Cast(Box::new(Cast {
39871                            this: arrow,
39872                            to: DataType::Array {
39873                                element_type: Box::new(DataType::Text),
39874                                dimension: None,
39875                            },
39876                            trailing_comments: vec![],
39877                            double_colon_syntax: false,
39878                            format: None,
39879                            default: None,
39880                            inferred_type: None,
39881                        })))
39882                    }
39883                    DialectType::Snowflake => {
39884                        let json_expr = args.remove(0);
39885                        let path_expr = args.remove(0);
39886                        // Convert JSON path from $.path to just path
39887                        let sf_path = if let Expression::Literal(ref lit) = path_expr {
39888                            if let Literal::String(ref s) = lit.as_ref() {
39889                                let trimmed = s.trim_start_matches('$').trim_start_matches('.');
39890                                Expression::Literal(Box::new(Literal::String(trimmed.to_string())))
39891                            } else {
39892                                path_expr.clone()
39893                            }
39894                        } else {
39895                            path_expr
39896                        };
39897                        let parse_json = Expression::Function(Box::new(Function::new(
39898                            "PARSE_JSON".to_string(),
39899                            vec![json_expr],
39900                        )));
39901                        let get_path = Expression::Function(Box::new(Function::new(
39902                            "GET_PATH".to_string(),
39903                            vec![parse_json, sf_path],
39904                        )));
39905                        // TRANSFORM(get_path, x -> CAST(x AS VARCHAR))
39906                        let cast_expr = Expression::Cast(Box::new(Cast {
39907                            this: Expression::Identifier(Identifier::new("x")),
39908                            to: DataType::VarChar {
39909                                length: None,
39910                                parenthesized_length: false,
39911                            },
39912                            trailing_comments: vec![],
39913                            double_colon_syntax: false,
39914                            format: None,
39915                            default: None,
39916                            inferred_type: None,
39917                        }));
39918                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
39919                            parameters: vec![Identifier::new("x")],
39920                            body: cast_expr,
39921                            colon: false,
39922                            parameter_types: vec![],
39923                        }));
39924                        Ok(Expression::Function(Box::new(Function::new(
39925                            "TRANSFORM".to_string(),
39926                            vec![get_path, lambda],
39927                        ))))
39928                    }
39929                    _ => Ok(Expression::Function(Box::new(Function::new(
39930                        "JSON_VALUE_ARRAY".to_string(),
39931                        args,
39932                    )))),
39933                }
39934            }
39935
39936            // BigQuery REGEXP_EXTRACT(val, regex[, position[, occurrence]]) -> target dialects
39937            // BigQuery's 3rd arg is "position" (starting char index), 4th is "occurrence" (which match to return)
39938            // This is different from Hive/Spark where 3rd arg is "group_index"
39939            "REGEXP_EXTRACT" if matches!(source, DialectType::BigQuery) => {
39940                match target {
39941                    DialectType::DuckDB
39942                    | DialectType::Presto
39943                    | DialectType::Trino
39944                    | DialectType::Athena => {
39945                        if args.len() == 2 {
39946                            // REGEXP_EXTRACT(val, regex) -> REGEXP_EXTRACT(val, regex, 1)
39947                            args.push(Expression::number(1));
39948                            Ok(Expression::Function(Box::new(Function::new(
39949                                "REGEXP_EXTRACT".to_string(),
39950                                args,
39951                            ))))
39952                        } else if args.len() == 3 {
39953                            let val = args.remove(0);
39954                            let regex = args.remove(0);
39955                            let position = args.remove(0);
39956                            let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39957                            if is_pos_1 {
39958                                Ok(Expression::Function(Box::new(Function::new(
39959                                    "REGEXP_EXTRACT".to_string(),
39960                                    vec![val, regex, Expression::number(1)],
39961                                ))))
39962                            } else {
39963                                let substring_expr = Expression::Function(Box::new(Function::new(
39964                                    "SUBSTRING".to_string(),
39965                                    vec![val, position],
39966                                )));
39967                                let nullif_expr = Expression::Function(Box::new(Function::new(
39968                                    "NULLIF".to_string(),
39969                                    vec![
39970                                        substring_expr,
39971                                        Expression::Literal(Box::new(Literal::String(
39972                                            String::new(),
39973                                        ))),
39974                                    ],
39975                                )));
39976                                Ok(Expression::Function(Box::new(Function::new(
39977                                    "REGEXP_EXTRACT".to_string(),
39978                                    vec![nullif_expr, regex, Expression::number(1)],
39979                                ))))
39980                            }
39981                        } else if args.len() == 4 {
39982                            let val = args.remove(0);
39983                            let regex = args.remove(0);
39984                            let position = args.remove(0);
39985                            let occurrence = args.remove(0);
39986                            let is_pos_1 = matches!(&position, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39987                            let is_occ_1 = matches!(&occurrence, Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(n) if n == "1"));
39988                            if is_pos_1 && is_occ_1 {
39989                                Ok(Expression::Function(Box::new(Function::new(
39990                                    "REGEXP_EXTRACT".to_string(),
39991                                    vec![val, regex, Expression::number(1)],
39992                                ))))
39993                            } else {
39994                                let subject = if is_pos_1 {
39995                                    val
39996                                } else {
39997                                    let substring_expr = Expression::Function(Box::new(
39998                                        Function::new("SUBSTRING".to_string(), vec![val, position]),
39999                                    ));
40000                                    Expression::Function(Box::new(Function::new(
40001                                        "NULLIF".to_string(),
40002                                        vec![
40003                                            substring_expr,
40004                                            Expression::Literal(Box::new(Literal::String(
40005                                                String::new(),
40006                                            ))),
40007                                        ],
40008                                    )))
40009                                };
40010                                let extract_all = Expression::Function(Box::new(Function::new(
40011                                    "REGEXP_EXTRACT_ALL".to_string(),
40012                                    vec![subject, regex, Expression::number(1)],
40013                                )));
40014                                Ok(Expression::Function(Box::new(Function::new(
40015                                    "ARRAY_EXTRACT".to_string(),
40016                                    vec![extract_all, occurrence],
40017                                ))))
40018                            }
40019                        } else {
40020                            Ok(Expression::Function(Box::new(Function {
40021                                name: f.name,
40022                                args,
40023                                distinct: f.distinct,
40024                                trailing_comments: f.trailing_comments,
40025                                use_bracket_syntax: f.use_bracket_syntax,
40026                                no_parens: f.no_parens,
40027                                quoted: f.quoted,
40028                                span: None,
40029                                inferred_type: None,
40030                            })))
40031                        }
40032                    }
40033                    DialectType::Snowflake => {
40034                        // BigQuery REGEXP_EXTRACT -> Snowflake REGEXP_SUBSTR
40035                        Ok(Expression::Function(Box::new(Function::new(
40036                            "REGEXP_SUBSTR".to_string(),
40037                            args,
40038                        ))))
40039                    }
40040                    _ => {
40041                        // For other targets (Hive/Spark/BigQuery): pass through as-is
40042                        // BigQuery's default group behavior matches Hive/Spark for 2-arg case
40043                        Ok(Expression::Function(Box::new(Function {
40044                            name: f.name,
40045                            args,
40046                            distinct: f.distinct,
40047                            trailing_comments: f.trailing_comments,
40048                            use_bracket_syntax: f.use_bracket_syntax,
40049                            no_parens: f.no_parens,
40050                            quoted: f.quoted,
40051                            span: None,
40052                            inferred_type: None,
40053                        })))
40054                    }
40055                }
40056            }
40057
40058            // BigQuery STRUCT(args) -> target-specific struct expression
40059            "STRUCT" => {
40060                // Convert Function args to Struct fields
40061                let mut fields: Vec<(Option<String>, Expression)> = Vec::new();
40062                for (i, arg) in args.into_iter().enumerate() {
40063                    match arg {
40064                        Expression::Alias(a) => {
40065                            // Named field: expr AS name
40066                            fields.push((Some(a.alias.name.clone()), a.this));
40067                        }
40068                        other => {
40069                            // Unnamed field: for Spark/Hive, keep as None
40070                            // For Snowflake, auto-name as _N
40071                            // For DuckDB, use column name for column refs, _N for others
40072                            if matches!(target, DialectType::Snowflake) {
40073                                fields.push((Some(format!("_{}", i)), other));
40074                            } else if matches!(target, DialectType::DuckDB) {
40075                                let auto_name = match &other {
40076                                    Expression::Column(col) => col.name.name.clone(),
40077                                    _ => format!("_{}", i),
40078                                };
40079                                fields.push((Some(auto_name), other));
40080                            } else {
40081                                fields.push((None, other));
40082                            }
40083                        }
40084                    }
40085                }
40086
40087                match target {
40088                    DialectType::Snowflake => {
40089                        // OBJECT_CONSTRUCT('name', value, ...)
40090                        let mut oc_args = Vec::new();
40091                        for (name, val) in &fields {
40092                            if let Some(n) = name {
40093                                oc_args.push(Expression::Literal(Box::new(Literal::String(
40094                                    n.clone(),
40095                                ))));
40096                                oc_args.push(val.clone());
40097                            } else {
40098                                oc_args.push(val.clone());
40099                            }
40100                        }
40101                        Ok(Expression::Function(Box::new(Function::new(
40102                            "OBJECT_CONSTRUCT".to_string(),
40103                            oc_args,
40104                        ))))
40105                    }
40106                    DialectType::DuckDB => {
40107                        // {'name': value, ...}
40108                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40109                            fields,
40110                        })))
40111                    }
40112                    DialectType::Hive => {
40113                        // STRUCT(val1, val2, ...) - strip aliases
40114                        let hive_fields: Vec<(Option<String>, Expression)> =
40115                            fields.into_iter().map(|(_, v)| (None, v)).collect();
40116                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40117                            fields: hive_fields,
40118                        })))
40119                    }
40120                    DialectType::Spark | DialectType::Databricks => {
40121                        // Use Expression::Struct to bypass Spark target transform auto-naming
40122                        Ok(Expression::Struct(Box::new(crate::expressions::Struct {
40123                            fields,
40124                        })))
40125                    }
40126                    DialectType::Presto | DialectType::Trino | DialectType::Athena => {
40127                        // Check if all fields are named AND all have inferable types - if so, wrap in CAST(ROW(...) AS ROW(name TYPE, ...))
40128                        let all_named =
40129                            !fields.is_empty() && fields.iter().all(|(name, _)| name.is_some());
40130                        let all_types_inferable = all_named
40131                            && fields
40132                                .iter()
40133                                .all(|(_, val)| Self::can_infer_presto_type(val));
40134                        let row_args: Vec<Expression> =
40135                            fields.iter().map(|(_, v)| v.clone()).collect();
40136                        let row_expr = Expression::Function(Box::new(Function::new(
40137                            "ROW".to_string(),
40138                            row_args,
40139                        )));
40140                        if all_named && all_types_inferable {
40141                            // Build ROW type with inferred types
40142                            let mut row_type_fields = Vec::new();
40143                            for (name, val) in &fields {
40144                                if let Some(n) = name {
40145                                    let type_str = Self::infer_sql_type_for_presto(val);
40146                                    row_type_fields.push(crate::expressions::StructField::new(
40147                                        n.clone(),
40148                                        crate::expressions::DataType::Custom { name: type_str },
40149                                    ));
40150                                }
40151                            }
40152                            let row_type = crate::expressions::DataType::Struct {
40153                                fields: row_type_fields,
40154                                nested: true,
40155                            };
40156                            Ok(Expression::Cast(Box::new(Cast {
40157                                this: row_expr,
40158                                to: row_type,
40159                                trailing_comments: Vec::new(),
40160                                double_colon_syntax: false,
40161                                format: None,
40162                                default: None,
40163                                inferred_type: None,
40164                            })))
40165                        } else {
40166                            Ok(row_expr)
40167                        }
40168                    }
40169                    _ => {
40170                        // Default: keep as STRUCT function with original args
40171                        let mut new_args = Vec::new();
40172                        for (name, val) in fields {
40173                            if let Some(n) = name {
40174                                new_args.push(Expression::Alias(Box::new(
40175                                    crate::expressions::Alias::new(val, Identifier::new(n)),
40176                                )));
40177                            } else {
40178                                new_args.push(val);
40179                            }
40180                        }
40181                        Ok(Expression::Function(Box::new(Function::new(
40182                            "STRUCT".to_string(),
40183                            new_args,
40184                        ))))
40185                    }
40186                }
40187            }
40188
40189            // ROUND(x, n, 'ROUND_HALF_EVEN') -> ROUND_EVEN(x, n) for DuckDB
40190            "ROUND" if args.len() == 3 => {
40191                let x = args.remove(0);
40192                let n = args.remove(0);
40193                let mode = args.remove(0);
40194                // Check if mode is 'ROUND_HALF_EVEN'
40195                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")));
40196                if is_half_even && matches!(target, DialectType::DuckDB) {
40197                    Ok(Expression::Function(Box::new(Function::new(
40198                        "ROUND_EVEN".to_string(),
40199                        vec![x, n],
40200                    ))))
40201                } else {
40202                    // Pass through with all args
40203                    Ok(Expression::Function(Box::new(Function::new(
40204                        "ROUND".to_string(),
40205                        vec![x, n, mode],
40206                    ))))
40207                }
40208            }
40209
40210            // MAKE_INTERVAL(year, month, named_args...) -> INTERVAL string for Snowflake/DuckDB
40211            "MAKE_INTERVAL" => {
40212                // MAKE_INTERVAL(1, 2, minute => 5, day => 3)
40213                // The positional args are: year, month
40214                // Named args are: day =>, minute =>, etc.
40215                // For Snowflake: INTERVAL '1 year, 2 month, 5 minute, 3 day'
40216                // For DuckDB: INTERVAL '1 year 2 month 5 minute 3 day'
40217                // For BigQuery->BigQuery: reorder named args (day before minute)
40218                if matches!(target, DialectType::Snowflake | DialectType::DuckDB) {
40219                    let mut parts: Vec<(String, String)> = Vec::new();
40220                    let mut pos_idx = 0;
40221                    let pos_units = ["year", "month"];
40222                    for arg in &args {
40223                        if let Expression::NamedArgument(na) = arg {
40224                            // Named arg like minute => 5
40225                            let unit = na.name.name.clone();
40226                            if let Expression::Literal(lit) = &na.value {
40227                                if let Literal::Number(n) = lit.as_ref() {
40228                                    parts.push((unit, n.clone()));
40229                                }
40230                            }
40231                        } else if pos_idx < pos_units.len() {
40232                            if let Expression::Literal(lit) = arg {
40233                                if let Literal::Number(n) = lit.as_ref() {
40234                                    parts.push((pos_units[pos_idx].to_string(), n.clone()));
40235                                }
40236                            }
40237                            pos_idx += 1;
40238                        }
40239                    }
40240                    // Don't sort - preserve original argument order
40241                    let separator = if matches!(target, DialectType::Snowflake) {
40242                        ", "
40243                    } else {
40244                        " "
40245                    };
40246                    let interval_str = parts
40247                        .iter()
40248                        .map(|(u, v)| format!("{} {}", v, u))
40249                        .collect::<Vec<_>>()
40250                        .join(separator);
40251                    Ok(Expression::Interval(Box::new(
40252                        crate::expressions::Interval {
40253                            this: Some(Expression::Literal(Box::new(Literal::String(
40254                                interval_str,
40255                            )))),
40256                            unit: None,
40257                        },
40258                    )))
40259                } else if matches!(target, DialectType::BigQuery) {
40260                    // BigQuery->BigQuery: reorder named args (day, minute, etc.)
40261                    let mut positional = Vec::new();
40262                    let mut named: Vec<(
40263                        String,
40264                        Expression,
40265                        crate::expressions::NamedArgSeparator,
40266                    )> = Vec::new();
40267                    let _pos_units = ["year", "month"];
40268                    let mut _pos_idx = 0;
40269                    for arg in args {
40270                        if let Expression::NamedArgument(na) = arg {
40271                            named.push((na.name.name.clone(), na.value, na.separator));
40272                        } else {
40273                            positional.push(arg);
40274                            _pos_idx += 1;
40275                        }
40276                    }
40277                    // Sort named args by: day, hour, minute, second
40278                    let unit_order = |u: &str| -> usize {
40279                        match u.to_ascii_lowercase().as_str() {
40280                            "day" => 0,
40281                            "hour" => 1,
40282                            "minute" => 2,
40283                            "second" => 3,
40284                            _ => 4,
40285                        }
40286                    };
40287                    named.sort_by_key(|(u, _, _)| unit_order(u));
40288                    let mut result_args = positional;
40289                    for (name, value, sep) in named {
40290                        result_args.push(Expression::NamedArgument(Box::new(
40291                            crate::expressions::NamedArgument {
40292                                name: Identifier::new(&name),
40293                                value,
40294                                separator: sep,
40295                            },
40296                        )));
40297                    }
40298                    Ok(Expression::Function(Box::new(Function::new(
40299                        "MAKE_INTERVAL".to_string(),
40300                        result_args,
40301                    ))))
40302                } else {
40303                    Ok(Expression::Function(Box::new(Function::new(
40304                        "MAKE_INTERVAL".to_string(),
40305                        args,
40306                    ))))
40307                }
40308            }
40309
40310            // ARRAY_TO_STRING(array, sep, null_text) -> ARRAY_TO_STRING(LIST_TRANSFORM(array, x -> COALESCE(x, null_text)), sep) for DuckDB
40311            "ARRAY_TO_STRING" if args.len() == 3 => {
40312                let arr = args.remove(0);
40313                let sep = args.remove(0);
40314                let null_text = args.remove(0);
40315                match target {
40316                    DialectType::DuckDB => {
40317                        // LIST_TRANSFORM(array, x -> COALESCE(x, null_text))
40318                        let _lambda_param =
40319                            Expression::Identifier(crate::expressions::Identifier::new("x"));
40320                        let coalesce =
40321                            Expression::Coalesce(Box::new(crate::expressions::VarArgFunc {
40322                                original_name: None,
40323                                expressions: vec![
40324                                    Expression::Identifier(crate::expressions::Identifier::new(
40325                                        "x",
40326                                    )),
40327                                    null_text,
40328                                ],
40329                                inferred_type: None,
40330                            }));
40331                        let lambda = Expression::Lambda(Box::new(crate::expressions::LambdaExpr {
40332                            parameters: vec![crate::expressions::Identifier::new("x")],
40333                            body: coalesce,
40334                            colon: false,
40335                            parameter_types: vec![],
40336                        }));
40337                        let list_transform = Expression::Function(Box::new(Function::new(
40338                            "LIST_TRANSFORM".to_string(),
40339                            vec![arr, lambda],
40340                        )));
40341                        Ok(Expression::Function(Box::new(Function::new(
40342                            "ARRAY_TO_STRING".to_string(),
40343                            vec![list_transform, sep],
40344                        ))))
40345                    }
40346                    _ => Ok(Expression::Function(Box::new(Function::new(
40347                        "ARRAY_TO_STRING".to_string(),
40348                        vec![arr, sep, null_text],
40349                    )))),
40350                }
40351            }
40352
40353            // LENGTH(x) -> CASE TYPEOF(x) ... for DuckDB
40354            "LENGTH" if args.len() == 1 => {
40355                let arg = args.remove(0);
40356                match target {
40357                    DialectType::DuckDB => {
40358                        // CASE TYPEOF(foo) WHEN 'BLOB' THEN OCTET_LENGTH(CAST(foo AS BLOB)) ELSE LENGTH(CAST(foo AS TEXT)) END
40359                        let typeof_func = Expression::Function(Box::new(Function::new(
40360                            "TYPEOF".to_string(),
40361                            vec![arg.clone()],
40362                        )));
40363                        let blob_cast = Expression::Cast(Box::new(Cast {
40364                            this: arg.clone(),
40365                            to: DataType::VarBinary { length: None },
40366                            trailing_comments: vec![],
40367                            double_colon_syntax: false,
40368                            format: None,
40369                            default: None,
40370                            inferred_type: None,
40371                        }));
40372                        let octet_length = Expression::Function(Box::new(Function::new(
40373                            "OCTET_LENGTH".to_string(),
40374                            vec![blob_cast],
40375                        )));
40376                        let text_cast = Expression::Cast(Box::new(Cast {
40377                            this: arg,
40378                            to: DataType::Text,
40379                            trailing_comments: vec![],
40380                            double_colon_syntax: false,
40381                            format: None,
40382                            default: None,
40383                            inferred_type: None,
40384                        }));
40385                        let length_text = Expression::Function(Box::new(Function::new(
40386                            "LENGTH".to_string(),
40387                            vec![text_cast],
40388                        )));
40389                        Ok(Expression::Case(Box::new(crate::expressions::Case {
40390                            operand: Some(typeof_func),
40391                            whens: vec![(
40392                                Expression::Literal(Box::new(Literal::String("BLOB".to_string()))),
40393                                octet_length,
40394                            )],
40395                            else_: Some(length_text),
40396                            comments: Vec::new(),
40397                            inferred_type: None,
40398                        })))
40399                    }
40400                    _ => Ok(Expression::Function(Box::new(Function::new(
40401                        "LENGTH".to_string(),
40402                        vec![arg],
40403                    )))),
40404                }
40405            }
40406
40407            // PERCENTILE_CONT(x, fraction RESPECT NULLS) -> QUANTILE_CONT(x, fraction) for DuckDB
40408            "PERCENTILE_CONT" if args.len() >= 2 && matches!(source, DialectType::BigQuery) => {
40409                // BigQuery PERCENTILE_CONT(x, fraction [RESPECT|IGNORE NULLS]) OVER ()
40410                // The args should be [x, fraction] with the null handling stripped
40411                // For DuckDB: QUANTILE_CONT(x, fraction)
40412                // For Spark: PERCENTILE_CONT(x, fraction) RESPECT NULLS (handled at window level)
40413                match target {
40414                    DialectType::DuckDB => {
40415                        // Strip down to just 2 args, rename to QUANTILE_CONT
40416                        let x = args[0].clone();
40417                        let frac = args[1].clone();
40418                        Ok(Expression::Function(Box::new(Function::new(
40419                            "QUANTILE_CONT".to_string(),
40420                            vec![x, frac],
40421                        ))))
40422                    }
40423                    _ => Ok(Expression::Function(Box::new(Function::new(
40424                        "PERCENTILE_CONT".to_string(),
40425                        args,
40426                    )))),
40427                }
40428            }
40429
40430            // All others: pass through
40431            _ => Ok(Expression::Function(Box::new(Function {
40432                name: f.name,
40433                args,
40434                distinct: f.distinct,
40435                trailing_comments: f.trailing_comments,
40436                use_bracket_syntax: f.use_bracket_syntax,
40437                no_parens: f.no_parens,
40438                quoted: f.quoted,
40439                span: None,
40440                inferred_type: None,
40441            }))),
40442        }
40443    }
40444
40445    /// Check if we can reliably infer the SQL type for Presto/Trino ROW CAST.
40446    /// Returns false for column references and other non-literal expressions where the type is unknown.
40447    fn can_infer_presto_type(expr: &Expression) -> bool {
40448        match expr {
40449            Expression::Literal(_) => true,
40450            Expression::Boolean(_) => true,
40451            Expression::Array(_) | Expression::ArrayFunc(_) => true,
40452            Expression::Struct(_) | Expression::StructFunc(_) => true,
40453            Expression::Function(f) => {
40454                f.name.eq_ignore_ascii_case("STRUCT")
40455                    || f.name.eq_ignore_ascii_case("ROW")
40456                    || f.name.eq_ignore_ascii_case("CURRENT_DATE")
40457                    || f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
40458                    || f.name.eq_ignore_ascii_case("NOW")
40459            }
40460            Expression::Cast(_) => true,
40461            Expression::Neg(inner) => Self::can_infer_presto_type(&inner.this),
40462            _ => false,
40463        }
40464    }
40465
40466    /// Infer SQL type name for a Presto/Trino ROW CAST from a literal expression
40467    fn infer_sql_type_for_presto(expr: &Expression) -> String {
40468        use crate::expressions::Literal;
40469        match expr {
40470            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40471                "VARCHAR".to_string()
40472            }
40473            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40474                let Literal::Number(n) = lit.as_ref() else {
40475                    unreachable!()
40476                };
40477                if n.contains('.') {
40478                    "DOUBLE".to_string()
40479                } else {
40480                    "INTEGER".to_string()
40481                }
40482            }
40483            Expression::Boolean(_) => "BOOLEAN".to_string(),
40484            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40485                "DATE".to_string()
40486            }
40487            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40488                "TIMESTAMP".to_string()
40489            }
40490            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
40491                "TIMESTAMP".to_string()
40492            }
40493            Expression::Array(_) | Expression::ArrayFunc(_) => "ARRAY(VARCHAR)".to_string(),
40494            Expression::Struct(_) | Expression::StructFunc(_) => "ROW".to_string(),
40495            Expression::Function(f) => {
40496                if f.name.eq_ignore_ascii_case("STRUCT") || f.name.eq_ignore_ascii_case("ROW") {
40497                    "ROW".to_string()
40498                } else if f.name.eq_ignore_ascii_case("CURRENT_DATE") {
40499                    "DATE".to_string()
40500                } else if f.name.eq_ignore_ascii_case("CURRENT_TIMESTAMP")
40501                    || f.name.eq_ignore_ascii_case("NOW")
40502                {
40503                    "TIMESTAMP".to_string()
40504                } else {
40505                    "VARCHAR".to_string()
40506                }
40507            }
40508            Expression::Cast(c) => {
40509                // If already cast, use the target type
40510                Self::data_type_to_presto_string(&c.to)
40511            }
40512            _ => "VARCHAR".to_string(),
40513        }
40514    }
40515
40516    /// Convert a DataType to its Presto/Trino string representation for ROW type
40517    fn data_type_to_presto_string(dt: &crate::expressions::DataType) -> String {
40518        use crate::expressions::DataType;
40519        match dt {
40520            DataType::VarChar { .. } | DataType::Text | DataType::String { .. } => {
40521                "VARCHAR".to_string()
40522            }
40523            DataType::Int { .. }
40524            | DataType::BigInt { .. }
40525            | DataType::SmallInt { .. }
40526            | DataType::TinyInt { .. } => "INTEGER".to_string(),
40527            DataType::Float { .. } | DataType::Double { .. } => "DOUBLE".to_string(),
40528            DataType::Boolean => "BOOLEAN".to_string(),
40529            DataType::Date => "DATE".to_string(),
40530            DataType::Timestamp { .. } => "TIMESTAMP".to_string(),
40531            DataType::Struct { fields, .. } => {
40532                let field_strs: Vec<String> = fields
40533                    .iter()
40534                    .map(|f| {
40535                        format!(
40536                            "{} {}",
40537                            f.name,
40538                            Self::data_type_to_presto_string(&f.data_type)
40539                        )
40540                    })
40541                    .collect();
40542                format!("ROW({})", field_strs.join(", "))
40543            }
40544            DataType::Array { element_type, .. } => {
40545                format!("ARRAY({})", Self::data_type_to_presto_string(element_type))
40546            }
40547            DataType::Custom { name } => {
40548                // Pass through custom type names (e.g., "INTEGER", "VARCHAR" from earlier inference)
40549                name.clone()
40550            }
40551            _ => "VARCHAR".to_string(),
40552        }
40553    }
40554
40555    /// Convert IntervalUnit to string
40556    fn interval_unit_to_string(unit: &crate::expressions::IntervalUnit) -> &'static str {
40557        match unit {
40558            crate::expressions::IntervalUnit::Year => "YEAR",
40559            crate::expressions::IntervalUnit::Quarter => "QUARTER",
40560            crate::expressions::IntervalUnit::Month => "MONTH",
40561            crate::expressions::IntervalUnit::Week => "WEEK",
40562            crate::expressions::IntervalUnit::Day => "DAY",
40563            crate::expressions::IntervalUnit::Hour => "HOUR",
40564            crate::expressions::IntervalUnit::Minute => "MINUTE",
40565            crate::expressions::IntervalUnit::Second => "SECOND",
40566            crate::expressions::IntervalUnit::Millisecond => "MILLISECOND",
40567            crate::expressions::IntervalUnit::Microsecond => "MICROSECOND",
40568            crate::expressions::IntervalUnit::Nanosecond => "NANOSECOND",
40569        }
40570    }
40571
40572    /// Extract unit string from an expression (uppercased)
40573    fn get_unit_str_static(expr: &Expression) -> String {
40574        use crate::expressions::Literal;
40575        match expr {
40576            Expression::Identifier(id) => id.name.to_ascii_uppercase(),
40577            Expression::Var(v) => v.this.to_ascii_uppercase(),
40578            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40579                let Literal::String(s) = lit.as_ref() else {
40580                    unreachable!()
40581                };
40582                s.to_ascii_uppercase()
40583            }
40584            Expression::Cast(cast)
40585                if cast.format.is_none()
40586                    && cast.default.is_none()
40587                    && Self::unit_cast_target_is_string(&cast.to)
40588                    && matches!(
40589                        &cast.this,
40590                        Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_))
40591                    ) =>
40592            {
40593                let Expression::Literal(lit) = &cast.this else {
40594                    unreachable!()
40595                };
40596                let Literal::String(s) = lit.as_ref() else {
40597                    unreachable!()
40598                };
40599                s.to_ascii_uppercase()
40600            }
40601            Expression::Column(col) => col.name.name.to_ascii_uppercase(),
40602            Expression::Function(f) => {
40603                let base = f.name.to_ascii_uppercase();
40604                if !f.args.is_empty() {
40605                    let inner = Self::get_unit_str_static(&f.args[0]);
40606                    format!("{}({})", base, inner)
40607                } else {
40608                    base
40609                }
40610            }
40611            _ => "DAY".to_string(),
40612        }
40613    }
40614
40615    fn unit_cast_target_is_string(data_type: &DataType) -> bool {
40616        matches!(
40617            data_type,
40618            DataType::Char { .. }
40619                | DataType::VarChar { .. }
40620                | DataType::String { .. }
40621                | DataType::Text
40622                | DataType::TextWithLength { .. }
40623        ) || matches!(
40624            data_type,
40625            DataType::Custom { name, .. }
40626                if name.eq_ignore_ascii_case("VARCHAR")
40627                    || name.eq_ignore_ascii_case("NVARCHAR")
40628                    || name.eq_ignore_ascii_case("VARCHAR(MAX)")
40629                    || name.eq_ignore_ascii_case("NVARCHAR(MAX)")
40630        )
40631    }
40632
40633    /// Parse unit string to IntervalUnit
40634    fn parse_interval_unit_static(s: &str) -> crate::expressions::IntervalUnit {
40635        match s {
40636            "YEAR" | "YY" | "YYYY" => crate::expressions::IntervalUnit::Year,
40637            "QUARTER" | "QQ" | "Q" => crate::expressions::IntervalUnit::Quarter,
40638            "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" | "M" => {
40639                crate::expressions::IntervalUnit::Month
40640            }
40641            "WEEK" | "WK" | "WW" | "ISOWEEK" => crate::expressions::IntervalUnit::Week,
40642            "DAY" | "DD" | "D" | "DY" => crate::expressions::IntervalUnit::Day,
40643            "HOUR" | "HH" => crate::expressions::IntervalUnit::Hour,
40644            "MINUTE" | "MI" | "N" => crate::expressions::IntervalUnit::Minute,
40645            "SECOND" | "SS" | "S" => crate::expressions::IntervalUnit::Second,
40646            "MILLISECOND" | "MS" => crate::expressions::IntervalUnit::Millisecond,
40647            "MICROSECOND" | "MCS" | "US" => crate::expressions::IntervalUnit::Microsecond,
40648            _ if s.starts_with("WEEK(") => crate::expressions::IntervalUnit::Week,
40649            _ => crate::expressions::IntervalUnit::Day,
40650        }
40651    }
40652
40653    /// Convert expression to simple string for interval building
40654    fn expr_to_string_static(expr: &Expression) -> String {
40655        use crate::expressions::Literal;
40656        match expr {
40657            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40658                let Literal::Number(s) = lit.as_ref() else {
40659                    unreachable!()
40660                };
40661                s.clone()
40662            }
40663            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40664                let Literal::String(s) = lit.as_ref() else {
40665                    unreachable!()
40666                };
40667                s.clone()
40668            }
40669            Expression::Identifier(id) => id.name.clone(),
40670            Expression::Neg(f) => format!("-{}", Self::expr_to_string_static(&f.this)),
40671            _ => "1".to_string(),
40672        }
40673    }
40674
40675    /// Extract a simple string representation from a literal expression
40676    fn expr_to_string(expr: &Expression) -> String {
40677        use crate::expressions::Literal;
40678        match expr {
40679            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40680                let Literal::Number(s) = lit.as_ref() else {
40681                    unreachable!()
40682                };
40683                s.clone()
40684            }
40685            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => {
40686                let Literal::String(s) = lit.as_ref() else {
40687                    unreachable!()
40688                };
40689                s.clone()
40690            }
40691            Expression::Neg(f) => format!("-{}", Self::expr_to_string(&f.this)),
40692            Expression::Identifier(id) => id.name.clone(),
40693            _ => "1".to_string(),
40694        }
40695    }
40696
40697    /// Quote an interval value expression as a string literal if it's a number (or negated number)
40698    fn quote_interval_val(expr: &Expression) -> Expression {
40699        use crate::expressions::Literal;
40700        match expr {
40701            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Number(_)) => {
40702                let Literal::Number(n) = lit.as_ref() else {
40703                    unreachable!()
40704                };
40705                Expression::Literal(Box::new(Literal::String(n.clone())))
40706            }
40707            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::String(_)) => expr.clone(),
40708            Expression::Neg(inner) => {
40709                if let Expression::Literal(lit) = &inner.this {
40710                    if let Literal::Number(n) = lit.as_ref() {
40711                        Expression::Literal(Box::new(Literal::String(format!("-{}", n))))
40712                    } else {
40713                        inner.this.clone()
40714                    }
40715                } else {
40716                    expr.clone()
40717                }
40718            }
40719            _ => expr.clone(),
40720        }
40721    }
40722
40723    /// Check if a timestamp string contains timezone info (offset like +02:00, or named timezone)
40724    fn timestamp_string_has_timezone(ts: &str) -> bool {
40725        let trimmed = ts.trim();
40726        // Check for numeric timezone offsets: +N, -N, +NN:NN, -NN:NN at end
40727        if let Some(last_space) = trimmed.rfind(' ') {
40728            let suffix = &trimmed[last_space + 1..];
40729            if (suffix.starts_with('+') || suffix.starts_with('-')) && suffix.len() > 1 {
40730                let rest = &suffix[1..];
40731                if rest.chars().all(|c| c.is_ascii_digit() || c == ':') {
40732                    return true;
40733                }
40734            }
40735        }
40736        // Check for named timezone abbreviations
40737        let ts_lower = trimmed.to_ascii_lowercase();
40738        let tz_abbrevs = [" utc", " gmt", " cet", " est", " pst", " cst", " mst"];
40739        for abbrev in &tz_abbrevs {
40740            if ts_lower.ends_with(abbrev) {
40741                return true;
40742            }
40743        }
40744        false
40745    }
40746
40747    /// Maybe CAST timestamp literal to TIMESTAMPTZ for Snowflake
40748    fn maybe_cast_ts_to_tz(expr: Expression, func_name: &str) -> Expression {
40749        use crate::expressions::{Cast, DataType, Literal};
40750        match expr {
40751            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40752                let Literal::Timestamp(s) = lit.as_ref() else {
40753                    unreachable!()
40754                };
40755                let tz = func_name.starts_with("TIMESTAMP");
40756                Expression::Cast(Box::new(Cast {
40757                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40758                    to: if tz {
40759                        DataType::Timestamp {
40760                            timezone: true,
40761                            precision: None,
40762                        }
40763                    } else {
40764                        DataType::Timestamp {
40765                            timezone: false,
40766                            precision: None,
40767                        }
40768                    },
40769                    trailing_comments: vec![],
40770                    double_colon_syntax: false,
40771                    format: None,
40772                    default: None,
40773                    inferred_type: None,
40774                }))
40775            }
40776            other => other,
40777        }
40778    }
40779
40780    /// Maybe CAST timestamp literal to TIMESTAMP (no tz)
40781    fn maybe_cast_ts(expr: Expression) -> Expression {
40782        use crate::expressions::{Cast, DataType, Literal};
40783        match expr {
40784            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
40785                let Literal::Timestamp(s) = lit.as_ref() else {
40786                    unreachable!()
40787                };
40788                Expression::Cast(Box::new(Cast {
40789                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40790                    to: DataType::Timestamp {
40791                        timezone: false,
40792                        precision: None,
40793                    },
40794                    trailing_comments: vec![],
40795                    double_colon_syntax: false,
40796                    format: None,
40797                    default: None,
40798                    inferred_type: None,
40799                }))
40800            }
40801            other => other,
40802        }
40803    }
40804
40805    /// Convert DATE 'x' literal to CAST('x' AS DATE)
40806    fn date_literal_to_cast(expr: Expression) -> Expression {
40807        use crate::expressions::{Cast, DataType, Literal};
40808        match expr {
40809            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40810                let Literal::Date(s) = lit.as_ref() else {
40811                    unreachable!()
40812                };
40813                Expression::Cast(Box::new(Cast {
40814                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40815                    to: DataType::Date,
40816                    trailing_comments: vec![],
40817                    double_colon_syntax: false,
40818                    format: None,
40819                    default: None,
40820                    inferred_type: None,
40821                }))
40822            }
40823            other => other,
40824        }
40825    }
40826
40827    /// Ensure an expression that should be a date is CAST(... AS DATE).
40828    /// Handles both DATE literals and string literals that look like dates.
40829    fn ensure_cast_date(expr: Expression) -> Expression {
40830        use crate::expressions::{Cast, DataType, Literal};
40831        match expr {
40832            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Date(_)) => {
40833                let Literal::Date(s) = lit.as_ref() else {
40834                    unreachable!()
40835                };
40836                Expression::Cast(Box::new(Cast {
40837                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
40838                    to: DataType::Date,
40839                    trailing_comments: vec![],
40840                    double_colon_syntax: false,
40841                    format: None,
40842                    default: None,
40843                    inferred_type: None,
40844                }))
40845            }
40846            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
40847                // String literal that should be a date -> CAST('s' AS DATE)
40848                Expression::Cast(Box::new(Cast {
40849                    this: expr,
40850                    to: DataType::Date,
40851                    trailing_comments: vec![],
40852                    double_colon_syntax: false,
40853                    format: None,
40854                    default: None,
40855                    inferred_type: None,
40856                }))
40857            }
40858            // Already a CAST or other expression -> leave as-is
40859            other => other,
40860        }
40861    }
40862
40863    /// Force CAST(expr AS DATE) for any expression (not just literals)
40864    /// Skips if the expression is already a CAST to DATE
40865    fn force_cast_date(expr: Expression) -> Expression {
40866        use crate::expressions::{Cast, DataType};
40867        // If it's already a CAST to DATE, don't double-wrap
40868        if let Expression::Cast(ref c) = expr {
40869            if matches!(c.to, DataType::Date) {
40870                return expr;
40871            }
40872        }
40873        Expression::Cast(Box::new(Cast {
40874            this: expr,
40875            to: DataType::Date,
40876            trailing_comments: vec![],
40877            double_colon_syntax: false,
40878            format: None,
40879            default: None,
40880            inferred_type: None,
40881        }))
40882    }
40883
40884    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
40885    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
40886    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
40887    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
40888
40889    fn ensure_to_date_preserved(expr: Expression) -> Expression {
40890        use crate::expressions::{Function, Literal};
40891        if matches!(expr, Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(_)))
40892        {
40893            Expression::Function(Box::new(Function::new(
40894                Self::PRESERVED_TO_DATE.to_string(),
40895                vec![expr],
40896            )))
40897        } else {
40898            expr
40899        }
40900    }
40901
40902    /// TRY_CAST(expr AS DATE) - used for DuckDB when TO_DATE is unwrapped
40903    fn try_cast_date(expr: Expression) -> Expression {
40904        use crate::expressions::{Cast, DataType};
40905        Expression::TryCast(Box::new(Cast {
40906            this: expr,
40907            to: DataType::Date,
40908            trailing_comments: vec![],
40909            double_colon_syntax: false,
40910            format: None,
40911            default: None,
40912            inferred_type: None,
40913        }))
40914    }
40915
40916    /// CAST(CAST(expr AS TIMESTAMP) AS DATE) - used when Hive string dates need to be cast
40917    fn double_cast_timestamp_date(expr: Expression) -> Expression {
40918        use crate::expressions::{Cast, DataType};
40919        let inner = Expression::Cast(Box::new(Cast {
40920            this: expr,
40921            to: DataType::Timestamp {
40922                timezone: false,
40923                precision: None,
40924            },
40925            trailing_comments: vec![],
40926            double_colon_syntax: false,
40927            format: None,
40928            default: None,
40929            inferred_type: None,
40930        }));
40931        Expression::Cast(Box::new(Cast {
40932            this: inner,
40933            to: DataType::Date,
40934            trailing_comments: vec![],
40935            double_colon_syntax: false,
40936            format: None,
40937            default: None,
40938            inferred_type: None,
40939        }))
40940    }
40941
40942    /// CAST(CAST(expr AS DATETIME) AS DATE) - BigQuery variant
40943    fn double_cast_datetime_date(expr: Expression) -> Expression {
40944        use crate::expressions::{Cast, DataType};
40945        let inner = Expression::Cast(Box::new(Cast {
40946            this: expr,
40947            to: DataType::Custom {
40948                name: "DATETIME".to_string(),
40949            },
40950            trailing_comments: vec![],
40951            double_colon_syntax: false,
40952            format: None,
40953            default: None,
40954            inferred_type: None,
40955        }));
40956        Expression::Cast(Box::new(Cast {
40957            this: inner,
40958            to: DataType::Date,
40959            trailing_comments: vec![],
40960            double_colon_syntax: false,
40961            format: None,
40962            default: None,
40963            inferred_type: None,
40964        }))
40965    }
40966
40967    /// CAST(CAST(expr AS DATETIME2) AS DATE) - TSQL variant
40968    fn double_cast_datetime2_date(expr: Expression) -> Expression {
40969        use crate::expressions::{Cast, DataType};
40970        let inner = Expression::Cast(Box::new(Cast {
40971            this: expr,
40972            to: DataType::Custom {
40973                name: "DATETIME2".to_string(),
40974            },
40975            trailing_comments: vec![],
40976            double_colon_syntax: false,
40977            format: None,
40978            default: None,
40979            inferred_type: None,
40980        }));
40981        Expression::Cast(Box::new(Cast {
40982            this: inner,
40983            to: DataType::Date,
40984            trailing_comments: vec![],
40985            double_colon_syntax: false,
40986            format: None,
40987            default: None,
40988            inferred_type: None,
40989        }))
40990    }
40991
40992    /// Convert Hive/Java-style date format strings to C-style (strftime) format
40993    /// e.g., "yyyy-MM-dd'T'HH" -> "%Y-%m-%d'T'%H"
40994    fn hive_format_to_c_format(fmt: &str) -> String {
40995        let mut result = String::new();
40996        let chars: Vec<char> = fmt.chars().collect();
40997        let mut i = 0;
40998        while i < chars.len() {
40999            match chars[i] {
41000                'y' => {
41001                    let mut count = 0;
41002                    while i < chars.len() && chars[i] == 'y' {
41003                        count += 1;
41004                        i += 1;
41005                    }
41006                    if count >= 4 {
41007                        result.push_str("%Y");
41008                    } else if count == 2 {
41009                        result.push_str("%y");
41010                    } else {
41011                        result.push_str("%Y");
41012                    }
41013                }
41014                'M' => {
41015                    let mut count = 0;
41016                    while i < chars.len() && chars[i] == 'M' {
41017                        count += 1;
41018                        i += 1;
41019                    }
41020                    if count >= 3 {
41021                        result.push_str("%b");
41022                    } else if count == 2 {
41023                        result.push_str("%m");
41024                    } else {
41025                        result.push_str("%m");
41026                    }
41027                }
41028                'd' => {
41029                    let mut _count = 0;
41030                    while i < chars.len() && chars[i] == 'd' {
41031                        _count += 1;
41032                        i += 1;
41033                    }
41034                    result.push_str("%d");
41035                }
41036                'H' => {
41037                    let mut _count = 0;
41038                    while i < chars.len() && chars[i] == 'H' {
41039                        _count += 1;
41040                        i += 1;
41041                    }
41042                    result.push_str("%H");
41043                }
41044                'h' => {
41045                    let mut _count = 0;
41046                    while i < chars.len() && chars[i] == 'h' {
41047                        _count += 1;
41048                        i += 1;
41049                    }
41050                    result.push_str("%I");
41051                }
41052                'm' => {
41053                    let mut _count = 0;
41054                    while i < chars.len() && chars[i] == 'm' {
41055                        _count += 1;
41056                        i += 1;
41057                    }
41058                    result.push_str("%M");
41059                }
41060                's' => {
41061                    let mut _count = 0;
41062                    while i < chars.len() && chars[i] == 's' {
41063                        _count += 1;
41064                        i += 1;
41065                    }
41066                    result.push_str("%S");
41067                }
41068                'S' => {
41069                    // Fractional seconds - skip
41070                    while i < chars.len() && chars[i] == 'S' {
41071                        i += 1;
41072                    }
41073                    result.push_str("%f");
41074                }
41075                'a' => {
41076                    // AM/PM
41077                    while i < chars.len() && chars[i] == 'a' {
41078                        i += 1;
41079                    }
41080                    result.push_str("%p");
41081                }
41082                'E' => {
41083                    let mut count = 0;
41084                    while i < chars.len() && chars[i] == 'E' {
41085                        count += 1;
41086                        i += 1;
41087                    }
41088                    if count >= 4 {
41089                        result.push_str("%A");
41090                    } else {
41091                        result.push_str("%a");
41092                    }
41093                }
41094                '\'' => {
41095                    // Quoted literal text - pass through the quotes and content
41096                    result.push('\'');
41097                    i += 1;
41098                    while i < chars.len() && chars[i] != '\'' {
41099                        result.push(chars[i]);
41100                        i += 1;
41101                    }
41102                    if i < chars.len() {
41103                        result.push('\'');
41104                        i += 1;
41105                    }
41106                }
41107                c => {
41108                    result.push(c);
41109                    i += 1;
41110                }
41111            }
41112        }
41113        result
41114    }
41115
41116    /// Convert Hive/Java format to Presto format (uses %T for HH:mm:ss)
41117    fn hive_format_to_presto_format(fmt: &str) -> String {
41118        let c_fmt = Self::hive_format_to_c_format(fmt);
41119        // Presto uses %T for HH:MM:SS
41120        c_fmt.replace("%H:%M:%S", "%T")
41121    }
41122
41123    /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMP)
41124    fn ensure_cast_timestamp(expr: Expression) -> Expression {
41125        use crate::expressions::{Cast, DataType, Literal};
41126        match expr {
41127            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41128                let Literal::Timestamp(s) = lit.as_ref() else {
41129                    unreachable!()
41130                };
41131                Expression::Cast(Box::new(Cast {
41132                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41133                    to: DataType::Timestamp {
41134                        timezone: false,
41135                        precision: None,
41136                    },
41137                    trailing_comments: vec![],
41138                    double_colon_syntax: false,
41139                    format: None,
41140                    default: None,
41141                    inferred_type: None,
41142                }))
41143            }
41144            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41145                Expression::Cast(Box::new(Cast {
41146                    this: expr,
41147                    to: DataType::Timestamp {
41148                        timezone: false,
41149                        precision: None,
41150                    },
41151                    trailing_comments: vec![],
41152                    double_colon_syntax: false,
41153                    format: None,
41154                    default: None,
41155                    inferred_type: None,
41156                }))
41157            }
41158            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
41159                let Literal::Datetime(s) = lit.as_ref() else {
41160                    unreachable!()
41161                };
41162                Expression::Cast(Box::new(Cast {
41163                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41164                    to: DataType::Timestamp {
41165                        timezone: false,
41166                        precision: None,
41167                    },
41168                    trailing_comments: vec![],
41169                    double_colon_syntax: false,
41170                    format: None,
41171                    default: None,
41172                    inferred_type: None,
41173                }))
41174            }
41175            other => other,
41176        }
41177    }
41178
41179    /// Force CAST to TIMESTAMP for any expression (not just literals)
41180    /// Used when transpiling from Redshift/TSQL where DATEDIFF/DATEADD args need explicit timestamp cast
41181    fn force_cast_timestamp(expr: Expression) -> Expression {
41182        use crate::expressions::{Cast, DataType};
41183        // Don't double-wrap if already a CAST to TIMESTAMP
41184        if let Expression::Cast(ref c) = expr {
41185            if matches!(c.to, DataType::Timestamp { .. }) {
41186                return expr;
41187            }
41188        }
41189        Expression::Cast(Box::new(Cast {
41190            this: expr,
41191            to: DataType::Timestamp {
41192                timezone: false,
41193                precision: None,
41194            },
41195            trailing_comments: vec![],
41196            double_colon_syntax: false,
41197            format: None,
41198            default: None,
41199            inferred_type: None,
41200        }))
41201    }
41202
41203    /// Ensure a timestamp-like expression for DuckDB with CAST(... AS TIMESTAMPTZ)
41204    fn ensure_cast_timestamptz(expr: Expression) -> Expression {
41205        use crate::expressions::{Cast, DataType, Literal};
41206        match expr {
41207            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41208                let Literal::Timestamp(s) = lit.as_ref() else {
41209                    unreachable!()
41210                };
41211                Expression::Cast(Box::new(Cast {
41212                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41213                    to: DataType::Timestamp {
41214                        timezone: true,
41215                        precision: None,
41216                    },
41217                    trailing_comments: vec![],
41218                    double_colon_syntax: false,
41219                    format: None,
41220                    default: None,
41221                    inferred_type: None,
41222                }))
41223            }
41224            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41225                Expression::Cast(Box::new(Cast {
41226                    this: expr,
41227                    to: DataType::Timestamp {
41228                        timezone: true,
41229                        precision: None,
41230                    },
41231                    trailing_comments: vec![],
41232                    double_colon_syntax: false,
41233                    format: None,
41234                    default: None,
41235                    inferred_type: None,
41236                }))
41237            }
41238            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Datetime(_)) => {
41239                let Literal::Datetime(s) = lit.as_ref() else {
41240                    unreachable!()
41241                };
41242                Expression::Cast(Box::new(Cast {
41243                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41244                    to: DataType::Timestamp {
41245                        timezone: true,
41246                        precision: None,
41247                    },
41248                    trailing_comments: vec![],
41249                    double_colon_syntax: false,
41250                    format: None,
41251                    default: None,
41252                    inferred_type: None,
41253                }))
41254            }
41255            other => other,
41256        }
41257    }
41258
41259    /// Ensure expression is CAST to DATETIME (for BigQuery)
41260    fn ensure_cast_datetime(expr: Expression) -> Expression {
41261        use crate::expressions::{Cast, DataType, Literal};
41262        match expr {
41263            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41264                Expression::Cast(Box::new(Cast {
41265                    this: expr,
41266                    to: DataType::Custom {
41267                        name: "DATETIME".to_string(),
41268                    },
41269                    trailing_comments: vec![],
41270                    double_colon_syntax: false,
41271                    format: None,
41272                    default: None,
41273                    inferred_type: None,
41274                }))
41275            }
41276            other => other,
41277        }
41278    }
41279
41280    /// Force CAST expression to DATETIME (for BigQuery) - always wraps unless already DATETIME
41281    fn force_cast_datetime(expr: Expression) -> Expression {
41282        use crate::expressions::{Cast, DataType};
41283        if let Expression::Cast(ref c) = expr {
41284            if let DataType::Custom { ref name } = c.to {
41285                if name.eq_ignore_ascii_case("DATETIME") {
41286                    return expr;
41287                }
41288            }
41289        }
41290        Expression::Cast(Box::new(Cast {
41291            this: expr,
41292            to: DataType::Custom {
41293                name: "DATETIME".to_string(),
41294            },
41295            trailing_comments: vec![],
41296            double_colon_syntax: false,
41297            format: None,
41298            default: None,
41299            inferred_type: None,
41300        }))
41301    }
41302
41303    /// Ensure expression is CAST to DATETIME2 (for TSQL)
41304    fn ensure_cast_datetime2(expr: Expression) -> Expression {
41305        use crate::expressions::{Cast, DataType, Literal};
41306        match expr {
41307            Expression::Literal(ref lit) if matches!(lit.as_ref(), Literal::String(ref _s)) => {
41308                Expression::Cast(Box::new(Cast {
41309                    this: expr,
41310                    to: DataType::Custom {
41311                        name: "DATETIME2".to_string(),
41312                    },
41313                    trailing_comments: vec![],
41314                    double_colon_syntax: false,
41315                    format: None,
41316                    default: None,
41317                    inferred_type: None,
41318                }))
41319            }
41320            other => other,
41321        }
41322    }
41323
41324    /// Convert TIMESTAMP 'x' literal to CAST('x' AS TIMESTAMPTZ) for DuckDB
41325    fn ts_literal_to_cast_tz(expr: Expression) -> Expression {
41326        use crate::expressions::{Cast, DataType, Literal};
41327        match expr {
41328            Expression::Literal(lit) if matches!(lit.as_ref(), Literal::Timestamp(_)) => {
41329                let Literal::Timestamp(s) = lit.as_ref() else {
41330                    unreachable!()
41331                };
41332                Expression::Cast(Box::new(Cast {
41333                    this: Expression::Literal(Box::new(Literal::String(s.clone()))),
41334                    to: DataType::Timestamp {
41335                        timezone: true,
41336                        precision: None,
41337                    },
41338                    trailing_comments: vec![],
41339                    double_colon_syntax: false,
41340                    format: None,
41341                    default: None,
41342                    inferred_type: None,
41343                }))
41344            }
41345            other => other,
41346        }
41347    }
41348
41349    /// Convert BigQuery format string to Snowflake format string
41350    fn bq_format_to_snowflake(format_expr: &Expression) -> Expression {
41351        use crate::expressions::Literal;
41352        if let Expression::Literal(lit) = format_expr {
41353            if let Literal::String(s) = lit.as_ref() {
41354                let sf = s
41355                    .replace("%Y", "yyyy")
41356                    .replace("%m", "mm")
41357                    .replace("%d", "DD")
41358                    .replace("%H", "HH24")
41359                    .replace("%M", "MI")
41360                    .replace("%S", "SS")
41361                    .replace("%b", "mon")
41362                    .replace("%B", "Month")
41363                    .replace("%e", "FMDD");
41364                Expression::Literal(Box::new(Literal::String(sf)))
41365            } else {
41366                format_expr.clone()
41367            }
41368        } else {
41369            format_expr.clone()
41370        }
41371    }
41372
41373    /// Convert BigQuery format string to DuckDB format string
41374    fn bq_format_to_duckdb(format_expr: &Expression) -> Expression {
41375        use crate::expressions::Literal;
41376        if let Expression::Literal(lit) = format_expr {
41377            if let Literal::String(s) = lit.as_ref() {
41378                let duck = s
41379                    .replace("%T", "%H:%M:%S")
41380                    .replace("%F", "%Y-%m-%d")
41381                    .replace("%D", "%m/%d/%y")
41382                    .replace("%x", "%m/%d/%y")
41383                    .replace("%c", "%a %b %-d %H:%M:%S %Y")
41384                    .replace("%e", "%-d")
41385                    .replace("%E6S", "%S.%f");
41386                Expression::Literal(Box::new(Literal::String(duck)))
41387            } else {
41388                format_expr.clone()
41389            }
41390        } else {
41391            format_expr.clone()
41392        }
41393    }
41394
41395    /// Convert BigQuery CAST FORMAT elements (like YYYY, MM, DD) to strftime (like %Y, %m, %d)
41396    fn bq_cast_format_to_strftime(format_expr: &Expression) -> Expression {
41397        use crate::expressions::Literal;
41398        if let Expression::Literal(lit) = format_expr {
41399            if let Literal::String(s) = lit.as_ref() {
41400                // Replace format elements from longest to shortest to avoid partial matches
41401                let result = s
41402                    .replace("YYYYMMDD", "%Y%m%d")
41403                    .replace("YYYY", "%Y")
41404                    .replace("YY", "%y")
41405                    .replace("MONTH", "%B")
41406                    .replace("MON", "%b")
41407                    .replace("MM", "%m")
41408                    .replace("DD", "%d")
41409                    .replace("HH24", "%H")
41410                    .replace("HH12", "%I")
41411                    .replace("HH", "%I")
41412                    .replace("MI", "%M")
41413                    .replace("SSTZH", "%S%z")
41414                    .replace("SS", "%S")
41415                    .replace("TZH", "%z");
41416                Expression::Literal(Box::new(Literal::String(result)))
41417            } else {
41418                format_expr.clone()
41419            }
41420        } else {
41421            format_expr.clone()
41422        }
41423    }
41424
41425    /// Normalize BigQuery format strings for BQ->BQ output
41426    fn bq_format_normalize_bq(format_expr: &Expression) -> Expression {
41427        use crate::expressions::Literal;
41428        if let Expression::Literal(lit) = format_expr {
41429            if let Literal::String(s) = lit.as_ref() {
41430                let norm = s.replace("%H:%M:%S", "%T").replace("%x", "%D");
41431                Expression::Literal(Box::new(Literal::String(norm)))
41432            } else {
41433                format_expr.clone()
41434            }
41435        } else {
41436            format_expr.clone()
41437        }
41438    }
41439}
41440
41441#[cfg(test)]
41442mod tests {
41443    use super::*;
41444
41445    #[test]
41446    fn test_dialect_type_from_str() {
41447        assert_eq!(
41448            "postgres".parse::<DialectType>().unwrap(),
41449            DialectType::PostgreSQL
41450        );
41451        assert_eq!(
41452            "postgresql".parse::<DialectType>().unwrap(),
41453            DialectType::PostgreSQL
41454        );
41455        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
41456        assert_eq!(
41457            "bigquery".parse::<DialectType>().unwrap(),
41458            DialectType::BigQuery
41459        );
41460    }
41461
41462    #[test]
41463    fn test_basic_transpile() {
41464        let dialect = Dialect::get(DialectType::Generic);
41465        let result = dialect
41466            .transpile("SELECT 1", DialectType::PostgreSQL)
41467            .unwrap();
41468        assert_eq!(result.len(), 1);
41469        assert_eq!(result[0], "SELECT 1");
41470    }
41471
41472    #[test]
41473    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
41474        let sqlite = Dialect::get(DialectType::SQLite);
41475        let result = sqlite
41476            .transpile(
41477                r#"CREATE TABLE "_collections" (
41478                    "type" TEXT DEFAULT "base" NOT NULL,
41479                    "fields" JSON DEFAULT "[]" NOT NULL,
41480                    "options" JSON DEFAULT "{}" NOT NULL
41481                )"#,
41482                DialectType::PostgreSQL,
41483            )
41484            .unwrap();
41485
41486        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
41487        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
41488        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
41489    }
41490
41491    #[test]
41492    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
41493        let sqlite = Dialect::get(DialectType::SQLite);
41494        let result = sqlite
41495            .transpile(
41496                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
41497                DialectType::SQLite,
41498            )
41499            .unwrap();
41500
41501        assert_eq!(
41502            result[0],
41503            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
41504        );
41505    }
41506
41507    #[test]
41508    fn test_function_transformation_mysql() {
41509        // NVL should be transformed to IFNULL in MySQL
41510        let dialect = Dialect::get(DialectType::Generic);
41511        let result = dialect
41512            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
41513            .unwrap();
41514        assert_eq!(result[0], "SELECT IFNULL(a, b)");
41515    }
41516
41517    #[test]
41518    fn test_get_path_duckdb() {
41519        // Test: step by step
41520        let snowflake = Dialect::get(DialectType::Snowflake);
41521
41522        // Step 1: Parse and check what Snowflake produces as intermediate
41523        let result_sf_sf = snowflake
41524            .transpile(
41525                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
41526                DialectType::Snowflake,
41527            )
41528            .unwrap();
41529        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
41530
41531        // Step 2: DuckDB target
41532        let result_sf_dk = snowflake
41533            .transpile(
41534                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
41535                DialectType::DuckDB,
41536            )
41537            .unwrap();
41538        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
41539
41540        // Step 3: GET_PATH directly
41541        let result_gp = snowflake
41542            .transpile(
41543                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
41544                DialectType::DuckDB,
41545            )
41546            .unwrap();
41547        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
41548    }
41549
41550    #[test]
41551    fn test_function_transformation_postgres() {
41552        // IFNULL should be transformed to COALESCE in PostgreSQL
41553        let dialect = Dialect::get(DialectType::Generic);
41554        let result = dialect
41555            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
41556            .unwrap();
41557        assert_eq!(result[0], "SELECT COALESCE(a, b)");
41558
41559        // NVL should also be transformed to COALESCE
41560        let result = dialect
41561            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
41562            .unwrap();
41563        assert_eq!(result[0], "SELECT COALESCE(a, b)");
41564    }
41565
41566    #[test]
41567    fn test_hive_cast_to_trycast() {
41568        // Hive CAST should become TRY_CAST for targets that support it
41569        let hive = Dialect::get(DialectType::Hive);
41570        let result = hive
41571            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
41572            .unwrap();
41573        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
41574
41575        let result = hive
41576            .transpile("CAST(1 AS INT)", DialectType::Presto)
41577            .unwrap();
41578        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
41579    }
41580
41581    #[test]
41582    fn test_hive_array_identity() {
41583        // Hive ARRAY<DATE> should preserve angle bracket syntax
41584        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')";
41585        let hive = Dialect::get(DialectType::Hive);
41586
41587        // Test via transpile (this works)
41588        let result = hive.transpile(sql, DialectType::Hive).unwrap();
41589        eprintln!("Hive ARRAY via transpile: {}", result[0]);
41590        assert!(
41591            result[0].contains("ARRAY<DATE>"),
41592            "transpile: Expected ARRAY<DATE>, got: {}",
41593            result[0]
41594        );
41595
41596        // Test via parse -> transform -> generate (identity test path)
41597        let ast = hive.parse(sql).unwrap();
41598        let transformed = hive.transform(ast[0].clone()).unwrap();
41599        let output = hive.generate(&transformed).unwrap();
41600        eprintln!("Hive ARRAY via identity path: {}", output);
41601        assert!(
41602            output.contains("ARRAY<DATE>"),
41603            "identity path: Expected ARRAY<DATE>, got: {}",
41604            output
41605        );
41606    }
41607
41608    #[test]
41609    fn test_starrocks_delete_between_expansion() {
41610        // StarRocks doesn't support BETWEEN in DELETE statements
41611        let dialect = Dialect::get(DialectType::Generic);
41612
41613        // BETWEEN should be expanded to >= AND <= in DELETE
41614        let result = dialect
41615            .transpile(
41616                "DELETE FROM t WHERE a BETWEEN b AND c",
41617                DialectType::StarRocks,
41618            )
41619            .unwrap();
41620        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
41621
41622        // NOT BETWEEN should be expanded to < OR > in DELETE
41623        let result = dialect
41624            .transpile(
41625                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
41626                DialectType::StarRocks,
41627            )
41628            .unwrap();
41629        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
41630
41631        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
41632        let result = dialect
41633            .transpile(
41634                "SELECT * FROM t WHERE a BETWEEN b AND c",
41635                DialectType::StarRocks,
41636            )
41637            .unwrap();
41638        assert!(
41639            result[0].contains("BETWEEN"),
41640            "BETWEEN should be preserved in SELECT"
41641        );
41642    }
41643
41644    #[test]
41645    fn test_snowflake_ltrim_rtrim_parse() {
41646        let sf = Dialect::get(DialectType::Snowflake);
41647        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
41648        let result = sf.transpile(sql, DialectType::DuckDB);
41649        match &result {
41650            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
41651            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
41652        }
41653        assert!(
41654            result.is_ok(),
41655            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
41656            result.err()
41657        );
41658    }
41659
41660    #[test]
41661    fn test_duckdb_count_if_parse() {
41662        let duck = Dialect::get(DialectType::DuckDB);
41663        let sql = "COUNT_IF(x)";
41664        let result = duck.transpile(sql, DialectType::DuckDB);
41665        match &result {
41666            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
41667            Err(e) => eprintln!("COUNT_IF error: {}", e),
41668        }
41669        assert!(
41670            result.is_ok(),
41671            "Expected successful parse of COUNT_IF(x), got error: {:?}",
41672            result.err()
41673        );
41674    }
41675
41676    #[test]
41677    fn test_tsql_cast_tinyint_parse() {
41678        let tsql = Dialect::get(DialectType::TSQL);
41679        let sql = "CAST(X AS TINYINT)";
41680        let result = tsql.transpile(sql, DialectType::DuckDB);
41681        match &result {
41682            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
41683            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
41684        }
41685        assert!(
41686            result.is_ok(),
41687            "Expected successful transpile, got error: {:?}",
41688            result.err()
41689        );
41690    }
41691
41692    #[test]
41693    fn test_pg_hash_bitwise_xor() {
41694        let dialect = Dialect::get(DialectType::PostgreSQL);
41695        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
41696        assert_eq!(result[0], "x # y");
41697    }
41698
41699    #[test]
41700    fn test_pg_array_to_duckdb() {
41701        let dialect = Dialect::get(DialectType::PostgreSQL);
41702        let result = dialect
41703            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
41704            .unwrap();
41705        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
41706    }
41707
41708    #[test]
41709    fn test_array_remove_bigquery() {
41710        let dialect = Dialect::get(DialectType::Generic);
41711        let result = dialect
41712            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
41713            .unwrap();
41714        assert_eq!(
41715            result[0],
41716            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
41717        );
41718    }
41719
41720    #[test]
41721    fn test_map_clickhouse_case() {
41722        let dialect = Dialect::get(DialectType::Generic);
41723        let parsed = dialect
41724            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
41725            .unwrap();
41726        eprintln!("MAP parsed: {:?}", parsed);
41727        let result = dialect
41728            .transpile(
41729                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
41730                DialectType::ClickHouse,
41731            )
41732            .unwrap();
41733        eprintln!("MAP result: {}", result[0]);
41734    }
41735
41736    #[test]
41737    fn test_generate_date_array_presto() {
41738        let dialect = Dialect::get(DialectType::Generic);
41739        let result = dialect.transpile(
41740            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41741            DialectType::Presto,
41742        ).unwrap();
41743        eprintln!("GDA -> Presto: {}", result[0]);
41744        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
41745    }
41746
41747    #[test]
41748    fn test_generate_date_array_postgres() {
41749        let dialect = Dialect::get(DialectType::Generic);
41750        let result = dialect.transpile(
41751            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41752            DialectType::PostgreSQL,
41753        ).unwrap();
41754        eprintln!("GDA -> PostgreSQL: {}", result[0]);
41755    }
41756
41757    #[test]
41758    fn test_generate_date_array_snowflake() {
41759        let dialect = Dialect::get(DialectType::Generic);
41760        let result = dialect
41761            .transpile(
41762                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41763                DialectType::Snowflake,
41764            )
41765            .unwrap();
41766        eprintln!("GDA -> Snowflake: {}", result[0]);
41767    }
41768
41769    #[test]
41770    fn test_array_length_generate_date_array_snowflake() {
41771        let dialect = Dialect::get(DialectType::Generic);
41772        let result = dialect.transpile(
41773            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41774            DialectType::Snowflake,
41775        ).unwrap();
41776        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
41777    }
41778
41779    #[test]
41780    fn test_generate_date_array_mysql() {
41781        let dialect = Dialect::get(DialectType::Generic);
41782        let result = dialect.transpile(
41783            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41784            DialectType::MySQL,
41785        ).unwrap();
41786        eprintln!("GDA -> MySQL: {}", result[0]);
41787    }
41788
41789    #[test]
41790    fn test_generate_date_array_redshift() {
41791        let dialect = Dialect::get(DialectType::Generic);
41792        let result = dialect.transpile(
41793            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41794            DialectType::Redshift,
41795        ).unwrap();
41796        eprintln!("GDA -> Redshift: {}", result[0]);
41797    }
41798
41799    #[test]
41800    fn test_generate_date_array_tsql() {
41801        let dialect = Dialect::get(DialectType::Generic);
41802        let result = dialect.transpile(
41803            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
41804            DialectType::TSQL,
41805        ).unwrap();
41806        eprintln!("GDA -> TSQL: {}", result[0]);
41807    }
41808
41809    #[test]
41810    fn test_struct_colon_syntax() {
41811        let dialect = Dialect::get(DialectType::Generic);
41812        // Test without colon first
41813        let result = dialect.transpile(
41814            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
41815            DialectType::ClickHouse,
41816        );
41817        match result {
41818            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
41819            Err(e) => eprintln!("STRUCT no colon error: {}", e),
41820        }
41821        // Now test with colon
41822        let result = dialect.transpile(
41823            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
41824            DialectType::ClickHouse,
41825        );
41826        match result {
41827            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
41828            Err(e) => eprintln!("STRUCT colon error: {}", e),
41829        }
41830    }
41831
41832    #[test]
41833    fn test_generate_date_array_cte_wrapped_mysql() {
41834        let dialect = Dialect::get(DialectType::Generic);
41835        let result = dialect.transpile(
41836            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
41837            DialectType::MySQL,
41838        ).unwrap();
41839        eprintln!("GDA CTE -> MySQL: {}", result[0]);
41840    }
41841
41842    #[test]
41843    fn test_generate_date_array_cte_wrapped_tsql() {
41844        let dialect = Dialect::get(DialectType::Generic);
41845        let result = dialect.transpile(
41846            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
41847            DialectType::TSQL,
41848        ).unwrap();
41849        eprintln!("GDA CTE -> TSQL: {}", result[0]);
41850    }
41851
41852    #[test]
41853    fn test_decode_literal_no_null_check() {
41854        // Oracle DECODE with all literals should produce simple equality, no IS NULL
41855        let dialect = Dialect::get(DialectType::Oracle);
41856        let result = dialect
41857            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
41858            .unwrap();
41859        assert_eq!(
41860            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
41861            "Literal DECODE should not have IS NULL checks"
41862        );
41863    }
41864
41865    #[test]
41866    fn test_decode_column_vs_literal_no_null_check() {
41867        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
41868        let dialect = Dialect::get(DialectType::Oracle);
41869        let result = dialect
41870            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
41871            .unwrap();
41872        assert_eq!(
41873            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
41874            "Column vs literal DECODE should not have IS NULL checks"
41875        );
41876    }
41877
41878    #[test]
41879    fn test_decode_column_vs_column_keeps_null_check() {
41880        // Oracle DECODE with column vs column should keep null-safe comparison
41881        let dialect = Dialect::get(DialectType::Oracle);
41882        let result = dialect
41883            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
41884            .unwrap();
41885        assert!(
41886            result[0].contains("IS NULL"),
41887            "Column vs column DECODE should have IS NULL checks, got: {}",
41888            result[0]
41889        );
41890    }
41891
41892    #[test]
41893    fn test_decode_null_search() {
41894        // Oracle DECODE with NULL search should use IS NULL
41895        let dialect = Dialect::get(DialectType::Oracle);
41896        let result = dialect
41897            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
41898            .unwrap();
41899        assert_eq!(
41900            result[0],
41901            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
41902        );
41903    }
41904
41905    // =========================================================================
41906    // REGEXP function transpilation tests
41907    // =========================================================================
41908
41909    #[test]
41910    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
41911        let dialect = Dialect::get(DialectType::Snowflake);
41912        let result = dialect
41913            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
41914            .unwrap();
41915        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41916    }
41917
41918    #[test]
41919    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
41920        let dialect = Dialect::get(DialectType::Snowflake);
41921        let result = dialect
41922            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
41923            .unwrap();
41924        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41925    }
41926
41927    #[test]
41928    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
41929        let dialect = Dialect::get(DialectType::Snowflake);
41930        let result = dialect
41931            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
41932            .unwrap();
41933        assert_eq!(
41934            result[0],
41935            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
41936        );
41937    }
41938
41939    #[test]
41940    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
41941        let dialect = Dialect::get(DialectType::Snowflake);
41942        let result = dialect
41943            .transpile(
41944                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
41945                DialectType::DuckDB,
41946            )
41947            .unwrap();
41948        assert_eq!(
41949            result[0],
41950            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
41951        );
41952    }
41953
41954    #[test]
41955    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
41956        let dialect = Dialect::get(DialectType::Snowflake);
41957        let result = dialect
41958            .transpile(
41959                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
41960                DialectType::DuckDB,
41961            )
41962            .unwrap();
41963        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41964    }
41965
41966    #[test]
41967    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
41968        let dialect = Dialect::get(DialectType::Snowflake);
41969        let result = dialect
41970            .transpile(
41971                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
41972                DialectType::DuckDB,
41973            )
41974            .unwrap();
41975        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
41976    }
41977
41978    #[test]
41979    fn test_regexp_substr_snowflake_identity_strip_group0() {
41980        let dialect = Dialect::get(DialectType::Snowflake);
41981        let result = dialect
41982            .transpile(
41983                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
41984                DialectType::Snowflake,
41985            )
41986            .unwrap();
41987        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
41988    }
41989
41990    #[test]
41991    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
41992        let dialect = Dialect::get(DialectType::Snowflake);
41993        let result = dialect
41994            .transpile(
41995                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
41996                DialectType::DuckDB,
41997            )
41998            .unwrap();
41999        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42000    }
42001
42002    #[test]
42003    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
42004        let dialect = Dialect::get(DialectType::Snowflake);
42005        let result = dialect
42006            .transpile(
42007                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
42008                DialectType::DuckDB,
42009            )
42010            .unwrap();
42011        assert_eq!(
42012            result[0],
42013            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
42014        );
42015    }
42016
42017    #[test]
42018    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
42019        let dialect = Dialect::get(DialectType::Snowflake);
42020        let result = dialect
42021            .transpile(
42022                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
42023                DialectType::DuckDB,
42024            )
42025            .unwrap();
42026        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42027    }
42028
42029    #[test]
42030    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
42031        let dialect = Dialect::get(DialectType::Snowflake);
42032        let result = dialect
42033            .transpile(
42034                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
42035                DialectType::DuckDB,
42036            )
42037            .unwrap();
42038        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
42039    }
42040
42041    #[test]
42042    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
42043        let dialect = Dialect::get(DialectType::Snowflake);
42044        let result = dialect
42045            .transpile(
42046                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
42047                DialectType::Snowflake,
42048            )
42049            .unwrap();
42050        assert_eq!(
42051            result[0],
42052            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
42053        );
42054    }
42055
42056    #[test]
42057    fn test_regexp_count_snowflake_to_duckdb_2arg() {
42058        let dialect = Dialect::get(DialectType::Snowflake);
42059        let result = dialect
42060            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
42061            .unwrap();
42062        assert_eq!(
42063            result[0],
42064            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
42065        );
42066    }
42067
42068    #[test]
42069    fn test_regexp_count_snowflake_to_duckdb_3arg() {
42070        let dialect = Dialect::get(DialectType::Snowflake);
42071        let result = dialect
42072            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
42073            .unwrap();
42074        assert_eq!(
42075            result[0],
42076            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
42077        );
42078    }
42079
42080    #[test]
42081    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
42082        let dialect = Dialect::get(DialectType::Snowflake);
42083        let result = dialect
42084            .transpile(
42085                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
42086                DialectType::DuckDB,
42087            )
42088            .unwrap();
42089        assert_eq!(
42090            result[0],
42091            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
42092        );
42093    }
42094
42095    #[test]
42096    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
42097        let dialect = Dialect::get(DialectType::Snowflake);
42098        let result = dialect
42099            .transpile(
42100                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
42101                DialectType::DuckDB,
42102            )
42103            .unwrap();
42104        assert_eq!(
42105            result[0],
42106            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
42107        );
42108    }
42109
42110    #[test]
42111    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
42112        let dialect = Dialect::get(DialectType::Snowflake);
42113        let result = dialect
42114            .transpile(
42115                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
42116                DialectType::DuckDB,
42117            )
42118            .unwrap();
42119        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
42120    }
42121
42122    #[test]
42123    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
42124        let dialect = Dialect::get(DialectType::Snowflake);
42125        let result = dialect
42126            .transpile(
42127                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
42128                DialectType::DuckDB,
42129            )
42130            .unwrap();
42131        assert_eq!(
42132            result[0],
42133            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
42134        );
42135    }
42136
42137    #[test]
42138    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
42139        let dialect = Dialect::get(DialectType::Snowflake);
42140        let result = dialect
42141            .transpile(
42142                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
42143                DialectType::DuckDB,
42144            )
42145            .unwrap();
42146        assert_eq!(
42147            result[0],
42148            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
42149        );
42150    }
42151
42152    #[test]
42153    fn test_rlike_snowflake_to_duckdb_2arg() {
42154        let dialect = Dialect::get(DialectType::Snowflake);
42155        let result = dialect
42156            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
42157            .unwrap();
42158        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
42159    }
42160
42161    #[test]
42162    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
42163        let dialect = Dialect::get(DialectType::Snowflake);
42164        let result = dialect
42165            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
42166            .unwrap();
42167        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
42168    }
42169
42170    #[test]
42171    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
42172        let dialect = Dialect::get(DialectType::BigQuery);
42173        let result = dialect
42174            .transpile(
42175                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
42176                DialectType::Snowflake,
42177            )
42178            .unwrap();
42179        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
42180    }
42181
42182    #[test]
42183    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
42184        let dialect = Dialect::get(DialectType::BigQuery);
42185        let result = dialect
42186            .transpile(
42187                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
42188                DialectType::Snowflake,
42189            )
42190            .unwrap();
42191        assert_eq!(
42192            result[0],
42193            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
42194        );
42195    }
42196
42197    #[test]
42198    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
42199        let dialect = Dialect::get(DialectType::Snowflake);
42200        let result = dialect
42201            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
42202            .unwrap();
42203        assert!(
42204            result[0].contains("CASE WHEN"),
42205            "Expected CASE WHEN in result: {}",
42206            result[0]
42207        );
42208        assert!(
42209            result[0].contains("LIST_SUM"),
42210            "Expected LIST_SUM in result: {}",
42211            result[0]
42212        );
42213    }
42214
42215    #[test]
42216    fn test_array_except_generic_to_duckdb() {
42217        let dialect = Dialect::get(DialectType::Generic);
42218        let result = dialect
42219            .transpile(
42220                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42221                DialectType::DuckDB,
42222            )
42223            .unwrap();
42224        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
42225        assert!(
42226            result[0].contains("CASE WHEN"),
42227            "Expected CASE WHEN: {}",
42228            result[0]
42229        );
42230        assert!(
42231            result[0].contains("LIST_FILTER"),
42232            "Expected LIST_FILTER: {}",
42233            result[0]
42234        );
42235        assert!(
42236            result[0].contains("LIST_DISTINCT"),
42237            "Expected LIST_DISTINCT: {}",
42238            result[0]
42239        );
42240        assert!(
42241            result[0].contains("IS NOT DISTINCT FROM"),
42242            "Expected IS NOT DISTINCT FROM: {}",
42243            result[0]
42244        );
42245        assert!(
42246            result[0].contains("= 0"),
42247            "Expected = 0 filter: {}",
42248            result[0]
42249        );
42250    }
42251
42252    #[test]
42253    fn test_array_except_generic_to_snowflake() {
42254        let dialect = Dialect::get(DialectType::Generic);
42255        let result = dialect
42256            .transpile(
42257                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42258                DialectType::Snowflake,
42259            )
42260            .unwrap();
42261        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
42262        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
42263    }
42264
42265    #[test]
42266    fn test_array_except_generic_to_presto() {
42267        let dialect = Dialect::get(DialectType::Generic);
42268        let result = dialect
42269            .transpile(
42270                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
42271                DialectType::Presto,
42272            )
42273            .unwrap();
42274        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
42275        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
42276    }
42277
42278    #[test]
42279    fn test_array_except_snowflake_to_duckdb() {
42280        let dialect = Dialect::get(DialectType::Snowflake);
42281        let result = dialect
42282            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
42283            .unwrap();
42284        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
42285        assert!(
42286            result[0].contains("CASE WHEN"),
42287            "Expected CASE WHEN: {}",
42288            result[0]
42289        );
42290        assert!(
42291            result[0].contains("LIST_TRANSFORM"),
42292            "Expected LIST_TRANSFORM: {}",
42293            result[0]
42294        );
42295    }
42296
42297    #[test]
42298    fn test_array_contains_snowflake_to_snowflake() {
42299        let dialect = Dialect::get(DialectType::Snowflake);
42300        let result = dialect
42301            .transpile(
42302                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
42303                DialectType::Snowflake,
42304            )
42305            .unwrap();
42306        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
42307        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
42308    }
42309
42310    #[test]
42311    fn test_array_contains_snowflake_to_duckdb() {
42312        let dialect = Dialect::get(DialectType::Snowflake);
42313        let result = dialect
42314            .transpile(
42315                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
42316                DialectType::DuckDB,
42317            )
42318            .unwrap();
42319        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
42320        assert!(
42321            result[0].contains("CASE WHEN"),
42322            "Expected CASE WHEN: {}",
42323            result[0]
42324        );
42325        assert!(
42326            result[0].contains("NULLIF"),
42327            "Expected NULLIF: {}",
42328            result[0]
42329        );
42330        assert!(
42331            result[0].contains("ARRAY_CONTAINS"),
42332            "Expected ARRAY_CONTAINS: {}",
42333            result[0]
42334        );
42335    }
42336
42337    #[test]
42338    fn test_array_distinct_snowflake_to_duckdb() {
42339        let dialect = Dialect::get(DialectType::Snowflake);
42340        let result = dialect
42341            .transpile(
42342                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
42343                DialectType::DuckDB,
42344            )
42345            .unwrap();
42346        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
42347        assert!(
42348            result[0].contains("CASE WHEN"),
42349            "Expected CASE WHEN: {}",
42350            result[0]
42351        );
42352        assert!(
42353            result[0].contains("LIST_DISTINCT"),
42354            "Expected LIST_DISTINCT: {}",
42355            result[0]
42356        );
42357        assert!(
42358            result[0].contains("LIST_APPEND"),
42359            "Expected LIST_APPEND: {}",
42360            result[0]
42361        );
42362        assert!(
42363            result[0].contains("LIST_FILTER"),
42364            "Expected LIST_FILTER: {}",
42365            result[0]
42366        );
42367    }
42368}