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 more than 30 SQL dialects. 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#[cfg(feature = "transpile")]
24mod normalization;
25
26#[cfg(feature = "dialect-athena")]
27mod athena;
28#[cfg(feature = "dialect-bigquery")]
29mod bigquery;
30#[cfg(feature = "dialect-clickhouse")]
31mod clickhouse;
32#[cfg(feature = "dialect-cockroachdb")]
33mod cockroachdb;
34#[cfg(feature = "dialect-databricks")]
35mod databricks;
36#[cfg(feature = "dialect-datafusion")]
37mod datafusion;
38#[cfg(feature = "dialect-doris")]
39mod doris;
40#[cfg(feature = "dialect-dremio")]
41mod dremio;
42#[cfg(feature = "dialect-drill")]
43mod drill;
44#[cfg(feature = "dialect-druid")]
45mod druid;
46#[cfg(feature = "dialect-duckdb")]
47mod duckdb;
48#[cfg(feature = "dialect-dune")]
49mod dune;
50#[cfg(feature = "dialect-exasol")]
51mod exasol;
52#[cfg(feature = "dialect-fabric")]
53mod fabric;
54#[cfg(feature = "dialect-hana")]
55mod hana;
56#[cfg(feature = "dialect-hive")]
57mod hive;
58#[cfg(feature = "dialect-materialize")]
59mod materialize;
60#[cfg(feature = "dialect-mysql")]
61mod mysql;
62#[cfg(feature = "dialect-oracle")]
63mod oracle;
64#[cfg(feature = "dialect-postgresql")]
65mod postgres;
66#[cfg(feature = "dialect-presto")]
67mod presto;
68#[cfg(feature = "dialect-redshift")]
69mod redshift;
70#[cfg(feature = "dialect-risingwave")]
71mod risingwave;
72#[cfg(feature = "dialect-singlestore")]
73mod singlestore;
74#[cfg(feature = "dialect-snowflake")]
75mod snowflake;
76#[cfg(feature = "dialect-solr")]
77mod solr;
78#[cfg(feature = "dialect-spark")]
79mod spark;
80#[cfg(feature = "dialect-sqlite")]
81mod sqlite;
82#[cfg(feature = "dialect-starrocks")]
83mod starrocks;
84#[cfg(feature = "dialect-tableau")]
85mod tableau;
86#[cfg(feature = "dialect-teradata")]
87mod teradata;
88#[cfg(feature = "dialect-tidb")]
89mod tidb;
90#[cfg(feature = "dialect-trino")]
91mod trino;
92#[cfg(any(feature = "dialect-tsql", feature = "dialect-fabric"))]
93mod tsql;
94#[cfg(feature = "dialect-vertica")]
95mod vertica;
96
97pub use generic::GenericDialect; // Always available
98
99#[cfg(feature = "dialect-athena")]
100pub use athena::AthenaDialect;
101#[cfg(feature = "dialect-bigquery")]
102pub use bigquery::BigQueryDialect;
103#[cfg(feature = "dialect-clickhouse")]
104pub use clickhouse::ClickHouseDialect;
105#[cfg(feature = "dialect-cockroachdb")]
106pub use cockroachdb::CockroachDBDialect;
107#[cfg(feature = "dialect-databricks")]
108pub use databricks::DatabricksDialect;
109#[cfg(feature = "dialect-datafusion")]
110pub use datafusion::DataFusionDialect;
111#[cfg(feature = "dialect-doris")]
112pub use doris::DorisDialect;
113#[cfg(feature = "dialect-dremio")]
114pub use dremio::DremioDialect;
115#[cfg(feature = "dialect-drill")]
116pub use drill::DrillDialect;
117#[cfg(feature = "dialect-druid")]
118pub use druid::DruidDialect;
119#[cfg(feature = "dialect-duckdb")]
120pub use duckdb::DuckDBDialect;
121#[cfg(feature = "dialect-dune")]
122pub use dune::DuneDialect;
123#[cfg(feature = "dialect-exasol")]
124pub use exasol::ExasolDialect;
125#[cfg(feature = "dialect-fabric")]
126pub use fabric::FabricDialect;
127#[cfg(feature = "dialect-hana")]
128pub use hana::HanaDialect;
129#[cfg(feature = "dialect-hive")]
130pub use hive::HiveDialect;
131#[cfg(feature = "dialect-materialize")]
132pub use materialize::MaterializeDialect;
133#[cfg(feature = "dialect-mysql")]
134pub use mysql::MySQLDialect;
135#[cfg(feature = "dialect-oracle")]
136pub use oracle::OracleDialect;
137#[cfg(feature = "dialect-postgresql")]
138pub use postgres::PostgresDialect;
139#[cfg(feature = "dialect-presto")]
140pub use presto::PrestoDialect;
141#[cfg(feature = "dialect-redshift")]
142pub use redshift::RedshiftDialect;
143#[cfg(feature = "dialect-risingwave")]
144pub use risingwave::RisingWaveDialect;
145#[cfg(feature = "dialect-singlestore")]
146pub use singlestore::SingleStoreDialect;
147#[cfg(feature = "dialect-snowflake")]
148pub use snowflake::SnowflakeDialect;
149#[cfg(feature = "dialect-solr")]
150pub use solr::SolrDialect;
151#[cfg(feature = "dialect-spark")]
152pub use spark::SparkDialect;
153#[cfg(feature = "dialect-sqlite")]
154pub use sqlite::SQLiteDialect;
155#[cfg(feature = "dialect-starrocks")]
156pub use starrocks::StarRocksDialect;
157#[cfg(feature = "dialect-tableau")]
158pub use tableau::TableauDialect;
159#[cfg(feature = "dialect-teradata")]
160pub use teradata::TeradataDialect;
161#[cfg(feature = "dialect-tidb")]
162pub use tidb::TiDBDialect;
163#[cfg(feature = "dialect-trino")]
164pub use trino::TrinoDialect;
165#[cfg(feature = "dialect-tsql")]
166pub use tsql::TSQLDialect;
167#[cfg(feature = "dialect-vertica")]
168pub use vertica::VerticaDialect;
169
170use crate::error::Result;
171#[cfg(feature = "transpile")]
172use crate::expressions::{
173    BinaryOp, Case, Cast, ColumnConstraint, DateBin, DateTimeField, Fetch, Function, Identifier,
174    Interval, IntervalUnit, IntervalUnitSpec, JoinKind, Literal, Offset, Over, Select, Subquery,
175    Top, Var, WindowFrame, WindowFrameBound, WindowFrameKind,
176};
177use crate::expressions::{DataType, Expression};
178#[cfg(any(
179    feature = "transpile",
180    feature = "ast-tools",
181    feature = "generate",
182    feature = "semantic"
183))]
184use crate::expressions::{From, FunctionBody, Join, Null, OrderBy, OutputClause, TableRef, With};
185#[cfg(feature = "transpile")]
186use crate::generator::UnsupportedLevel;
187#[cfg(feature = "generate")]
188use crate::generator::{Generator, GeneratorConfig};
189#[cfg(feature = "transpile")]
190use crate::guard::enforce_generate_ast;
191use crate::guard::{enforce_input, ComplexityGuardOptions, ParseOptions};
192#[cfg(feature = "transpile")]
193use crate::helper::find_new_name;
194use crate::parser::Parser;
195#[cfg(feature = "transpile")]
196use crate::tokens::TokenType;
197use crate::tokens::{Token, Tokenizer, TokenizerConfig};
198#[cfg(feature = "transpile")]
199use crate::traversal::{is_aggregate, ExpressionWalk};
200use serde::{Deserialize, Serialize};
201use std::collections::HashMap;
202#[cfg(feature = "transpile")]
203use std::collections::HashSet;
204use std::sync::{Arc, LazyLock, RwLock};
205
206/// Enumeration of all supported SQL dialects.
207///
208/// Each variant corresponds to a specific SQL database engine or query language.
209/// The `Generic` variant represents standard SQL with no dialect-specific behavior,
210/// and is used as the default when no dialect is specified.
211///
212/// Dialect names are case-insensitive when parsed from strings via [`FromStr`].
213/// Some dialects accept aliases (e.g., "mssql" and "sqlserver" both resolve to [`TSQL`](DialectType::TSQL)).
214#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
215#[cfg_attr(feature = "bindings", derive(ts_rs::TS))]
216#[serde(rename_all = "lowercase")]
217pub enum DialectType {
218    /// Standard SQL with no dialect-specific behavior (default).
219    Generic,
220    /// PostgreSQL -- advanced open-source relational database.
221    PostgreSQL,
222    /// MySQL -- widely-used open-source relational database (also accepts "mysql").
223    MySQL,
224    /// Google BigQuery -- serverless cloud data warehouse with unique syntax (backtick quoting, STRUCT types, QUALIFY).
225    BigQuery,
226    /// Snowflake -- cloud data platform with QUALIFY clause, FLATTEN, and variant types.
227    Snowflake,
228    /// DuckDB -- in-process analytical database with modern SQL extensions.
229    DuckDB,
230    /// SQLite -- lightweight embedded relational database.
231    SQLite,
232    /// Apache Hive -- data warehouse on Hadoop with HiveQL syntax.
233    Hive,
234    /// Apache Spark SQL -- distributed query engine (also accepts "spark2").
235    Spark,
236    /// Trino -- distributed SQL query engine (formerly PrestoSQL).
237    Trino,
238    /// PrestoDB -- distributed SQL query engine for big data.
239    Presto,
240    /// Amazon Redshift -- cloud data warehouse based on PostgreSQL.
241    Redshift,
242    /// Transact-SQL (T-SQL) -- Microsoft SQL Server and Azure SQL (also accepts "mssql", "sqlserver").
243    TSQL,
244    /// Oracle Database -- commercial relational database with PL/SQL extensions.
245    Oracle,
246    /// ClickHouse -- column-oriented OLAP database for real-time analytics.
247    ClickHouse,
248    /// Databricks SQL -- Spark-based lakehouse platform with QUALIFY support.
249    Databricks,
250    /// Amazon Athena -- serverless query service (hybrid Trino/Hive engine).
251    Athena,
252    /// Teradata -- enterprise data warehouse with proprietary SQL extensions.
253    Teradata,
254    /// Apache Doris -- real-time analytical database (MySQL-compatible).
255    Doris,
256    /// StarRocks -- sub-second OLAP database (MySQL-compatible).
257    StarRocks,
258    /// Materialize -- streaming SQL database built on differential dataflow.
259    Materialize,
260    /// RisingWave -- distributed streaming database with PostgreSQL compatibility.
261    RisingWave,
262    /// SingleStore (formerly MemSQL) -- distributed SQL database (also accepts "memsql").
263    SingleStore,
264    /// CockroachDB -- distributed SQL database with PostgreSQL compatibility (also accepts "cockroach").
265    CockroachDB,
266    /// TiDB -- distributed HTAP database with MySQL compatibility.
267    TiDB,
268    /// Apache Druid -- real-time analytics database.
269    Druid,
270    /// Apache Solr -- search platform with SQL interface.
271    Solr,
272    /// Tableau -- data visualization platform with its own SQL dialect.
273    Tableau,
274    /// Dune Analytics -- blockchain analytics SQL engine.
275    Dune,
276    /// Microsoft Fabric -- unified analytics platform (T-SQL based).
277    Fabric,
278    /// Apache Drill -- schema-free SQL query engine for big data.
279    Drill,
280    /// Dremio -- data lakehouse platform with Arrow-based query engine.
281    Dremio,
282    /// Exasol -- in-memory analytic database.
283    Exasol,
284    /// Apache DataFusion -- Arrow-based query engine with modern SQL extensions.
285    DataFusion,
286    /// SAP HANA Cloud and SAP HANA Platform SQL.
287    HANA,
288    /// Vertica (OpenText Analytics Database) -- columnar MPP analytic database.
289    Vertica,
290}
291
292impl DialectType {
293    /// Whether SELECT projections may use string literals as column aliases.
294    /// DuckDB permits this only when the alias has an explicit AS keyword.
295    pub(crate) const fn supports_string_aliases(self, explicit_as: bool) -> bool {
296        matches!(
297            self,
298            DialectType::TSQL | DialectType::Fabric | DialectType::MySQL | DialectType::SQLite
299        ) || (matches!(self, DialectType::DuckDB) && explicit_as)
300    }
301
302    /// Whether this dialect natively accepts DuckDB-style `ORDER BY ALL`
303    /// (a bare `ALL` keyword meaning "every projected column, in order").
304    /// Other targets need it rewritten to positional `ORDER BY 1, 2, ...`.
305    pub(crate) const fn supports_order_by_all(self) -> bool {
306        matches!(
307            self,
308            DialectType::DuckDB
309                | DialectType::Snowflake
310                | DialectType::ClickHouse
311                | DialectType::Databricks
312                | DialectType::Spark
313        )
314    }
315}
316
317impl Default for DialectType {
318    fn default() -> Self {
319        DialectType::Generic
320    }
321}
322
323impl std::fmt::Display for DialectType {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        match self {
326            DialectType::Generic => write!(f, "generic"),
327            DialectType::PostgreSQL => write!(f, "postgresql"),
328            DialectType::MySQL => write!(f, "mysql"),
329            DialectType::BigQuery => write!(f, "bigquery"),
330            DialectType::Snowflake => write!(f, "snowflake"),
331            DialectType::DuckDB => write!(f, "duckdb"),
332            DialectType::SQLite => write!(f, "sqlite"),
333            DialectType::Hive => write!(f, "hive"),
334            DialectType::Spark => write!(f, "spark"),
335            DialectType::Trino => write!(f, "trino"),
336            DialectType::Presto => write!(f, "presto"),
337            DialectType::Redshift => write!(f, "redshift"),
338            DialectType::TSQL => write!(f, "tsql"),
339            DialectType::Oracle => write!(f, "oracle"),
340            DialectType::ClickHouse => write!(f, "clickhouse"),
341            DialectType::Databricks => write!(f, "databricks"),
342            DialectType::Athena => write!(f, "athena"),
343            DialectType::Teradata => write!(f, "teradata"),
344            DialectType::Doris => write!(f, "doris"),
345            DialectType::StarRocks => write!(f, "starrocks"),
346            DialectType::Materialize => write!(f, "materialize"),
347            DialectType::RisingWave => write!(f, "risingwave"),
348            DialectType::SingleStore => write!(f, "singlestore"),
349            DialectType::CockroachDB => write!(f, "cockroachdb"),
350            DialectType::TiDB => write!(f, "tidb"),
351            DialectType::Druid => write!(f, "druid"),
352            DialectType::Solr => write!(f, "solr"),
353            DialectType::Tableau => write!(f, "tableau"),
354            DialectType::Dune => write!(f, "dune"),
355            DialectType::Fabric => write!(f, "fabric"),
356            DialectType::Drill => write!(f, "drill"),
357            DialectType::Dremio => write!(f, "dremio"),
358            DialectType::Exasol => write!(f, "exasol"),
359            DialectType::DataFusion => write!(f, "datafusion"),
360            DialectType::HANA => write!(f, "hana"),
361            DialectType::Vertica => write!(f, "vertica"),
362        }
363    }
364}
365
366impl std::str::FromStr for DialectType {
367    type Err = crate::error::Error;
368
369    fn from_str(s: &str) -> Result<Self> {
370        match s.to_ascii_lowercase().as_str() {
371            "generic" | "" => Ok(DialectType::Generic),
372            "postgres" | "postgresql" => Ok(DialectType::PostgreSQL),
373            "mysql" => Ok(DialectType::MySQL),
374            "bigquery" => Ok(DialectType::BigQuery),
375            "snowflake" => Ok(DialectType::Snowflake),
376            "duckdb" => Ok(DialectType::DuckDB),
377            "sqlite" => Ok(DialectType::SQLite),
378            "hive" => Ok(DialectType::Hive),
379            "spark" | "spark2" => Ok(DialectType::Spark),
380            "trino" => Ok(DialectType::Trino),
381            "presto" => Ok(DialectType::Presto),
382            "redshift" => Ok(DialectType::Redshift),
383            "tsql" | "mssql" | "sqlserver" => Ok(DialectType::TSQL),
384            "oracle" => Ok(DialectType::Oracle),
385            "clickhouse" => Ok(DialectType::ClickHouse),
386            "databricks" => Ok(DialectType::Databricks),
387            "athena" => Ok(DialectType::Athena),
388            "teradata" => Ok(DialectType::Teradata),
389            "doris" => Ok(DialectType::Doris),
390            "starrocks" => Ok(DialectType::StarRocks),
391            "materialize" => Ok(DialectType::Materialize),
392            "risingwave" => Ok(DialectType::RisingWave),
393            "singlestore" | "memsql" => Ok(DialectType::SingleStore),
394            "cockroachdb" | "cockroach" => Ok(DialectType::CockroachDB),
395            "tidb" => Ok(DialectType::TiDB),
396            "druid" => Ok(DialectType::Druid),
397            "solr" => Ok(DialectType::Solr),
398            "tableau" => Ok(DialectType::Tableau),
399            "dune" => Ok(DialectType::Dune),
400            "fabric" => Ok(DialectType::Fabric),
401            "drill" => Ok(DialectType::Drill),
402            "dremio" => Ok(DialectType::Dremio),
403            "exasol" => Ok(DialectType::Exasol),
404            "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Ok(DialectType::DataFusion),
405            "hana" | "saphana" | "sap_hana" => Ok(DialectType::HANA),
406            "vertica" => Ok(DialectType::Vertica),
407            _ => Err(crate::error::Error::parse(
408                format!("Unknown dialect: {}", s),
409                0,
410                0,
411                0,
412                0,
413            )),
414        }
415    }
416}
417
418/// Trait that each concrete SQL dialect must implement.
419///
420/// `DialectImpl` provides the configuration hooks and per-expression transform logic
421/// that distinguish one dialect from another. Implementors supply:
422///
423/// - A [`DialectType`] identifier.
424/// - Optional overrides for tokenizer and generator configuration (defaults to generic SQL).
425/// - An expression-level transform function ([`transform_expr`](DialectImpl::transform_expr))
426///   that rewrites individual AST nodes for this dialect (e.g., converting `NVL` to `COALESCE`).
427/// - An optional preprocessing step ([`preprocess`](DialectImpl::preprocess)) for whole-tree
428///   rewrites that must run before the recursive per-node transform (e.g., eliminating QUALIFY).
429///
430/// The default implementations are no-ops, so a minimal dialect only needs to provide
431/// [`dialect_type`](DialectImpl::dialect_type) and override the methods that differ from
432/// standard SQL.
433pub trait DialectImpl {
434    /// Returns the [`DialectType`] that identifies this dialect.
435    fn dialect_type(&self) -> DialectType;
436
437    /// Returns the tokenizer configuration for this dialect.
438    ///
439    /// Override to customize identifier quoting characters, string escape rules,
440    /// comment styles, and other lexing behavior.
441    fn tokenizer_config(&self) -> TokenizerConfig {
442        TokenizerConfig::default()
443    }
444
445    /// Returns the generator configuration for this dialect.
446    ///
447    /// Override to customize identifier quoting style, function name casing,
448    /// keyword casing, and other SQL generation behavior.
449    #[cfg(feature = "generate")]
450    fn generator_config(&self) -> GeneratorConfig {
451        GeneratorConfig::default()
452    }
453
454    /// Returns a generator configuration tailored to a specific expression.
455    ///
456    /// Override this for hybrid dialects like Athena that route to different SQL engines
457    /// based on expression type (e.g., Hive-style generation for DDL, Trino-style for DML).
458    /// The default delegates to [`generator_config`](DialectImpl::generator_config).
459    #[cfg(feature = "generate")]
460    fn generator_config_for_expr(&self, _expr: &Expression) -> GeneratorConfig {
461        self.generator_config()
462    }
463
464    /// Transforms a single expression node for this dialect, without recursing into children.
465    ///
466    /// This is the per-node rewrite hook invoked by [`transform_recursive`]. Return the
467    /// expression unchanged if no dialect-specific rewrite is needed. Transformations
468    /// typically include function renaming, operator substitution, and type mapping.
469    #[cfg(feature = "transpile")]
470    fn transform_expr(&self, expr: Expression) -> Result<Expression> {
471        Ok(expr)
472    }
473
474    /// Applies whole-tree preprocessing transforms before the recursive per-node pass.
475    ///
476    /// Override this to apply structural rewrites that must see the entire tree at once,
477    /// such as `eliminate_qualify`, `eliminate_distinct_on`, `ensure_bools`, or
478    /// `explode_projection_to_unnest`. The default is a no-op pass-through.
479    #[cfg(feature = "transpile")]
480    fn preprocess(&self, expr: Expression) -> Result<Expression> {
481        Ok(expr)
482    }
483}
484
485/// Recursively transforms a [`DataType`](crate::expressions::DataType), handling nested
486/// parametric types such as `ARRAY<INT>`, `STRUCT<a INT, b TEXT>`, and `MAP<STRING, INT>`.
487///
488/// The outer type is first passed through `transform_fn` as an `Expression::DataType`,
489/// and then nested element/field types are recursed into. This ensures that dialect-level
490/// type mappings (e.g., `INT` to `INTEGER`) propagate into complex nested types.
491#[cfg(any(
492    feature = "transpile",
493    feature = "ast-tools",
494    feature = "generate",
495    feature = "semantic"
496))]
497pub(crate) fn transform_data_type_recursive<F>(
498    dt: crate::expressions::DataType,
499    transform_fn: &F,
500) -> Result<crate::expressions::DataType>
501where
502    F: Fn(Expression) -> Result<Expression>,
503{
504    use crate::expressions::DataType;
505    // First, transform the outermost type through the expression system
506    let dt_expr = transform_fn(Expression::DataType(dt))?;
507    let dt = match dt_expr {
508        Expression::DataType(d) => d,
509        _ => {
510            return Ok(match dt_expr {
511                _ => DataType::Custom {
512                    name: "UNKNOWN".to_string(),
513                },
514            })
515        }
516    };
517    // Then recurse into nested types
518    match dt {
519        DataType::Array {
520            element_type,
521            dimension,
522        } => {
523            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
524            Ok(DataType::Array {
525                element_type: Box::new(inner),
526                dimension,
527            })
528        }
529        DataType::List { element_type } => {
530            let inner = transform_data_type_recursive(*element_type, transform_fn)?;
531            Ok(DataType::List {
532                element_type: Box::new(inner),
533            })
534        }
535        DataType::Struct { fields, nested } => {
536            let mut new_fields = Vec::new();
537            for mut field in fields {
538                field.data_type = transform_data_type_recursive(field.data_type, transform_fn)?;
539                new_fields.push(field);
540            }
541            Ok(DataType::Struct {
542                fields: new_fields,
543                nested,
544            })
545        }
546        DataType::Map {
547            key_type,
548            value_type,
549        } => {
550            let k = transform_data_type_recursive(*key_type, transform_fn)?;
551            let v = transform_data_type_recursive(*value_type, transform_fn)?;
552            Ok(DataType::Map {
553                key_type: Box::new(k),
554                value_type: Box::new(v),
555            })
556        }
557        other => Ok(other),
558    }
559}
560
561/// Convert DuckDB C-style format strings to Presto C-style format strings.
562/// DuckDB and Presto both use C-style % directives but with different specifiers for some cases.
563#[cfg(feature = "transpile")]
564fn duckdb_to_presto_format(fmt: &str) -> String {
565    // Order matters: handle longer patterns first to avoid partial replacements
566    let mut result = fmt.to_string();
567    // First pass: mark multi-char patterns with placeholders
568    result = result.replace("%-m", "\x01NOPADM\x01");
569    result = result.replace("%-d", "\x01NOPADD\x01");
570    result = result.replace("%-I", "\x01NOPADI\x01");
571    result = result.replace("%-H", "\x01NOPADH\x01");
572    result = result.replace("%H:%M:%S", "\x01HMS\x01");
573    result = result.replace("%Y-%m-%d", "\x01YMD\x01");
574    // Now convert individual specifiers
575    result = result.replace("%M", "%i");
576    result = result.replace("%S", "%s");
577    // Restore multi-char patterns with Presto equivalents
578    result = result.replace("\x01NOPADM\x01", "%c");
579    result = result.replace("\x01NOPADD\x01", "%e");
580    result = result.replace("\x01NOPADI\x01", "%l");
581    result = result.replace("\x01NOPADH\x01", "%k");
582    result = result.replace("\x01HMS\x01", "%T");
583    result = result.replace("\x01YMD\x01", "%Y-%m-%d");
584    result
585}
586
587/// Convert DuckDB C-style format strings to BigQuery format strings.
588/// BigQuery uses a mix of strftime-like directives.
589#[cfg(feature = "transpile")]
590fn duckdb_to_bigquery_format(fmt: &str) -> String {
591    let mut result = fmt.to_string();
592    // Handle longer patterns first
593    result = result.replace("%-d", "%e");
594    result = result.replace("%Y-%m-%d %H:%M:%S", "%F %T");
595    result = result.replace("%Y-%m-%d", "%F");
596    result = result.replace("%H:%M:%S", "%T");
597    result
598}
599
600#[cfg(feature = "transpile")]
601fn presto_to_java_format(fmt: &str) -> String {
602    fmt.replace("%Y", "yyyy")
603        .replace("%m", "MM")
604        .replace("%d", "dd")
605        .replace("%H", "HH")
606        .replace("%i", "mm")
607        .replace("%S", "ss")
608        .replace("%s", "ss")
609        .replace("%y", "yy")
610        .replace("%T", "HH:mm:ss")
611        .replace("%F", "yyyy-MM-dd")
612        .replace("%M", "MMMM")
613}
614
615#[cfg(feature = "transpile")]
616fn presto_to_java_parse_format(fmt: &str) -> String {
617    fmt.replace("%Y", "yyyy")
618        .replace("%m", "M")
619        .replace("%d", "d")
620        .replace("%H", "H")
621        .replace("%i", "m")
622        .replace("%S", "s")
623        .replace("%s", "s")
624        .replace("%y", "yy")
625        .replace("%T", "H:m:s")
626        .replace("%F", "yyyy-M-d")
627        .replace("%M", "MMMM")
628}
629
630#[cfg(feature = "transpile")]
631fn normalize_presto_format(fmt: &str) -> String {
632    fmt.replace("%H:%i:%S", "%T").replace("%H:%i:%s", "%T")
633}
634
635#[cfg(feature = "transpile")]
636fn presto_to_duckdb_format(fmt: &str) -> String {
637    fmt.replace("%i", "%M")
638        .replace("%s", "%S")
639        .replace("%T", "%H:%M:%S")
640}
641
642#[cfg(feature = "transpile")]
643fn presto_to_bigquery_format(fmt: &str) -> String {
644    fmt.replace("%Y-%m-%d", "%F")
645        .replace("%H:%i:%S", "%T")
646        .replace("%H:%i:%s", "%T")
647        .replace("%i", "%M")
648        .replace("%s", "%S")
649}
650
651#[cfg(feature = "transpile")]
652fn is_default_presto_timestamp_format(fmt: &str) -> bool {
653    let normalized = normalize_presto_format(fmt);
654    normalized == "%Y-%m-%d %T"
655        || normalized == "%Y-%m-%d %H:%i:%S"
656        || fmt == "%Y-%m-%d %H:%i:%S"
657        || fmt == "%Y-%m-%d %T"
658}
659
660#[cfg(feature = "transpile")]
661fn is_default_presto_date_format(fmt: &str) -> bool {
662    fmt == "%Y-%m-%d" || fmt == "%F"
663}
664
665/// The parser marks the ORDER BY ALL keyword as a Var, never an identifier.
666#[cfg(feature = "generate")]
667pub(crate) fn is_order_by_all_marker(e: &Expression) -> bool {
668    matches!(e, Expression::Var(v) if v.this.eq_ignore_ascii_case("all"))
669}
670
671#[cfg(feature = "transpile")]
672fn is_order_by_all(order_by: &Option<crate::expressions::OrderBy>) -> bool {
673    order_by.as_ref().is_some_and(|ob| {
674        ob.expressions.len() == 1 && is_order_by_all_marker(&ob.expressions[0].this)
675    })
676}
677
678/// Whether `e` is a projection item that can produce more or fewer than one
679/// output column (`*`, or DuckDB's `COLUMNS(...)` macro), making the output
680/// column count unknowable from the AST alone.
681#[cfg(feature = "transpile")]
682fn has_unknown_column_count(e: &Expression) -> bool {
683    use crate::expressions::Expression as E;
684    let mut inner = e;
685    loop {
686        inner = match inner {
687            E::Alias(a) => &a.this,
688            E::Paren(p) => &p.this,
689            _ => break,
690        };
691    }
692    if matches!(inner, E::Star(_)) {
693        return true;
694    }
695    // COLUMNS still expands inside arithmetic, casts and aggregate/function
696    // arguments. COUNT(*) and scalar subqueries, however, produce one column.
697    contains_in_projection(inner, |node| match node {
698        E::Columns(_) => true,
699        E::Function(f) => {
700            !f.quoted && f.qualified_name.is_empty() && f.name.eq_ignore_ascii_case("COLUMNS")
701        }
702        E::MethodCall(m) => m.method.name.eq_ignore_ascii_case("COLUMNS"),
703        _ => false,
704    })
705}
706
707#[cfg(feature = "transpile")]
708pub(crate) fn contains_in_projection(
709    e: &Expression,
710    predicate: impl Fn(&Expression) -> bool,
711) -> bool {
712    let mut pending = vec![e];
713    while let Some(node) = pending.pop() {
714        if matches!(
715            node,
716            Expression::Subquery(_) | Expression::Select(_) | Expression::Exists(_)
717        ) {
718            continue;
719        }
720        if predicate(node) {
721            return true;
722        }
723        crate::ast_children::for_each_child_untracked(node, |child| pending.push(child));
724    }
725    false
726}
727
728/// The number of columns a query block projects, when that count is known
729/// statically from the AST. Compound queries (UNION/INTERSECT/EXCEPT) take
730/// their width from the left-hand operand, per standard SQL set-operation
731/// rules (both sides must already agree on column count).
732#[cfg(feature = "transpile")]
733fn query_output_width(e: &Expression) -> Option<usize> {
734    use crate::expressions::Expression as E;
735    match e {
736        E::Select(sel) => {
737            if sel.expressions.is_empty() || sel.expressions.iter().any(has_unknown_column_count) {
738                None
739            } else {
740                Some(sel.expressions.len())
741            }
742        }
743        E::Paren(p) => query_output_width(&p.this),
744        E::Subquery(s) => query_output_width(&s.this),
745        E::Union(u) if !u.by_name => query_output_width(&u.left),
746        E::Intersect(i) => query_output_width(&i.left),
747        E::Except(ex) => query_output_width(&ex.left),
748        _ => None,
749    }
750}
751
752/// Rewrite an `ORDER BY ALL` clause into positional `ORDER BY 1..n` over
753/// `width` columns, in place. No-op if `order_by` isn't the ALL marker.
754/// Errors if the marker is present but `width` is unknown (e.g. a `*` or
755/// `COLUMNS(...)` projection, where positional ordinals can't be derived).
756///
757/// The source's explicit direction and NULL order (if any) carry over to each
758/// positional entry unchanged; the later cross-dialect NULL-ordering pass
759/// fills in the source dialect's implied default where the target's differs.
760/// MySQL is the exception: it has no NULLS FIRST/LAST syntax and is skipped by
761/// that pass, so compare every supported source's default against MySQL here.
762#[cfg(feature = "transpile")]
763fn expand_order_by_all(
764    order_by: &mut Option<crate::expressions::OrderBy>,
765    width: Option<usize>,
766    source: DialectType,
767    target: DialectType,
768) -> Result<()> {
769    use crate::expressions::Expression as E;
770    if !is_order_by_all(order_by) {
771        return Ok(());
772    }
773    let Some(n) = width else {
774        return Err(crate::error::Error::unsupported(
775            "ORDER BY ALL over a projection with an unknown column count (*, COLUMNS(...), or UNION BY NAME) cannot be expanded to positional ORDER BY",
776            target.to_string(),
777        ));
778    };
779    let ob = order_by.as_mut().unwrap();
780    let (desc, nulls_first) = (ob.expressions[0].desc, ob.expressions[0].nulls_first);
781
782    // An ordinal cannot be used inside a CASE expression to emulate NULL
783    // placement: it would become a constant instead of a column reference.
784    if matches!(target, DialectType::MySQL) {
785        let required_nulls_first = nulls_first.unwrap_or(match source {
786            DialectType::DuckDB | DialectType::ClickHouse => false,
787            DialectType::Snowflake => desc,
788            DialectType::Spark | DialectType::Databricks => !desc,
789            _ => unreachable!("ORDER BY ALL source capability must be checked"),
790        });
791        if required_nulls_first != !desc {
792            return Err(crate::error::Error::unsupported(
793                "ORDER BY ALL's NULL ordering (MySQL's default NULL placement does not match and cannot be emulated with positional ordering)",
794                target.to_string(),
795            ));
796        }
797    }
798
799    ob.expressions = (1..=n as i64)
800        .map(|i| {
801            let mut o = ob.expressions[0].clone();
802            o.this = E::number(i);
803            if target == DialectType::MySQL {
804                // Matching explicit NULL placement is redundant and MySQL
805                // cannot print the NULLS FIRST/LAST syntax.
806                o.nulls_first = None;
807            }
808            o
809        })
810        .collect();
811    Ok(())
812}
813
814/// Expand when the target lacks ALL, or when a native target cannot use it
815/// for this query (aggregate projections in Snowflake, DISTINCT ON emulation).
816#[cfg(feature = "transpile")]
817fn expand_select_order_by_all(
818    mut sel: Box<crate::expressions::Select>,
819    source: DialectType,
820    target: DialectType,
821) -> Result<Box<crate::expressions::Select>> {
822    if !is_order_by_all(&sel.order_by) {
823        return Ok(sel);
824    }
825    let emulate_distinct = sel
826        .distinct_on
827        .as_ref()
828        .is_some_and(|cols| !cols.is_empty())
829        && !Dialect::target_supports_distinct_on(target);
830    let snowflake_aggregate = target == DialectType::Snowflake
831        && sel
832            .expressions
833            .iter()
834            .any(|e| contains_in_projection(e, crate::traversal::is_aggregate));
835    if target.supports_order_by_all() && !emulate_distinct && !snowflake_aggregate {
836        return Ok(sel);
837    }
838    let width =
839        if sel.expressions.is_empty() || sel.expressions.iter().any(has_unknown_column_count) {
840            None
841        } else {
842            Some(sel.expressions.len())
843        };
844    expand_order_by_all(&mut sel.order_by, width, source, target)?;
845    Ok(sel)
846}
847
848/// Applies a dialect transform bottom-up through selected syntax children.
849///
850/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
851/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
852/// trees, and common binary/unary expression chains). Less common shapes currently
853/// reuse the reference recursive implementation so semantics stay identical while
854/// the hot path avoids stack growth.
855#[cfg(any(
856    feature = "transpile",
857    feature = "ast-tools",
858    feature = "generate",
859    feature = "semantic"
860))]
861pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
862where
863    F: Fn(Expression) -> Result<Expression>,
864{
865    #[cfg(feature = "stacker")]
866    {
867        let red_zone = if cfg!(debug_assertions) {
868            4 * 1024 * 1024
869        } else {
870            1024 * 1024
871        };
872        stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
873            transform_recursive_inner(expr, transform_fn)
874        })
875    }
876    #[cfg(not(feature = "stacker"))]
877    {
878        transform_recursive_inner(expr, transform_fn)
879    }
880}
881
882#[cfg(any(
883    feature = "transpile",
884    feature = "ast-tools",
885    feature = "generate",
886    feature = "semantic"
887))]
888fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
889where
890    F: Fn(Expression) -> Result<Expression>,
891{
892    // These are the shapes handled by the former explicit-stack fast path. Other
893    // nodes retain the reference transformer's selective and wrapper-aware child
894    // semantics even though all physical children are visible to traversal APIs.
895    fn uses_generated_dispatch(expression: &Expression) -> bool {
896        match expression {
897            Expression::CreateTable(_) => true,
898            Expression::Select(select) => {
899                select.joins.is_empty()
900                    && select.with.is_none()
901                    && select.order_by.is_none()
902                    && select.windows.is_none()
903                    && select.settings.is_none()
904            }
905            Expression::Union(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
906            Expression::Intersect(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
907            Expression::Except(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
908            Expression::Subquery(subquery) => subquery.order_by.is_none(),
909            Expression::Vertica(_)
910            | Expression::Literal(_)
911            | Expression::Boolean(_)
912            | Expression::Null(_)
913            | Expression::Identifier(_)
914            | Expression::Star(_)
915            | Expression::Parameter(_)
916            | Expression::Placeholder(_)
917            | Expression::SessionParameter(_)
918            | Expression::Interval(_)
919            | Expression::Alias(_)
920            | Expression::Paren(_)
921            | Expression::Not(_)
922            | Expression::Neg(_)
923            | Expression::IsNull(_)
924            | Expression::IsTrue(_)
925            | Expression::IsFalse(_)
926            | Expression::Exists(_)
927            | Expression::Any(_)
928            | Expression::All(_)
929            | Expression::TableArgument(_)
930            | Expression::And(_)
931            | Expression::Or(_)
932            | Expression::Add(_)
933            | Expression::Sub(_)
934            | Expression::Mul(_)
935            | Expression::Div(_)
936            | Expression::IntDiv(_)
937            | Expression::Eq(_)
938            | Expression::NullSafeEq(_)
939            | Expression::NullSafeNeq(_)
940            | Expression::Lt(_)
941            | Expression::Gt(_)
942            | Expression::Neq(_)
943            | Expression::Lte(_)
944            | Expression::Gte(_)
945            | Expression::Mod(_)
946            | Expression::Concat(_)
947            | Expression::BitwiseAnd(_)
948            | Expression::BitwiseOr(_)
949            | Expression::BitwiseXor(_)
950            | Expression::Is(_)
951            | Expression::MemberOf(_)
952            | Expression::ArrayContainsAll(_)
953            | Expression::ArrayContainedBy(_)
954            | Expression::ArrayOverlaps(_)
955            | Expression::TsMatch(_)
956            | Expression::Adjacent(_)
957            | Expression::Like(_)
958            | Expression::ILike(_)
959            | Expression::Function(_)
960            | Expression::AggregateFunction(_)
961            | Expression::JSONValue(_)
962            | Expression::JSONTable(_)
963            | Expression::JSONColumnDef(_)
964            | Expression::JsonQuery(_)
965            | Expression::RegexpLike(_)
966            | Expression::RegexpReplace(_)
967            | Expression::RegexpExtract(_)
968            | Expression::RegexpInstr(_)
969            | Expression::RegexpCount(_)
970            | Expression::Cube(_)
971            | Expression::Rollup(_)
972            | Expression::GroupingSets(_)
973            | Expression::Hint(_)
974            | Expression::PartitionByProperty(_)
975            | Expression::StorageProperty(_)
976            | Expression::Hierarchy(_)
977            | Expression::ViewParameter(_)
978            | Expression::Call(_)
979            | Expression::Lead(_)
980            | Expression::Lag(_)
981            | Expression::Array(_)
982            | Expression::Tuple(_)
983            | Expression::ArrayFunc(_)
984            | Expression::Coalesce(_)
985            | Expression::Greatest(_)
986            | Expression::Least(_)
987            | Expression::ArrayConcat(_)
988            | Expression::ArrayIntersect(_)
989            | Expression::ArrayZip(_)
990            | Expression::MapConcat(_)
991            | Expression::JsonArray(_)
992            | Expression::From(_) => true,
993            _ => false,
994        }
995    }
996
997    crate::traversal::transform_with_dispatch(
998        expr,
999        transform_fn,
1000        &uses_generated_dispatch,
1001        &transform_recursive_reference,
1002    )
1003}
1004
1005#[cfg(any(
1006    feature = "transpile",
1007    feature = "ast-tools",
1008    feature = "generate",
1009    feature = "semantic"
1010))]
1011fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1012where
1013    F: Fn(Expression) -> Result<Expression>,
1014{
1015    match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1016        Expression::Table(table) => Ok(*table),
1017        _ => Err(crate::error::Error::parse(
1018            "TableRef transformation returned non-table expression",
1019            0,
1020            0,
1021            0,
1022            0,
1023        )),
1024    }
1025}
1026
1027#[cfg(any(
1028    feature = "transpile",
1029    feature = "ast-tools",
1030    feature = "generate",
1031    feature = "semantic"
1032))]
1033fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1034where
1035    F: Fn(Expression) -> Result<Expression>,
1036{
1037    match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1038        Expression::From(from) => Ok(*from),
1039        _ => Err(crate::error::Error::parse(
1040            "FROM transformation returned non-FROM expression",
1041            0,
1042            0,
1043            0,
1044            0,
1045        )),
1046    }
1047}
1048
1049#[cfg(any(
1050    feature = "transpile",
1051    feature = "ast-tools",
1052    feature = "generate",
1053    feature = "semantic"
1054))]
1055fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1056where
1057    F: Fn(Expression) -> Result<Expression>,
1058{
1059    join.this = transform_recursive(join.this, transform_fn)?;
1060    if let Some(on) = join.on.take() {
1061        join.on = Some(transform_recursive(on, transform_fn)?);
1062    }
1063    if let Some(match_condition) = join.match_condition.take() {
1064        join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1065    }
1066    join.pivots = join
1067        .pivots
1068        .into_iter()
1069        .map(|pivot| transform_recursive(pivot, transform_fn))
1070        .collect::<Result<Vec<_>>>()?;
1071
1072    match transform_fn(Expression::Join(Box::new(join)))? {
1073        Expression::Join(join) => Ok(*join),
1074        _ => Err(crate::error::Error::parse(
1075            "Join transformation returned non-join expression",
1076            0,
1077            0,
1078            0,
1079            0,
1080        )),
1081    }
1082}
1083
1084#[cfg(any(
1085    feature = "transpile",
1086    feature = "ast-tools",
1087    feature = "generate",
1088    feature = "semantic"
1089))]
1090fn transform_output_clause_recursive<F>(
1091    mut output: OutputClause,
1092    transform_fn: &F,
1093) -> Result<OutputClause>
1094where
1095    F: Fn(Expression) -> Result<Expression>,
1096{
1097    output.columns = output
1098        .columns
1099        .into_iter()
1100        .map(|column| transform_recursive(column, transform_fn))
1101        .collect::<Result<Vec<_>>>()?;
1102    if let Some(into_table) = output.into_table.take() {
1103        output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1104    }
1105    Ok(output)
1106}
1107
1108#[cfg(any(
1109    feature = "transpile",
1110    feature = "ast-tools",
1111    feature = "generate",
1112    feature = "semantic"
1113))]
1114fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1115where
1116    F: Fn(Expression) -> Result<Expression>,
1117{
1118    with.ctes = with
1119        .ctes
1120        .into_iter()
1121        .map(|mut cte| {
1122            cte.this = transform_recursive(cte.this, transform_fn)?;
1123            Ok(cte)
1124        })
1125        .collect::<Result<Vec<_>>>()?;
1126    if let Some(search) = with.search.take() {
1127        with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1128    }
1129    Ok(with)
1130}
1131
1132#[cfg(any(
1133    feature = "transpile",
1134    feature = "ast-tools",
1135    feature = "generate",
1136    feature = "semantic"
1137))]
1138fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1139where
1140    F: Fn(Expression) -> Result<Expression>,
1141{
1142    order.expressions = order
1143        .expressions
1144        .into_iter()
1145        .map(|mut ordered| {
1146            let original = ordered.this.clone();
1147            ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1148            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1149                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1150                Ok(_) | Err(_) => Ok(ordered),
1151            }
1152        })
1153        .collect::<Result<Vec<_>>>()?;
1154    Ok(order)
1155}
1156
1157#[cfg(any(
1158    feature = "transpile",
1159    feature = "ast-tools",
1160    feature = "generate",
1161    feature = "semantic"
1162))]
1163fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
1164where
1165    F: Fn(Expression) -> Result<Expression>,
1166{
1167    use crate::expressions::BinaryOp;
1168
1169    // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
1170    macro_rules! recurse_agg {
1171        ($variant:ident, $f:expr) => {{
1172            let mut f = $f;
1173            f.this = transform_recursive(f.this, transform_fn)?;
1174            if let Some(filter) = f.filter.take() {
1175                f.filter = Some(transform_recursive(filter, transform_fn)?);
1176            }
1177            for ord in &mut f.order_by {
1178                ord.this = transform_recursive(
1179                    std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
1180                    transform_fn,
1181                )?;
1182            }
1183            if let Some((ref mut expr, _)) = f.having_max {
1184                *expr = Box::new(transform_recursive(
1185                    std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
1186                    transform_fn,
1187                )?);
1188            }
1189            if let Some(limit) = f.limit.take() {
1190                f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
1191            }
1192            Expression::$variant(f)
1193        }};
1194    }
1195
1196    // Helper macro to transform binary ops with Box<BinaryOp>
1197    macro_rules! transform_binary {
1198        ($variant:ident, $op:expr) => {{
1199            let left = transform_recursive($op.left, transform_fn)?;
1200            let right = transform_recursive($op.right, transform_fn)?;
1201            Expression::$variant(Box::new(BinaryOp {
1202                left,
1203                right,
1204                left_comments: $op.left_comments,
1205                operator_comments: $op.operator_comments,
1206                trailing_comments: $op.trailing_comments,
1207                inferred_type: $op.inferred_type,
1208            }))
1209        }};
1210    }
1211
1212    // Fast path: leaf nodes never need child traversal, apply transform directly
1213    if matches!(
1214        &expr,
1215        Expression::Literal(_)
1216            | Expression::Boolean(_)
1217            | Expression::Null(_)
1218            | Expression::Identifier(_)
1219            | Expression::Star(_)
1220            | Expression::Parameter(_)
1221            | Expression::Placeholder(_)
1222            | Expression::SessionParameter(_)
1223    ) {
1224        return transform_fn(expr);
1225    }
1226
1227    // First recursively transform children, then apply the transform function
1228    let expr = match expr {
1229        Expression::Select(mut select) => {
1230            select.query_hints = std::mem::take(&mut select.query_hints)
1231                .into_iter()
1232                .map(|hint| transform_recursive(hint, transform_fn))
1233                .collect::<Result<Vec<_>>>()?;
1234            if let Some(extension) = &mut select.vertica {
1235                use crate::ast_children::AstNode;
1236                let mut failure = None;
1237                extension.visit_expressions_mut(&mut |child| {
1238                    if failure.is_none() {
1239                        let old =
1240                            std::mem::replace(child, Expression::Null(crate::expressions::Null));
1241                        match transform_recursive(old, transform_fn) {
1242                            Ok(new) => *child = new,
1243                            Err(error) => failure = Some(error),
1244                        }
1245                    }
1246                });
1247                if let Some(error) = failure {
1248                    return Err(error);
1249                }
1250            }
1251            select.expressions = select
1252                .expressions
1253                .into_iter()
1254                .map(|e| transform_recursive(e, transform_fn))
1255                .collect::<Result<Vec<_>>>()?;
1256
1257            // Transform FROM clause
1258            if let Some(mut from) = select.from.take() {
1259                from.expressions = from
1260                    .expressions
1261                    .into_iter()
1262                    .map(|e| transform_recursive(e, transform_fn))
1263                    .collect::<Result<Vec<_>>>()?;
1264                select.from = Some(from);
1265            }
1266
1267            // Transform JOINs - important for CROSS APPLY / LATERAL transformations
1268            select.joins = select
1269                .joins
1270                .into_iter()
1271                .map(|mut join| {
1272                    join.this = transform_recursive(join.this, transform_fn)?;
1273                    if let Some(on) = join.on.take() {
1274                        join.on = Some(transform_recursive(on, transform_fn)?);
1275                    }
1276                    // Wrap join in Expression::Join to allow transform_fn to transform it
1277                    match transform_fn(Expression::Join(Box::new(join)))? {
1278                        Expression::Join(j) => Ok(*j),
1279                        _ => Err(crate::error::Error::parse(
1280                            "Join transformation returned non-join expression",
1281                            0,
1282                            0,
1283                            0,
1284                            0,
1285                        )),
1286                    }
1287                })
1288                .collect::<Result<Vec<_>>>()?;
1289
1290            // Transform LATERAL VIEW expressions (Hive/Spark)
1291            select.lateral_views = select
1292                .lateral_views
1293                .into_iter()
1294                .map(|mut lv| {
1295                    lv.this = transform_recursive(lv.this, transform_fn)?;
1296                    Ok(lv)
1297                })
1298                .collect::<Result<Vec<_>>>()?;
1299
1300            // Transform WHERE clause
1301            if let Some(mut where_clause) = select.where_clause.take() {
1302                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1303                select.where_clause = Some(where_clause);
1304            }
1305
1306            // Transform GROUP BY
1307            if let Some(mut group_by) = select.group_by.take() {
1308                group_by.expressions = group_by
1309                    .expressions
1310                    .into_iter()
1311                    .map(|e| transform_recursive(e, transform_fn))
1312                    .collect::<Result<Vec<_>>>()?;
1313                select.group_by = Some(group_by);
1314            }
1315
1316            // Transform HAVING
1317            if let Some(mut having) = select.having.take() {
1318                having.this = transform_recursive(having.this, transform_fn)?;
1319                select.having = Some(having);
1320            }
1321
1322            // Transform WITH (CTEs)
1323            if let Some(mut with) = select.with.take() {
1324                with.ctes = with
1325                    .ctes
1326                    .into_iter()
1327                    .map(|mut cte| {
1328                        let original = cte.this.clone();
1329                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1330                        cte
1331                    })
1332                    .collect();
1333                select.with = Some(with);
1334            }
1335
1336            // Transform ORDER BY
1337            if let Some(mut order) = select.order_by.take() {
1338                order.expressions = order
1339                    .expressions
1340                    .into_iter()
1341                    .map(|o| {
1342                        let mut o = o;
1343                        let original = o.this.clone();
1344                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1345                        // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
1346                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1347                            Ok(Expression::Ordered(transformed)) => *transformed,
1348                            Ok(_) | Err(_) => o,
1349                        }
1350                    })
1351                    .collect();
1352                select.order_by = Some(order);
1353            }
1354
1355            // Transform WINDOW clause order_by
1356            if let Some(ref mut windows) = select.windows {
1357                for nw in windows.iter_mut() {
1358                    nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1359                        .into_iter()
1360                        .map(|o| {
1361                            let mut o = o;
1362                            let original = o.this.clone();
1363                            o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1364                            match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1365                                Ok(Expression::Ordered(transformed)) => *transformed,
1366                                Ok(_) | Err(_) => o,
1367                            }
1368                        })
1369                        .collect();
1370                }
1371            }
1372
1373            // Transform QUALIFY
1374            if let Some(mut qual) = select.qualify.take() {
1375                qual.this = transform_recursive(qual.this, transform_fn)?;
1376                select.qualify = Some(qual);
1377            }
1378
1379            Expression::Select(select)
1380        }
1381        Expression::Function(mut f) => {
1382            f.args = f
1383                .args
1384                .into_iter()
1385                .map(|e| transform_recursive(e, transform_fn))
1386                .collect::<Result<Vec<_>>>()?;
1387            Expression::Function(f)
1388        }
1389        Expression::AggregateFunction(mut f) => {
1390            f.args = f
1391                .args
1392                .into_iter()
1393                .map(|e| transform_recursive(e, transform_fn))
1394                .collect::<Result<Vec<_>>>()?;
1395            if let Some(filter) = f.filter {
1396                f.filter = Some(transform_recursive(filter, transform_fn)?);
1397            }
1398            Expression::AggregateFunction(f)
1399        }
1400        Expression::WindowFunction(mut wf) => {
1401            wf.this = transform_recursive(wf.this, transform_fn)?;
1402            wf.over.partition_by = wf
1403                .over
1404                .partition_by
1405                .into_iter()
1406                .map(|e| transform_recursive(e, transform_fn))
1407                .collect::<Result<Vec<_>>>()?;
1408            // Transform order_by items through Expression::Ordered wrapper
1409            wf.over.order_by = wf
1410                .over
1411                .order_by
1412                .into_iter()
1413                .map(|o| {
1414                    let mut o = o;
1415                    o.this = transform_recursive(o.this, transform_fn)?;
1416                    match transform_fn(Expression::Ordered(Box::new(o)))? {
1417                        Expression::Ordered(transformed) => Ok(*transformed),
1418                        _ => Err(crate::error::Error::parse(
1419                            "Ordered transformation returned non-Ordered expression",
1420                            0,
1421                            0,
1422                            0,
1423                            0,
1424                        )),
1425                    }
1426                })
1427                .collect::<Result<Vec<_>>>()?;
1428            Expression::WindowFunction(wf)
1429        }
1430        Expression::Alias(mut a) => {
1431            a.this = transform_recursive(a.this, transform_fn)?;
1432            Expression::Alias(a)
1433        }
1434        Expression::Cast(mut c) => {
1435            c.this = transform_recursive(c.this, transform_fn)?;
1436            // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
1437            c.to = transform_data_type_recursive(c.to, transform_fn)?;
1438            Expression::Cast(c)
1439        }
1440        Expression::And(op) => transform_binary!(And, *op),
1441        Expression::Or(op) => transform_binary!(Or, *op),
1442        Expression::Add(op) => transform_binary!(Add, *op),
1443        Expression::Sub(op) => transform_binary!(Sub, *op),
1444        Expression::Mul(op) => transform_binary!(Mul, *op),
1445        Expression::Div(op) => transform_binary!(Div, *op),
1446        Expression::Eq(op) => transform_binary!(Eq, *op),
1447        Expression::Lt(op) => transform_binary!(Lt, *op),
1448        Expression::Gt(op) => transform_binary!(Gt, *op),
1449        Expression::Paren(mut p) => {
1450            p.this = transform_recursive(p.this, transform_fn)?;
1451            Expression::Paren(p)
1452        }
1453        Expression::Coalesce(mut f) => {
1454            f.expressions = f
1455                .expressions
1456                .into_iter()
1457                .map(|e| transform_recursive(e, transform_fn))
1458                .collect::<Result<Vec<_>>>()?;
1459            Expression::Coalesce(f)
1460        }
1461        Expression::IfNull(mut f) => {
1462            f.this = transform_recursive(f.this, transform_fn)?;
1463            f.expression = transform_recursive(f.expression, transform_fn)?;
1464            Expression::IfNull(f)
1465        }
1466        Expression::Nvl(mut f) => {
1467            f.this = transform_recursive(f.this, transform_fn)?;
1468            f.expression = transform_recursive(f.expression, transform_fn)?;
1469            Expression::Nvl(f)
1470        }
1471        Expression::In(mut i) => {
1472            i.this = transform_recursive(i.this, transform_fn)?;
1473            i.expressions = i
1474                .expressions
1475                .into_iter()
1476                .map(|e| transform_recursive(e, transform_fn))
1477                .collect::<Result<Vec<_>>>()?;
1478            if let Some(query) = i.query {
1479                i.query = Some(transform_recursive(query, transform_fn)?);
1480            }
1481            Expression::In(i)
1482        }
1483        Expression::Not(mut n) => {
1484            n.this = transform_recursive(n.this, transform_fn)?;
1485            Expression::Not(n)
1486        }
1487        Expression::ArraySlice(mut s) => {
1488            s.this = transform_recursive(s.this, transform_fn)?;
1489            if let Some(start) = s.start {
1490                s.start = Some(transform_recursive(start, transform_fn)?);
1491            }
1492            if let Some(end) = s.end {
1493                s.end = Some(transform_recursive(end, transform_fn)?);
1494            }
1495            Expression::ArraySlice(s)
1496        }
1497        Expression::Subscript(mut s) => {
1498            s.this = transform_recursive(s.this, transform_fn)?;
1499            s.index = transform_recursive(s.index, transform_fn)?;
1500            Expression::Subscript(s)
1501        }
1502        Expression::Array(mut a) => {
1503            a.expressions = a
1504                .expressions
1505                .into_iter()
1506                .map(|e| transform_recursive(e, transform_fn))
1507                .collect::<Result<Vec<_>>>()?;
1508            Expression::Array(a)
1509        }
1510        Expression::Struct(mut s) => {
1511            let mut new_fields = Vec::new();
1512            for (name, expr) in s.fields {
1513                let transformed = transform_recursive(expr, transform_fn)?;
1514                new_fields.push((name, transformed));
1515            }
1516            s.fields = new_fields;
1517            Expression::Struct(s)
1518        }
1519        Expression::NamedArgument(mut na) => {
1520            na.value = transform_recursive(na.value, transform_fn)?;
1521            Expression::NamedArgument(na)
1522        }
1523        Expression::MapFunc(mut m) => {
1524            m.keys = m
1525                .keys
1526                .into_iter()
1527                .map(|e| transform_recursive(e, transform_fn))
1528                .collect::<Result<Vec<_>>>()?;
1529            m.values = m
1530                .values
1531                .into_iter()
1532                .map(|e| transform_recursive(e, transform_fn))
1533                .collect::<Result<Vec<_>>>()?;
1534            Expression::MapFunc(m)
1535        }
1536        Expression::ArrayFunc(mut a) => {
1537            a.expressions = a
1538                .expressions
1539                .into_iter()
1540                .map(|e| transform_recursive(e, transform_fn))
1541                .collect::<Result<Vec<_>>>()?;
1542            Expression::ArrayFunc(a)
1543        }
1544        Expression::Lambda(mut l) => {
1545            l.body = transform_recursive(l.body, transform_fn)?;
1546            Expression::Lambda(l)
1547        }
1548        Expression::JsonExtract(mut f) => {
1549            f.this = transform_recursive(f.this, transform_fn)?;
1550            f.path = transform_recursive(f.path, transform_fn)?;
1551            Expression::JsonExtract(f)
1552        }
1553        Expression::JsonExtractScalar(mut f) => {
1554            f.this = transform_recursive(f.this, transform_fn)?;
1555            f.path = transform_recursive(f.path, transform_fn)?;
1556            Expression::JsonExtractScalar(f)
1557        }
1558
1559        // ===== UnaryFunc-based expressions =====
1560        // These all have a single `this: Expression` child
1561        Expression::Length(mut f) => {
1562            f.this = transform_recursive(f.this, transform_fn)?;
1563            Expression::Length(f)
1564        }
1565        Expression::Upper(mut f) => {
1566            f.this = transform_recursive(f.this, transform_fn)?;
1567            Expression::Upper(f)
1568        }
1569        Expression::Lower(mut f) => {
1570            f.this = transform_recursive(f.this, transform_fn)?;
1571            Expression::Lower(f)
1572        }
1573        Expression::LTrim(mut f) => {
1574            f.this = transform_recursive(f.this, transform_fn)?;
1575            Expression::LTrim(f)
1576        }
1577        Expression::RTrim(mut f) => {
1578            f.this = transform_recursive(f.this, transform_fn)?;
1579            Expression::RTrim(f)
1580        }
1581        Expression::Reverse(mut f) => {
1582            f.this = transform_recursive(f.this, transform_fn)?;
1583            Expression::Reverse(f)
1584        }
1585        Expression::Abs(mut f) => {
1586            f.this = transform_recursive(f.this, transform_fn)?;
1587            Expression::Abs(f)
1588        }
1589        Expression::Ceil(mut f) => {
1590            f.this = transform_recursive(f.this, transform_fn)?;
1591            Expression::Ceil(f)
1592        }
1593        Expression::Floor(mut f) => {
1594            f.this = transform_recursive(f.this, transform_fn)?;
1595            Expression::Floor(f)
1596        }
1597        Expression::Sign(mut f) => {
1598            f.this = transform_recursive(f.this, transform_fn)?;
1599            Expression::Sign(f)
1600        }
1601        Expression::Sqrt(mut f) => {
1602            f.this = transform_recursive(f.this, transform_fn)?;
1603            Expression::Sqrt(f)
1604        }
1605        Expression::Cbrt(mut f) => {
1606            f.this = transform_recursive(f.this, transform_fn)?;
1607            Expression::Cbrt(f)
1608        }
1609        Expression::Ln(mut f) => {
1610            f.this = transform_recursive(f.this, transform_fn)?;
1611            Expression::Ln(f)
1612        }
1613        Expression::Log(mut f) => {
1614            f.this = transform_recursive(f.this, transform_fn)?;
1615            if let Some(base) = f.base {
1616                f.base = Some(transform_recursive(base, transform_fn)?);
1617            }
1618            Expression::Log(f)
1619        }
1620        Expression::Exp(mut f) => {
1621            f.this = transform_recursive(f.this, transform_fn)?;
1622            Expression::Exp(f)
1623        }
1624        Expression::Date(mut f) => {
1625            f.this = transform_recursive(f.this, transform_fn)?;
1626            Expression::Date(f)
1627        }
1628        Expression::Stddev(f) => recurse_agg!(Stddev, f),
1629        Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
1630        Expression::Variance(f) => recurse_agg!(Variance, f),
1631
1632        // ===== BinaryFunc-based expressions =====
1633        Expression::ModFunc(mut f) => {
1634            f.this = transform_recursive(f.this, transform_fn)?;
1635            f.expression = transform_recursive(f.expression, transform_fn)?;
1636            Expression::ModFunc(f)
1637        }
1638        Expression::Power(mut f) => {
1639            f.this = transform_recursive(f.this, transform_fn)?;
1640            f.expression = transform_recursive(f.expression, transform_fn)?;
1641            Expression::Power(f)
1642        }
1643        Expression::MapFromArrays(mut f) => {
1644            f.this = transform_recursive(f.this, transform_fn)?;
1645            f.expression = transform_recursive(f.expression, transform_fn)?;
1646            Expression::MapFromArrays(f)
1647        }
1648        Expression::ElementAt(mut f) => {
1649            f.this = transform_recursive(f.this, transform_fn)?;
1650            f.expression = transform_recursive(f.expression, transform_fn)?;
1651            Expression::ElementAt(f)
1652        }
1653        Expression::MapContainsKey(mut f) => {
1654            f.this = transform_recursive(f.this, transform_fn)?;
1655            f.expression = transform_recursive(f.expression, transform_fn)?;
1656            Expression::MapContainsKey(f)
1657        }
1658        Expression::Left(mut f) => {
1659            f.this = transform_recursive(f.this, transform_fn)?;
1660            f.length = transform_recursive(f.length, transform_fn)?;
1661            Expression::Left(f)
1662        }
1663        Expression::Right(mut f) => {
1664            f.this = transform_recursive(f.this, transform_fn)?;
1665            f.length = transform_recursive(f.length, transform_fn)?;
1666            Expression::Right(f)
1667        }
1668        Expression::Repeat(mut f) => {
1669            f.this = transform_recursive(f.this, transform_fn)?;
1670            f.times = transform_recursive(f.times, transform_fn)?;
1671            Expression::Repeat(f)
1672        }
1673
1674        // ===== Complex function expressions =====
1675        Expression::Substring(mut f) => {
1676            f.this = transform_recursive(f.this, transform_fn)?;
1677            f.start = transform_recursive(f.start, transform_fn)?;
1678            if let Some(len) = f.length {
1679                f.length = Some(transform_recursive(len, transform_fn)?);
1680            }
1681            Expression::Substring(f)
1682        }
1683        Expression::Replace(mut f) => {
1684            f.this = transform_recursive(f.this, transform_fn)?;
1685            f.old = transform_recursive(f.old, transform_fn)?;
1686            f.new = transform_recursive(f.new, transform_fn)?;
1687            Expression::Replace(f)
1688        }
1689        Expression::ConcatWs(mut f) => {
1690            f.separator = transform_recursive(f.separator, transform_fn)?;
1691            f.expressions = f
1692                .expressions
1693                .into_iter()
1694                .map(|e| transform_recursive(e, transform_fn))
1695                .collect::<Result<Vec<_>>>()?;
1696            Expression::ConcatWs(f)
1697        }
1698        Expression::Trim(mut f) => {
1699            f.this = transform_recursive(f.this, transform_fn)?;
1700            if let Some(chars) = f.characters {
1701                f.characters = Some(transform_recursive(chars, transform_fn)?);
1702            }
1703            Expression::Trim(f)
1704        }
1705        Expression::Split(mut f) => {
1706            f.this = transform_recursive(f.this, transform_fn)?;
1707            f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
1708            Expression::Split(f)
1709        }
1710        Expression::Lpad(mut f) => {
1711            f.this = transform_recursive(f.this, transform_fn)?;
1712            f.length = transform_recursive(f.length, transform_fn)?;
1713            if let Some(fill) = f.fill {
1714                f.fill = Some(transform_recursive(fill, transform_fn)?);
1715            }
1716            Expression::Lpad(f)
1717        }
1718        Expression::Rpad(mut f) => {
1719            f.this = transform_recursive(f.this, transform_fn)?;
1720            f.length = transform_recursive(f.length, transform_fn)?;
1721            if let Some(fill) = f.fill {
1722                f.fill = Some(transform_recursive(fill, transform_fn)?);
1723            }
1724            Expression::Rpad(f)
1725        }
1726
1727        // ===== Conditional expressions =====
1728        Expression::Case(mut c) => {
1729            if let Some(operand) = c.operand {
1730                c.operand = Some(transform_recursive(operand, transform_fn)?);
1731            }
1732            c.whens = c
1733                .whens
1734                .into_iter()
1735                .map(|(cond, then)| {
1736                    let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
1737                    let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
1738                    (new_cond, new_then)
1739                })
1740                .collect();
1741            if let Some(else_expr) = c.else_ {
1742                c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
1743            }
1744            Expression::Case(c)
1745        }
1746        Expression::IfFunc(mut f) => {
1747            f.condition = transform_recursive(f.condition, transform_fn)?;
1748            f.true_value = transform_recursive(f.true_value, transform_fn)?;
1749            if let Some(false_val) = f.false_value {
1750                f.false_value = Some(transform_recursive(false_val, transform_fn)?);
1751            }
1752            Expression::IfFunc(f)
1753        }
1754
1755        // ===== Date/Time expressions =====
1756        Expression::DateAdd(mut f) => {
1757            f.this = transform_recursive(f.this, transform_fn)?;
1758            f.interval = transform_recursive(f.interval, transform_fn)?;
1759            Expression::DateAdd(f)
1760        }
1761        Expression::DateSub(mut f) => {
1762            f.this = transform_recursive(f.this, transform_fn)?;
1763            f.interval = transform_recursive(f.interval, transform_fn)?;
1764            Expression::DateSub(f)
1765        }
1766        Expression::DateDiff(mut f) => {
1767            f.this = transform_recursive(f.this, transform_fn)?;
1768            f.expression = transform_recursive(f.expression, transform_fn)?;
1769            Expression::DateDiff(f)
1770        }
1771        Expression::DateTrunc(mut f) => {
1772            f.this = transform_recursive(f.this, transform_fn)?;
1773            Expression::DateTrunc(f)
1774        }
1775        Expression::Extract(mut f) => {
1776            f.this = transform_recursive(f.this, transform_fn)?;
1777            Expression::Extract(f)
1778        }
1779
1780        // ===== JSON expressions =====
1781        Expression::JsonObject(mut f) => {
1782            f.pairs = f
1783                .pairs
1784                .into_iter()
1785                .map(|(k, v)| {
1786                    let new_k = transform_recursive(k, transform_fn)?;
1787                    let new_v = transform_recursive(v, transform_fn)?;
1788                    Ok((new_k, new_v))
1789                })
1790                .collect::<Result<Vec<_>>>()?;
1791            Expression::JsonObject(f)
1792        }
1793
1794        // ===== Subquery expressions =====
1795        Expression::Subquery(mut s) => {
1796            s.this = transform_recursive(s.this, transform_fn)?;
1797            if let Some(order_by) = s.order_by.take() {
1798                s.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
1799            }
1800            Expression::Subquery(s)
1801        }
1802        Expression::Exists(mut e) => {
1803            e.this = transform_recursive(e.this, transform_fn)?;
1804            Expression::Exists(e)
1805        }
1806        Expression::Describe(mut d) => {
1807            d.target = transform_recursive(d.target, transform_fn)?;
1808            Expression::Describe(d)
1809        }
1810
1811        // ===== Set operations =====
1812        Expression::Union(mut u) => {
1813            let left = std::mem::replace(&mut u.left, Expression::Null(Null));
1814            u.left = transform_recursive(left, transform_fn)?;
1815            let right = std::mem::replace(&mut u.right, Expression::Null(Null));
1816            u.right = transform_recursive(right, transform_fn)?;
1817            if let Some(mut order) = u.order_by.take() {
1818                order.expressions = order
1819                    .expressions
1820                    .into_iter()
1821                    .map(|o| {
1822                        let mut o = o;
1823                        let original = o.this.clone();
1824                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1825                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1826                            Ok(Expression::Ordered(transformed)) => *transformed,
1827                            Ok(_) | Err(_) => o,
1828                        }
1829                    })
1830                    .collect();
1831                u.order_by = Some(order);
1832            }
1833            if let Some(mut with) = u.with.take() {
1834                with.ctes = with
1835                    .ctes
1836                    .into_iter()
1837                    .map(|mut cte| {
1838                        let original = cte.this.clone();
1839                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1840                        cte
1841                    })
1842                    .collect();
1843                u.with = Some(with);
1844            }
1845            Expression::Union(u)
1846        }
1847        Expression::Intersect(mut i) => {
1848            let left = std::mem::replace(&mut i.left, Expression::Null(Null));
1849            i.left = transform_recursive(left, transform_fn)?;
1850            let right = std::mem::replace(&mut i.right, Expression::Null(Null));
1851            i.right = transform_recursive(right, transform_fn)?;
1852            if let Some(mut order) = i.order_by.take() {
1853                order.expressions = order
1854                    .expressions
1855                    .into_iter()
1856                    .map(|o| {
1857                        let mut o = o;
1858                        let original = o.this.clone();
1859                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1860                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1861                            Ok(Expression::Ordered(transformed)) => *transformed,
1862                            Ok(_) | Err(_) => o,
1863                        }
1864                    })
1865                    .collect();
1866                i.order_by = Some(order);
1867            }
1868            if let Some(mut with) = i.with.take() {
1869                with.ctes = with
1870                    .ctes
1871                    .into_iter()
1872                    .map(|mut cte| {
1873                        let original = cte.this.clone();
1874                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1875                        cte
1876                    })
1877                    .collect();
1878                i.with = Some(with);
1879            }
1880            Expression::Intersect(i)
1881        }
1882        Expression::Except(mut e) => {
1883            let left = std::mem::replace(&mut e.left, Expression::Null(Null));
1884            e.left = transform_recursive(left, transform_fn)?;
1885            let right = std::mem::replace(&mut e.right, Expression::Null(Null));
1886            e.right = transform_recursive(right, transform_fn)?;
1887            if let Some(mut order) = e.order_by.take() {
1888                order.expressions = order
1889                    .expressions
1890                    .into_iter()
1891                    .map(|o| {
1892                        let mut o = o;
1893                        let original = o.this.clone();
1894                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1895                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1896                            Ok(Expression::Ordered(transformed)) => *transformed,
1897                            Ok(_) | Err(_) => o,
1898                        }
1899                    })
1900                    .collect();
1901                e.order_by = Some(order);
1902            }
1903            if let Some(mut with) = e.with.take() {
1904                with.ctes = with
1905                    .ctes
1906                    .into_iter()
1907                    .map(|mut cte| {
1908                        let original = cte.this.clone();
1909                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1910                        cte
1911                    })
1912                    .collect();
1913                e.with = Some(with);
1914            }
1915            Expression::Except(e)
1916        }
1917
1918        // ===== DML expressions =====
1919        Expression::Insert(mut ins) => {
1920            // Transform VALUES clause expressions
1921            let mut new_values = Vec::new();
1922            for row in ins.values {
1923                let mut new_row = Vec::new();
1924                for e in row {
1925                    new_row.push(transform_recursive(e, transform_fn)?);
1926                }
1927                new_values.push(new_row);
1928            }
1929            ins.values = new_values;
1930
1931            // Transform query (for INSERT ... SELECT)
1932            if let Some(query) = ins.query {
1933                ins.query = Some(transform_recursive(query, transform_fn)?);
1934            }
1935
1936            // Transform RETURNING clause
1937            let mut new_returning = Vec::new();
1938            for e in ins.returning {
1939                new_returning.push(transform_recursive(e, transform_fn)?);
1940            }
1941            ins.returning = new_returning;
1942
1943            // Transform ON CONFLICT clause
1944            if let Some(on_conflict) = ins.on_conflict {
1945                ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
1946            }
1947
1948            Expression::Insert(ins)
1949        }
1950        Expression::Update(mut upd) => {
1951            upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
1952            upd.extra_tables = upd
1953                .extra_tables
1954                .into_iter()
1955                .map(|table| transform_table_ref_recursive(table, transform_fn))
1956                .collect::<Result<Vec<_>>>()?;
1957            upd.table_joins = upd
1958                .table_joins
1959                .into_iter()
1960                .map(|join| transform_join_recursive(join, transform_fn))
1961                .collect::<Result<Vec<_>>>()?;
1962            upd.set = upd
1963                .set
1964                .into_iter()
1965                .map(|(id, val)| {
1966                    let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
1967                    (id, new_val)
1968                })
1969                .collect();
1970            if let Some(from_clause) = upd.from_clause.take() {
1971                upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
1972            }
1973            upd.from_joins = upd
1974                .from_joins
1975                .into_iter()
1976                .map(|join| transform_join_recursive(join, transform_fn))
1977                .collect::<Result<Vec<_>>>()?;
1978            if let Some(mut where_clause) = upd.where_clause.take() {
1979                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1980                upd.where_clause = Some(where_clause);
1981            }
1982            upd.returning = upd
1983                .returning
1984                .into_iter()
1985                .map(|expr| transform_recursive(expr, transform_fn))
1986                .collect::<Result<Vec<_>>>()?;
1987            if let Some(output) = upd.output.take() {
1988                upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
1989            }
1990            if let Some(with) = upd.with.take() {
1991                upd.with = Some(transform_with_recursive(with, transform_fn)?);
1992            }
1993            if let Some(limit) = upd.limit.take() {
1994                upd.limit = Some(transform_recursive(limit, transform_fn)?);
1995            }
1996            if let Some(order_by) = upd.order_by.take() {
1997                upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
1998            }
1999            Expression::Update(upd)
2000        }
2001        Expression::Delete(mut del) => {
2002            del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2003            del.using = del
2004                .using
2005                .into_iter()
2006                .map(|table| transform_table_ref_recursive(table, transform_fn))
2007                .collect::<Result<Vec<_>>>()?;
2008            if let Some(mut where_clause) = del.where_clause.take() {
2009                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2010                del.where_clause = Some(where_clause);
2011            }
2012            if let Some(output) = del.output.take() {
2013                del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2014            }
2015            if let Some(with) = del.with.take() {
2016                del.with = Some(transform_with_recursive(with, transform_fn)?);
2017            }
2018            if let Some(limit) = del.limit.take() {
2019                del.limit = Some(transform_recursive(limit, transform_fn)?);
2020            }
2021            if let Some(order_by) = del.order_by.take() {
2022                del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2023            }
2024            del.returning = del
2025                .returning
2026                .into_iter()
2027                .map(|expr| transform_recursive(expr, transform_fn))
2028                .collect::<Result<Vec<_>>>()?;
2029            del.tables = del
2030                .tables
2031                .into_iter()
2032                .map(|table| transform_table_ref_recursive(table, transform_fn))
2033                .collect::<Result<Vec<_>>>()?;
2034            del.joins = del
2035                .joins
2036                .into_iter()
2037                .map(|join| transform_join_recursive(join, transform_fn))
2038                .collect::<Result<Vec<_>>>()?;
2039            Expression::Delete(del)
2040        }
2041
2042        // ===== CTE expressions =====
2043        Expression::With(mut w) => {
2044            w.ctes = w
2045                .ctes
2046                .into_iter()
2047                .map(|mut cte| {
2048                    let original = cte.this.clone();
2049                    cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2050                    cte
2051                })
2052                .collect();
2053            Expression::With(w)
2054        }
2055        Expression::Cte(mut c) => {
2056            c.this = transform_recursive(c.this, transform_fn)?;
2057            Expression::Cte(c)
2058        }
2059
2060        // ===== Order expressions =====
2061        Expression::Ordered(mut o) => {
2062            o.this = transform_recursive(o.this, transform_fn)?;
2063            Expression::Ordered(o)
2064        }
2065
2066        // ===== Negation =====
2067        Expression::Neg(mut n) => {
2068            n.this = transform_recursive(n.this, transform_fn)?;
2069            Expression::Neg(n)
2070        }
2071
2072        // ===== Between =====
2073        Expression::Between(mut b) => {
2074            b.this = transform_recursive(b.this, transform_fn)?;
2075            b.low = transform_recursive(b.low, transform_fn)?;
2076            b.high = transform_recursive(b.high, transform_fn)?;
2077            Expression::Between(b)
2078        }
2079        Expression::IsNull(mut i) => {
2080            i.this = transform_recursive(i.this, transform_fn)?;
2081            Expression::IsNull(i)
2082        }
2083        Expression::IsTrue(mut i) => {
2084            i.this = transform_recursive(i.this, transform_fn)?;
2085            Expression::IsTrue(i)
2086        }
2087        Expression::IsFalse(mut i) => {
2088            i.this = transform_recursive(i.this, transform_fn)?;
2089            Expression::IsFalse(i)
2090        }
2091
2092        // ===== Like expressions =====
2093        Expression::Like(mut l) => {
2094            l.left = transform_recursive(l.left, transform_fn)?;
2095            l.right = transform_recursive(l.right, transform_fn)?;
2096            Expression::Like(l)
2097        }
2098        Expression::ILike(mut l) => {
2099            l.left = transform_recursive(l.left, transform_fn)?;
2100            l.right = transform_recursive(l.right, transform_fn)?;
2101            Expression::ILike(l)
2102        }
2103
2104        // ===== Additional binary ops not covered by macro =====
2105        Expression::Neq(op) => transform_binary!(Neq, *op),
2106        Expression::Lte(op) => transform_binary!(Lte, *op),
2107        Expression::Gte(op) => transform_binary!(Gte, *op),
2108        Expression::Mod(op) => transform_binary!(Mod, *op),
2109        Expression::Concat(op) => transform_binary!(Concat, *op),
2110        Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2111        Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2112        Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2113        Expression::Is(op) => transform_binary!(Is, *op),
2114
2115        // ===== TryCast / SafeCast =====
2116        Expression::TryCast(mut c) => {
2117            c.this = transform_recursive(c.this, transform_fn)?;
2118            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2119            Expression::TryCast(c)
2120        }
2121        Expression::SafeCast(mut c) => {
2122            c.this = transform_recursive(c.this, transform_fn)?;
2123            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2124            Expression::SafeCast(c)
2125        }
2126
2127        // ===== Misc =====
2128        Expression::Unnest(mut f) => {
2129            f.this = transform_recursive(f.this, transform_fn)?;
2130            f.expressions = f
2131                .expressions
2132                .into_iter()
2133                .map(|e| transform_recursive(e, transform_fn))
2134                .collect::<Result<Vec<_>>>()?;
2135            Expression::Unnest(f)
2136        }
2137        Expression::Explode(mut f) => {
2138            f.this = transform_recursive(f.this, transform_fn)?;
2139            Expression::Explode(f)
2140        }
2141        Expression::GroupConcat(mut f) => {
2142            f.this = transform_recursive(f.this, transform_fn)?;
2143            Expression::GroupConcat(f)
2144        }
2145        Expression::StringAgg(mut f) => {
2146            f.this = transform_recursive(f.this, transform_fn)?;
2147            if let Some(order_by) = f.order_by.take() {
2148                f.order_by = Some(
2149                    order_by
2150                        .into_iter()
2151                        .map(|mut ordered| {
2152                            let original = ordered.this.clone();
2153                            ordered.this =
2154                                transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2155                            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2156                                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2157                                Ok(_) | Err(_) => Ok(ordered),
2158                            }
2159                        })
2160                        .collect::<Result<Vec<_>>>()?,
2161                );
2162            }
2163            Expression::StringAgg(f)
2164        }
2165        Expression::ListAgg(mut f) => {
2166            f.this = transform_recursive(f.this, transform_fn)?;
2167            Expression::ListAgg(f)
2168        }
2169        Expression::ArrayAgg(mut f) => {
2170            f.this = transform_recursive(f.this, transform_fn)?;
2171            Expression::ArrayAgg(f)
2172        }
2173        Expression::ParseJson(mut f) => {
2174            f.this = transform_recursive(f.this, transform_fn)?;
2175            Expression::ParseJson(f)
2176        }
2177        Expression::ToJson(mut f) => {
2178            f.this = transform_recursive(f.this, transform_fn)?;
2179            Expression::ToJson(f)
2180        }
2181        Expression::JSONExtract(mut e) => {
2182            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2183            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2184            Expression::JSONExtract(e)
2185        }
2186        Expression::JSONExtractScalar(mut e) => {
2187            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2188            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2189            Expression::JSONExtractScalar(e)
2190        }
2191
2192        // StrToTime: recurse into this
2193        Expression::StrToTime(mut e) => {
2194            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2195            Expression::StrToTime(e)
2196        }
2197
2198        // UnixToTime: recurse into this
2199        Expression::UnixToTime(mut e) => {
2200            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2201            Expression::UnixToTime(e)
2202        }
2203
2204        Expression::Upsert(mut upsert) => {
2205            upsert.table = transform_table_ref_recursive(upsert.table, transform_fn)?;
2206            upsert.source = transform_recursive(upsert.source, transform_fn)?;
2207            if let Some(partition) = upsert.partition.take() {
2208                upsert.partition = Some(transform_recursive(partition, transform_fn)?);
2209            }
2210            if let Some(condition) = upsert.condition.take() {
2211                upsert.condition = Some(transform_recursive(condition, transform_fn)?);
2212            }
2213            Expression::Upsert(upsert)
2214        }
2215
2216        // CreateTable: recurse into column defaults, on_update expressions, and data types
2217        Expression::CreateTable(mut ct) => {
2218            for col in &mut ct.columns {
2219                if let Some(default_expr) = col.default.take() {
2220                    col.default = Some(transform_recursive(default_expr, transform_fn)?);
2221                }
2222                if let Some(on_update_expr) = col.on_update.take() {
2223                    col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
2224                }
2225                // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
2226                // are NOT applied here because per-dialect transforms are designed for CAST/expression
2227                // contexts and may not produce correct results for DDL column definitions.
2228                // The DDL type mappings would need dedicated handling per source/target pair.
2229            }
2230            if let Some(as_select) = ct.as_select.take() {
2231                ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
2232            }
2233            Expression::CreateTable(ct)
2234        }
2235
2236        // CreateView: recurse into the view body query
2237        Expression::CreateView(mut cv) => {
2238            cv.query = transform_recursive(cv.query, transform_fn)?;
2239            Expression::CreateView(cv)
2240        }
2241
2242        // CreateTask: recurse into the task body
2243        Expression::CreateTask(mut ct) => {
2244            ct.body = transform_recursive(ct.body, transform_fn)?;
2245            Expression::CreateTask(ct)
2246        }
2247
2248        // Prepare: recurse into the prepared statement body
2249        Expression::Prepare(mut prepare) => {
2250            prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
2251            Expression::Prepare(prepare)
2252        }
2253
2254        // Execute: recurse into procedure/prepared name and argument values
2255        Expression::Execute(mut execute) => {
2256            execute.this = transform_recursive(execute.this, transform_fn)?;
2257            execute.arguments = execute
2258                .arguments
2259                .into_iter()
2260                .map(|argument| transform_recursive(argument, transform_fn))
2261                .collect::<Result<Vec<_>>>()?;
2262            execute.parameters = execute
2263                .parameters
2264                .into_iter()
2265                .map(|mut parameter| {
2266                    parameter.value = transform_recursive(parameter.value, transform_fn)?;
2267                    Ok(parameter)
2268                })
2269                .collect::<Result<Vec<_>>>()?;
2270            Expression::Execute(execute)
2271        }
2272
2273        // CreateProcedure: recurse into body expressions
2274        Expression::CreateProcedure(mut cp) => {
2275            if let Some(body) = cp.body.take() {
2276                cp.body = Some(match body {
2277                    FunctionBody::Expression(expr) => {
2278                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2279                    }
2280                    FunctionBody::Return(expr) => {
2281                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2282                    }
2283                    FunctionBody::Statements(stmts) => {
2284                        let transformed_stmts = stmts
2285                            .into_iter()
2286                            .map(|s| transform_recursive(s, transform_fn))
2287                            .collect::<Result<Vec<_>>>()?;
2288                        FunctionBody::Statements(transformed_stmts)
2289                    }
2290                    other => other,
2291                });
2292            }
2293            Expression::CreateProcedure(cp)
2294        }
2295
2296        // CreateFunction: recurse into body expressions
2297        Expression::CreateFunction(mut cf) => {
2298            if let Some(body) = cf.body.take() {
2299                cf.body = Some(match body {
2300                    FunctionBody::Expression(expr) => {
2301                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2302                    }
2303                    FunctionBody::Return(expr) => {
2304                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2305                    }
2306                    FunctionBody::Statements(stmts) => {
2307                        let transformed_stmts = stmts
2308                            .into_iter()
2309                            .map(|s| transform_recursive(s, transform_fn))
2310                            .collect::<Result<Vec<_>>>()?;
2311                        FunctionBody::Statements(transformed_stmts)
2312                    }
2313                    other => other,
2314                });
2315            }
2316            Expression::CreateFunction(cf)
2317        }
2318
2319        // MemberOf: recurse into left and right operands
2320        Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
2321        // ArrayContainsAll (@>): recurse into left and right operands
2322        Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
2323        // ArrayContainedBy (<@): recurse into left and right operands
2324        Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
2325        // ArrayOverlaps (&&): recurse into left and right operands
2326        Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
2327        // TsMatch (@@): recurse into left and right operands
2328        Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
2329        // Adjacent (-|-): recurse into left and right operands
2330        Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
2331
2332        // Table: recurse into when (HistoricalData) and changes fields
2333        Expression::Table(mut t) => {
2334            if let Some(when) = t.when.take() {
2335                let transformed =
2336                    transform_recursive(Expression::HistoricalData(when), transform_fn)?;
2337                if let Expression::HistoricalData(hd) = transformed {
2338                    t.when = Some(hd);
2339                }
2340            }
2341            if let Some(changes) = t.changes.take() {
2342                let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
2343                if let Expression::Changes(c) = transformed {
2344                    t.changes = Some(c);
2345                }
2346            }
2347            Expression::Table(t)
2348        }
2349
2350        // HistoricalData (Snowflake time travel): recurse into expression
2351        Expression::HistoricalData(mut hd) => {
2352            *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
2353            Expression::HistoricalData(hd)
2354        }
2355
2356        // Changes (Snowflake CHANGES clause): recurse into at_before and end
2357        Expression::Changes(mut c) => {
2358            if let Some(at_before) = c.at_before.take() {
2359                c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
2360            }
2361            if let Some(end) = c.end.take() {
2362                c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
2363            }
2364            Expression::Changes(c)
2365        }
2366
2367        // TableArgument: TABLE(expr) or MODEL(expr)
2368        Expression::TableArgument(mut ta) => {
2369            ta.this = transform_recursive(ta.this, transform_fn)?;
2370            Expression::TableArgument(ta)
2371        }
2372
2373        // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
2374        Expression::JoinedTable(mut jt) => {
2375            jt.left = transform_recursive(jt.left, transform_fn)?;
2376            jt.joins = jt
2377                .joins
2378                .into_iter()
2379                .map(|mut join| {
2380                    join.this = transform_recursive(join.this, transform_fn)?;
2381                    if let Some(on) = join.on.take() {
2382                        join.on = Some(transform_recursive(on, transform_fn)?);
2383                    }
2384                    match transform_fn(Expression::Join(Box::new(join)))? {
2385                        Expression::Join(j) => Ok(*j),
2386                        _ => Err(crate::error::Error::parse(
2387                            "Join transformation returned non-join expression",
2388                            0,
2389                            0,
2390                            0,
2391                            0,
2392                        )),
2393                    }
2394                })
2395                .collect::<Result<Vec<_>>>()?;
2396            jt.lateral_views = jt
2397                .lateral_views
2398                .into_iter()
2399                .map(|mut lv| {
2400                    lv.this = transform_recursive(lv.this, transform_fn)?;
2401                    Ok(lv)
2402                })
2403                .collect::<Result<Vec<_>>>()?;
2404            Expression::JoinedTable(jt)
2405        }
2406
2407        // Lateral: LATERAL func() - recurse into the function expression
2408        Expression::Lateral(mut lat) => {
2409            *lat.this = transform_recursive(*lat.this, transform_fn)?;
2410            Expression::Lateral(lat)
2411        }
2412
2413        // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
2414        // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
2415        // as a unit together with the WithinGroup wrapper
2416        Expression::WithinGroup(mut wg) => {
2417            wg.order_by = wg
2418                .order_by
2419                .into_iter()
2420                .map(|mut o| {
2421                    let original = o.this.clone();
2422                    o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2423                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2424                        Ok(Expression::Ordered(transformed)) => *transformed,
2425                        Ok(_) | Err(_) => o,
2426                    }
2427                })
2428                .collect();
2429            Expression::WithinGroup(wg)
2430        }
2431
2432        // Filter: recurse into both the aggregate and the filter condition
2433        Expression::Filter(mut f) => {
2434            f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
2435            f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
2436            Expression::Filter(f)
2437        }
2438
2439        // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
2440        // filter, order_by, having_max, and limit.
2441        // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
2442        Expression::Sum(f) => recurse_agg!(Sum, f),
2443        Expression::Avg(f) => recurse_agg!(Avg, f),
2444        Expression::Min(f) => recurse_agg!(Min, f),
2445        Expression::Max(f) => recurse_agg!(Max, f),
2446        Expression::CountIf(f) => recurse_agg!(CountIf, f),
2447        Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
2448        Expression::VarPop(f) => recurse_agg!(VarPop, f),
2449        Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
2450        Expression::Median(f) => recurse_agg!(Median, f),
2451        Expression::Mode(f) => recurse_agg!(Mode, f),
2452        Expression::First(f) => recurse_agg!(First, f),
2453        Expression::Last(f) => recurse_agg!(Last, f),
2454        Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
2455        Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
2456        Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
2457        Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
2458        Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
2459        Expression::Skewness(f) => recurse_agg!(Skewness, f),
2460        Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
2461        Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
2462        Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
2463        Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
2464        Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
2465        Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
2466
2467        // Count has its own struct with an Option<Expression> `this` field
2468        Expression::Count(mut c) => {
2469            if let Some(this) = c.this.take() {
2470                c.this = Some(transform_recursive(this, transform_fn)?);
2471            }
2472            if let Some(filter) = c.filter.take() {
2473                c.filter = Some(transform_recursive(filter, transform_fn)?);
2474            }
2475            Expression::Count(c)
2476        }
2477
2478        Expression::PipeOperator(mut pipe) => {
2479            pipe.this = transform_recursive(pipe.this, transform_fn)?;
2480            pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
2481            Expression::PipeOperator(pipe)
2482        }
2483
2484        // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
2485        Expression::ArrayExcept(mut f) => {
2486            f.this = transform_recursive(f.this, transform_fn)?;
2487            f.expression = transform_recursive(f.expression, transform_fn)?;
2488            Expression::ArrayExcept(f)
2489        }
2490        Expression::ArrayContains(mut f) => {
2491            f.this = transform_recursive(f.this, transform_fn)?;
2492            f.expression = transform_recursive(f.expression, transform_fn)?;
2493            Expression::ArrayContains(f)
2494        }
2495        Expression::ArrayDistinct(mut f) => {
2496            f.this = transform_recursive(f.this, transform_fn)?;
2497            Expression::ArrayDistinct(f)
2498        }
2499        Expression::ArrayPosition(mut f) => {
2500            f.this = transform_recursive(f.this, transform_fn)?;
2501            f.expression = transform_recursive(f.expression, transform_fn)?;
2502            Expression::ArrayPosition(f)
2503        }
2504
2505        // Pass through leaf nodes unchanged
2506        other => other,
2507    };
2508
2509    // Then apply the transform function
2510    transform_fn(expr)
2511}
2512
2513/// Returns the tokenizer config, generator config, and expression transform closure
2514/// for a built-in dialect type. This is the shared implementation used by both
2515/// `Dialect::get()` and custom dialect construction.
2516// ---------------------------------------------------------------------------
2517// Cached dialect configurations
2518// ---------------------------------------------------------------------------
2519
2520/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
2521/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
2522struct CachedDialectConfig {
2523    tokenizer_config: Arc<TokenizerConfig>,
2524    #[cfg(feature = "generate")]
2525    generator_config: Arc<GeneratorConfig>,
2526}
2527
2528struct DialectConfigs {
2529    tokenizer_config: Arc<TokenizerConfig>,
2530    #[cfg(feature = "generate")]
2531    generator_config: Arc<GeneratorConfig>,
2532    #[cfg(feature = "transpile")]
2533    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2534}
2535
2536/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
2537macro_rules! cached_dialect {
2538    ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
2539        #[cfg(feature = $feature)]
2540        static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2541            let d = $dialect_struct;
2542            CachedDialectConfig {
2543                tokenizer_config: Arc::new(d.tokenizer_config()),
2544                #[cfg(feature = "generate")]
2545                generator_config: Arc::new(d.generator_config()),
2546            }
2547        });
2548    };
2549}
2550
2551static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2552    let d = GenericDialect;
2553    CachedDialectConfig {
2554        tokenizer_config: Arc::new(d.tokenizer_config()),
2555        #[cfg(feature = "generate")]
2556        generator_config: Arc::new(d.generator_config()),
2557    }
2558});
2559
2560cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
2561cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
2562cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
2563cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
2564cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
2565cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
2566cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
2567cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
2568cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
2569cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
2570cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
2571cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
2572cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
2573cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
2574cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
2575cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
2576cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
2577cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
2578cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
2579cached_dialect!(
2580    CACHED_MATERIALIZE,
2581    MaterializeDialect,
2582    "dialect-materialize"
2583);
2584cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
2585cached_dialect!(
2586    CACHED_SINGLESTORE,
2587    SingleStoreDialect,
2588    "dialect-singlestore"
2589);
2590cached_dialect!(
2591    CACHED_COCKROACHDB,
2592    CockroachDBDialect,
2593    "dialect-cockroachdb"
2594);
2595cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
2596cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
2597cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
2598cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
2599cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
2600cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
2601cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
2602cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
2603cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
2604cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
2605cached_dialect!(CACHED_HANA, HanaDialect, "dialect-hana");
2606cached_dialect!(CACHED_VERTICA, VerticaDialect, "dialect-vertica");
2607
2608fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
2609    /// Clone configs from a cached static and pair with a fresh transform closure.
2610    macro_rules! from_cache {
2611        ($cache:expr, $dialect_struct:expr) => {{
2612            let c = &*$cache;
2613            DialectConfigs {
2614                tokenizer_config: c.tokenizer_config.clone(),
2615                #[cfg(feature = "generate")]
2616                generator_config: c.generator_config.clone(),
2617                #[cfg(feature = "transpile")]
2618                transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
2619            }
2620        }};
2621    }
2622    match dt {
2623        #[cfg(feature = "dialect-postgresql")]
2624        DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
2625        #[cfg(feature = "dialect-mysql")]
2626        DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
2627        #[cfg(feature = "dialect-bigquery")]
2628        DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
2629        #[cfg(feature = "dialect-snowflake")]
2630        DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
2631        #[cfg(feature = "dialect-duckdb")]
2632        DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
2633        #[cfg(feature = "dialect-tsql")]
2634        DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
2635        #[cfg(feature = "dialect-oracle")]
2636        DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
2637        #[cfg(feature = "dialect-hive")]
2638        DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
2639        #[cfg(feature = "dialect-spark")]
2640        DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
2641        #[cfg(feature = "dialect-sqlite")]
2642        DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
2643        #[cfg(feature = "dialect-presto")]
2644        DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
2645        #[cfg(feature = "dialect-trino")]
2646        DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
2647        #[cfg(feature = "dialect-redshift")]
2648        DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
2649        #[cfg(feature = "dialect-clickhouse")]
2650        DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
2651        #[cfg(feature = "dialect-databricks")]
2652        DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
2653        #[cfg(feature = "dialect-athena")]
2654        DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
2655        #[cfg(feature = "dialect-teradata")]
2656        DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
2657        #[cfg(feature = "dialect-doris")]
2658        DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
2659        #[cfg(feature = "dialect-starrocks")]
2660        DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
2661        #[cfg(feature = "dialect-materialize")]
2662        DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
2663        #[cfg(feature = "dialect-risingwave")]
2664        DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
2665        #[cfg(feature = "dialect-singlestore")]
2666        DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
2667        #[cfg(feature = "dialect-cockroachdb")]
2668        DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
2669        #[cfg(feature = "dialect-tidb")]
2670        DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
2671        #[cfg(feature = "dialect-druid")]
2672        DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
2673        #[cfg(feature = "dialect-solr")]
2674        DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
2675        #[cfg(feature = "dialect-tableau")]
2676        DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
2677        #[cfg(feature = "dialect-dune")]
2678        DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
2679        #[cfg(feature = "dialect-fabric")]
2680        DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
2681        #[cfg(feature = "dialect-drill")]
2682        DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
2683        #[cfg(feature = "dialect-dremio")]
2684        DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
2685        #[cfg(feature = "dialect-exasol")]
2686        DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
2687        #[cfg(feature = "dialect-datafusion")]
2688        DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
2689        #[cfg(feature = "dialect-hana")]
2690        DialectType::HANA => from_cache!(CACHED_HANA, HanaDialect),
2691        #[cfg(feature = "dialect-vertica")]
2692        DialectType::Vertica => from_cache!(CACHED_VERTICA, VerticaDialect),
2693        _ => from_cache!(CACHED_GENERIC, GenericDialect),
2694    }
2695}
2696
2697// ---------------------------------------------------------------------------
2698// Custom dialect registry
2699// ---------------------------------------------------------------------------
2700
2701static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
2702    LazyLock::new(|| RwLock::new(HashMap::new()));
2703
2704struct CustomDialectConfig {
2705    name: String,
2706    base_dialect: DialectType,
2707    tokenizer_config: Arc<TokenizerConfig>,
2708    #[cfg(feature = "generate")]
2709    generator_config: GeneratorConfig,
2710    #[cfg(feature = "transpile")]
2711    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2712    #[cfg(feature = "transpile")]
2713    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2714}
2715
2716/// Fluent builder for creating and registering custom SQL dialects.
2717///
2718/// A custom dialect is based on an existing built-in dialect and allows selective
2719/// overrides of tokenizer configuration, generator configuration, and expression
2720/// transforms.
2721///
2722/// # Example
2723///
2724/// ```rust,ignore
2725/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
2726/// use polyglot_sql::generator::NormalizeFunctions;
2727///
2728/// CustomDialectBuilder::new("my_postgres")
2729///     .based_on(DialectType::PostgreSQL)
2730///     .generator_config_modifier(|gc| {
2731///         gc.normalize_functions = NormalizeFunctions::Lower;
2732///     })
2733///     .register()
2734///     .unwrap();
2735///
2736/// let d = Dialect::get_by_name("my_postgres").unwrap();
2737/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
2738/// let sql = d.generate(&exprs[0]).unwrap();
2739/// assert_eq!(sql, "select count(*)");
2740///
2741/// polyglot_sql::unregister_custom_dialect("my_postgres");
2742/// ```
2743pub struct CustomDialectBuilder {
2744    name: String,
2745    base_dialect: DialectType,
2746    tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
2747    #[cfg(feature = "generate")]
2748    generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
2749    #[cfg(feature = "transpile")]
2750    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2751    #[cfg(feature = "transpile")]
2752    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2753}
2754
2755impl CustomDialectBuilder {
2756    /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
2757    pub fn new(name: impl Into<String>) -> Self {
2758        Self {
2759            name: name.into(),
2760            base_dialect: DialectType::Generic,
2761            tokenizer_modifier: None,
2762            #[cfg(feature = "generate")]
2763            generator_modifier: None,
2764            #[cfg(feature = "transpile")]
2765            transform: None,
2766            #[cfg(feature = "transpile")]
2767            preprocess: None,
2768        }
2769    }
2770
2771    /// Set the base built-in dialect to inherit configuration from.
2772    pub fn based_on(mut self, dialect: DialectType) -> Self {
2773        self.base_dialect = dialect;
2774        self
2775    }
2776
2777    /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
2778    pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
2779    where
2780        F: FnOnce(&mut TokenizerConfig) + 'static,
2781    {
2782        self.tokenizer_modifier = Some(Box::new(f));
2783        self
2784    }
2785
2786    /// Provide a closure that modifies the generator configuration inherited from the base dialect.
2787    #[cfg(feature = "generate")]
2788    pub fn generator_config_modifier<F>(mut self, f: F) -> Self
2789    where
2790        F: FnOnce(&mut GeneratorConfig) + 'static,
2791    {
2792        self.generator_modifier = Some(Box::new(f));
2793        self
2794    }
2795
2796    /// Set a custom per-node expression transform function.
2797    ///
2798    /// This replaces the base dialect's transform. It is called on every expression
2799    /// node during the recursive transform pass.
2800    #[cfg(feature = "transpile")]
2801    pub fn transform_fn<F>(mut self, f: F) -> Self
2802    where
2803        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2804    {
2805        self.transform = Some(Arc::new(f));
2806        self
2807    }
2808
2809    /// Set a custom whole-tree preprocessing function.
2810    ///
2811    /// This replaces the base dialect's built-in preprocessing. It is called once
2812    /// on the entire expression tree before the recursive per-node transform.
2813    #[cfg(feature = "transpile")]
2814    pub fn preprocess_fn<F>(mut self, f: F) -> Self
2815    where
2816        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2817    {
2818        self.preprocess = Some(Arc::new(f));
2819        self
2820    }
2821
2822    /// Build the custom dialect configuration and register it in the global registry.
2823    ///
2824    /// Returns an error if:
2825    /// - The name collides with a built-in dialect name
2826    /// - A custom dialect with the same name is already registered
2827    pub fn register(self) -> Result<()> {
2828        // Reject names that collide with built-in dialects
2829        if DialectType::from_str(&self.name).is_ok() {
2830            return Err(crate::error::Error::parse(
2831                format!(
2832                    "Cannot register custom dialect '{}': name collides with built-in dialect",
2833                    self.name
2834                ),
2835                0,
2836                0,
2837                0,
2838                0,
2839            ));
2840        }
2841
2842        // Get base configs
2843        let base_configs = configs_for_dialect_type(self.base_dialect);
2844        let mut tok_config = (*base_configs.tokenizer_config).clone();
2845        #[cfg(feature = "generate")]
2846        let mut gen_config = (*base_configs.generator_config).clone();
2847
2848        // Apply modifiers
2849        if let Some(tok_mod) = self.tokenizer_modifier {
2850            tok_mod(&mut tok_config);
2851        }
2852        #[cfg(feature = "generate")]
2853        if let Some(gen_mod) = self.generator_modifier {
2854            gen_mod(&mut gen_config);
2855        }
2856
2857        let config = CustomDialectConfig {
2858            name: self.name.clone(),
2859            base_dialect: self.base_dialect,
2860            tokenizer_config: Arc::new(tok_config),
2861            #[cfg(feature = "generate")]
2862            generator_config: gen_config,
2863            #[cfg(feature = "transpile")]
2864            transform: self.transform,
2865            #[cfg(feature = "transpile")]
2866            preprocess: self.preprocess,
2867        };
2868
2869        register_custom_dialect(config)
2870    }
2871}
2872
2873use std::str::FromStr;
2874
2875fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
2876    let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
2877        crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
2878    })?;
2879
2880    if registry.contains_key(&config.name) {
2881        return Err(crate::error::Error::parse(
2882            format!("Custom dialect '{}' is already registered", config.name),
2883            0,
2884            0,
2885            0,
2886            0,
2887        ));
2888    }
2889
2890    registry.insert(config.name.clone(), Arc::new(config));
2891    Ok(())
2892}
2893
2894/// Remove a custom dialect from the global registry.
2895///
2896/// Returns `true` if a dialect with that name was found and removed,
2897/// `false` if no such custom dialect existed.
2898pub fn unregister_custom_dialect(name: &str) -> bool {
2899    if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
2900        registry.remove(name).is_some()
2901    } else {
2902        false
2903    }
2904}
2905
2906fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
2907    CUSTOM_DIALECT_REGISTRY
2908        .read()
2909        .ok()
2910        .and_then(|registry| registry.get(name).cloned())
2911}
2912
2913/// Main entry point for dialect-specific SQL operations.
2914///
2915/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
2916/// transformer for a specific SQL database engine. It is the high-level API through
2917/// which callers parse, generate, transform, and transpile SQL.
2918///
2919/// # Usage
2920///
2921/// ```rust,ignore
2922/// use polyglot_sql::dialects::{Dialect, DialectType};
2923///
2924/// // Parse PostgreSQL SQL into an AST
2925/// let pg = Dialect::get(DialectType::PostgreSQL);
2926/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
2927///
2928/// // Transpile from PostgreSQL to BigQuery
2929/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
2930/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
2931/// ```
2932///
2933/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
2934/// The struct is `Send + Sync` safe so it can be shared across threads.
2935pub struct Dialect {
2936    dialect_type: DialectType,
2937    tokenizer: Tokenizer,
2938    #[cfg(feature = "generate")]
2939    generator_config: Arc<GeneratorConfig>,
2940    #[cfg(feature = "transpile")]
2941    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2942    /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
2943    #[cfg(feature = "generate")]
2944    generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
2945    /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
2946    #[cfg(feature = "transpile")]
2947    custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2948}
2949
2950/// Options for [`Dialect::transpile_with`].
2951///
2952/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
2953/// The struct is marked `#[non_exhaustive]` so new fields can be added without
2954/// breaking the API.
2955///
2956/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
2957/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
2958#[cfg(feature = "transpile")]
2959#[derive(Debug, Clone, Serialize, Deserialize)]
2960#[serde(rename_all = "camelCase", default)]
2961#[non_exhaustive]
2962pub struct TranspileOptions {
2963    /// Whether to pretty-print the output SQL.
2964    pub pretty: bool,
2965    /// How unsupported target-dialect constructs should be handled.
2966    ///
2967    /// The default is [`UnsupportedLevel::Warn`], which preserves the current
2968    /// compatibility behavior and continues transpilation.
2969    pub unsupported_level: UnsupportedLevel,
2970    /// Maximum number of unsupported diagnostics to include in raised errors.
2971    pub max_unsupported: usize,
2972    /// Complexity guard limits used while parsing, transforming, and generating.
2973    pub complexity_guard: ComplexityGuardOptions,
2974}
2975
2976#[cfg(feature = "transpile")]
2977impl Default for TranspileOptions {
2978    fn default() -> Self {
2979        Self {
2980            pretty: false,
2981            unsupported_level: UnsupportedLevel::Warn,
2982            max_unsupported: 3,
2983            complexity_guard: ComplexityGuardOptions::default(),
2984        }
2985    }
2986}
2987
2988#[cfg(feature = "transpile")]
2989impl TranspileOptions {
2990    /// Construct options with pretty-printing enabled.
2991    pub fn pretty() -> Self {
2992        Self {
2993            pretty: true,
2994            ..Default::default()
2995        }
2996    }
2997
2998    /// Construct options that raise when known unsupported constructs remain.
2999    pub fn strict() -> Self {
3000        Self {
3001            unsupported_level: UnsupportedLevel::Raise,
3002            ..Default::default()
3003        }
3004    }
3005
3006    /// Set how unsupported target-dialect constructs should be handled.
3007    pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3008        self.unsupported_level = level;
3009        self
3010    }
3011
3012    /// Set the maximum number of unsupported diagnostics to include in raised errors.
3013    pub fn with_max_unsupported(mut self, max: usize) -> Self {
3014        self.max_unsupported = max;
3015        self
3016    }
3017
3018    /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3019    pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3020        self.complexity_guard = guard;
3021        self
3022    }
3023}
3024
3025/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3026/// [`Dialect::transpile_with`].
3027///
3028/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3029/// dialect handle, including custom ones). End users do not normally need to
3030/// implement this trait themselves.
3031#[cfg(feature = "transpile")]
3032pub trait TranspileTarget {
3033    /// Invoke `f` with a reference to the resolved target dialect.
3034    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3035}
3036
3037#[cfg(feature = "transpile")]
3038impl TranspileTarget for DialectType {
3039    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3040        f(&Dialect::get(self))
3041    }
3042}
3043
3044#[cfg(feature = "transpile")]
3045impl TranspileTarget for &Dialect {
3046    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3047        f(self)
3048    }
3049}
3050
3051impl Dialect {
3052    /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3053    ///
3054    /// This is the primary constructor. It initializes the tokenizer, generator config,
3055    /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3056    /// For hybrid dialects like Athena, it also sets up expression-specific generator
3057    /// config routing.
3058    pub fn get(dialect_type: DialectType) -> Self {
3059        let configs = configs_for_dialect_type(dialect_type);
3060        let tokenizer_config = configs.tokenizer_config;
3061        #[cfg(feature = "generate")]
3062        let generator_config = configs.generator_config;
3063        #[cfg(feature = "transpile")]
3064        let transformer = configs.transformer;
3065
3066        // Set up expression-specific generator config for hybrid dialects
3067        #[cfg(feature = "generate")]
3068        let generator_config_for_expr: Option<
3069            Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3070        > = match dialect_type {
3071            #[cfg(feature = "dialect-athena")]
3072            DialectType::Athena => Some(Box::new(|expr| {
3073                AthenaDialect.generator_config_for_expr(expr)
3074            })),
3075            _ => None,
3076        };
3077
3078        Self {
3079            dialect_type,
3080            tokenizer: Tokenizer::from_shared_config(tokenizer_config),
3081            #[cfg(feature = "generate")]
3082            generator_config,
3083            #[cfg(feature = "transpile")]
3084            transformer,
3085            #[cfg(feature = "generate")]
3086            generator_config_for_expr,
3087            #[cfg(feature = "transpile")]
3088            custom_preprocess: None,
3089        }
3090    }
3091
3092    /// Look up a dialect by string name.
3093    ///
3094    /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3095    /// falls back to the custom dialect registry. Returns `None` if no dialect
3096    /// with the given name exists.
3097    pub fn get_by_name(name: &str) -> Option<Self> {
3098        // Try built-in first
3099        if let Ok(dt) = DialectType::from_str(name) {
3100            return Some(Self::get(dt));
3101        }
3102
3103        // Try custom registry
3104        let config = get_custom_dialect_config(name)?;
3105        Some(Self::from_custom_config(&config))
3106    }
3107
3108    /// Construct a `Dialect` from a custom dialect configuration.
3109    fn from_custom_config(config: &CustomDialectConfig) -> Self {
3110        // Build the transformer: use custom if provided, else use base dialect's
3111        #[cfg(feature = "transpile")]
3112        let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3113            if let Some(ref custom_transform) = config.transform {
3114                let t = Arc::clone(custom_transform);
3115                Box::new(move |e| t(e))
3116            } else {
3117                configs_for_dialect_type(config.base_dialect).transformer
3118            };
3119
3120        // Build the custom preprocess: use custom if provided
3121        #[cfg(feature = "transpile")]
3122        let custom_preprocess: Option<
3123            Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3124        > = config.preprocess.as_ref().map(|p| {
3125            let p = Arc::clone(p);
3126            Box::new(move |e: Expression| p(e))
3127                as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3128        });
3129
3130        Self {
3131            dialect_type: config.base_dialect,
3132            tokenizer: Tokenizer::from_shared_config(config.tokenizer_config.clone()),
3133            #[cfg(feature = "generate")]
3134            generator_config: Arc::new(config.generator_config.clone()),
3135            #[cfg(feature = "transpile")]
3136            transformer,
3137            #[cfg(feature = "generate")]
3138            generator_config_for_expr: None,
3139            #[cfg(feature = "transpile")]
3140            custom_preprocess,
3141        }
3142    }
3143
3144    /// Get the dialect type
3145    pub fn dialect_type(&self) -> DialectType {
3146        self.dialect_type
3147    }
3148
3149    /// Get the generator configuration
3150    #[cfg(feature = "generate")]
3151    pub fn generator_config(&self) -> &GeneratorConfig {
3152        &self.generator_config
3153    }
3154
3155    /// Parses a SQL string into a list of [`Expression`] AST nodes.
3156    ///
3157    /// The input may contain multiple semicolon-separated statements; each one
3158    /// produces a separate element in the returned vector. Tokenization uses
3159    /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3160    pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3161        self.parse_with_guard(sql, self.default_complexity_guard())
3162    }
3163
3164    /// Parse using per-call limits without changing this dialect's defaults.
3165    pub fn parse_with_options(&self, sql: &str, options: &ParseOptions) -> Result<Vec<Expression>> {
3166        self.parse_with_guard(
3167            sql,
3168            options
3169                .complexity_guard
3170                .unwrap_or_else(|| self.default_complexity_guard()),
3171        )
3172    }
3173
3174    fn parse_with_guard(
3175        &self,
3176        sql: &str,
3177        complexity_guard: ComplexityGuardOptions,
3178    ) -> Result<Vec<Expression>> {
3179        enforce_input(sql, &complexity_guard)?;
3180        let source: Arc<str> = Arc::from(sql);
3181        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3182        let config = crate::parser::ParserConfig {
3183            dialect: Some(self.dialect_type),
3184            complexity_guard,
3185            ..Default::default()
3186        };
3187        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3188        parser.parse()
3189    }
3190
3191    fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3192        let mut guard = ComplexityGuardOptions::default();
3193        if matches!(self.dialect_type, DialectType::ClickHouse) {
3194            guard.max_ast_depth = Some(4_096);
3195            guard.max_function_call_depth = Some(512);
3196        }
3197        guard
3198    }
3199
3200    #[cfg(feature = "transpile")]
3201    fn default_transpile_complexity_guard(
3202        &self,
3203        target_dialect: &Dialect,
3204        guard: ComplexityGuardOptions,
3205    ) -> ComplexityGuardOptions {
3206        if guard != ComplexityGuardOptions::default() {
3207            return guard;
3208        }
3209
3210        if matches!(self.dialect_type, DialectType::ClickHouse)
3211            || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3212        {
3213            let mut guard = guard;
3214            guard.max_ast_depth = Some(4_096);
3215            guard.max_function_call_depth = Some(512);
3216            guard
3217        } else {
3218            guard
3219        }
3220    }
3221
3222    /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
3223    ///
3224    /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
3225    /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
3226    pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
3227        self.parse_data_type_with_options(sql, &ParseOptions::default())
3228    }
3229
3230    /// Parse a standalone data type using per-call complexity limits.
3231    pub fn parse_data_type_with_options(
3232        &self,
3233        sql: &str,
3234        options: &ParseOptions,
3235    ) -> Result<DataType> {
3236        let complexity_guard = options
3237            .complexity_guard
3238            .unwrap_or_else(|| self.default_complexity_guard());
3239        enforce_input(sql, &complexity_guard)?;
3240        let source: Arc<str> = Arc::from(sql);
3241        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3242        let config = crate::parser::ParserConfig {
3243            dialect: Some(self.dialect_type),
3244            complexity_guard,
3245            ..Default::default()
3246        };
3247        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3248        parser.parse_standalone_data_type()
3249    }
3250
3251    /// Tokenize SQL using this dialect's tokenizer configuration.
3252    pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
3253        self.tokenizer.tokenize(sql)
3254    }
3255
3256    /// Get the generator config for a specific expression (supports hybrid dialects).
3257    /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
3258    #[cfg(feature = "generate")]
3259    fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
3260        if let Some(ref config_fn) = self.generator_config_for_expr {
3261            config_fn(expr)
3262        } else {
3263            (*self.generator_config).clone()
3264        }
3265    }
3266
3267    /// Generates a SQL string from an [`Expression`] AST node.
3268    ///
3269    /// The output uses this dialect's generator configuration for identifier quoting,
3270    /// keyword casing, function name normalization, and syntax style. The result is
3271    /// a single-line (non-pretty) SQL string.
3272    #[cfg(feature = "generate")]
3273    pub fn generate(&self, expr: &Expression) -> Result<String> {
3274        // Fast path: when no per-expression config override, share the Arc cheaply.
3275        if self.generator_config_for_expr.is_none() {
3276            let mut generator = Generator::with_arc_config(self.generator_config.clone());
3277            return generator.generate(expr);
3278        }
3279        let config = self.get_config_for_expr(expr);
3280        let mut generator = Generator::with_config(config);
3281        generator.generate(expr)
3282    }
3283
3284    /// Generate SQL with pretty printing, retaining explicit null-ordering clauses.
3285    #[cfg(feature = "generate")]
3286    pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
3287        let mut config = self.get_config_for_expr(expr);
3288        config.pretty = true;
3289        let mut generator = Generator::with_config(config).with_preserved_null_ordering();
3290        generator.generate(expr)
3291    }
3292
3293    /// Render analysis facts with the same AST limits used to accept the input.
3294    #[cfg(all(feature = "semantic", feature = "generate"))]
3295    pub(crate) fn generate_with_guard(
3296        &self,
3297        expr: &Expression,
3298        guard: Option<ComplexityGuardOptions>,
3299    ) -> Result<String> {
3300        let Some(guard) = guard else {
3301            return self.generate(expr);
3302        };
3303        let mut config = self.get_config_for_expr(expr);
3304        config.complexity_guard = guard;
3305        Generator::with_config(config).generate(expr)
3306    }
3307
3308    /// Generate SQL from an expression with source dialect info (for transpilation)
3309    #[cfg(feature = "generate")]
3310    pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
3311        let mut config = self.get_config_for_expr(expr);
3312        config.source_dialect = Some(source);
3313        let mut generator = Generator::with_config(config);
3314        generator.generate(expr)
3315    }
3316
3317    /// Generate SQL from an expression with pretty printing and source dialect info
3318    #[cfg(feature = "generate")]
3319    pub fn generate_pretty_with_source(
3320        &self,
3321        expr: &Expression,
3322        source: DialectType,
3323    ) -> Result<String> {
3324        let mut config = self.get_config_for_expr(expr);
3325        config.pretty = true;
3326        config.source_dialect = Some(source);
3327        let mut generator = Generator::with_config(config);
3328        generator.generate(expr)
3329    }
3330
3331    /// Generate SQL from an expression with source dialect and transpile options.
3332    #[cfg(all(feature = "generate", feature = "transpile"))]
3333    fn generate_with_transpile_options(
3334        &self,
3335        expr: &Expression,
3336        source: DialectType,
3337        opts: &TranspileOptions,
3338    ) -> Result<String> {
3339        let mut config = self.get_config_for_expr(expr);
3340        config.source_dialect = Some(source);
3341        config.pretty = opts.pretty;
3342        config.unsupported_level = opts.unsupported_level;
3343        config.max_unsupported = opts.max_unsupported.max(1);
3344        config.complexity_guard = opts.complexity_guard;
3345        let mut generator = Generator::with_config(config);
3346        generator.generate(expr)
3347    }
3348
3349    /// Generate SQL from an expression with forced identifier quoting (identify=True)
3350    #[cfg(feature = "generate")]
3351    pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
3352        let mut config = self.get_config_for_expr(expr);
3353        config.always_quote_identifiers = true;
3354        let mut generator = Generator::with_config(config);
3355        generator.generate(expr)
3356    }
3357
3358    /// Generate SQL from an expression with pretty printing and forced identifier quoting
3359    #[cfg(feature = "generate")]
3360    pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
3361        let mut config = (*self.generator_config).clone();
3362        config.pretty = true;
3363        config.always_quote_identifiers = true;
3364        let mut generator = Generator::with_config(config);
3365        generator.generate(expr)
3366    }
3367
3368    /// Generate SQL from an expression with caller-specified config overrides
3369    #[cfg(feature = "generate")]
3370    pub fn generate_with_overrides(
3371        &self,
3372        expr: &Expression,
3373        overrides: impl FnOnce(&mut GeneratorConfig),
3374    ) -> Result<String> {
3375        let mut config = self.get_config_for_expr(expr);
3376        overrides(&mut config);
3377        let mut generator = Generator::with_config(config);
3378        generator.generate(expr)
3379    }
3380
3381    /// Transforms an expression tree to conform to this dialect's syntax and semantics.
3382    ///
3383    /// The transformation proceeds in two phases:
3384    /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
3385    ///    ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
3386    /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
3387    ///    that applies this dialect's [`DialectImpl::transform_expr`] to every node.
3388    ///
3389    /// This method is used both during transpilation (to rewrite an AST for a target dialect)
3390    /// and for identity transforms (normalizing SQL within the same dialect).
3391    #[cfg(feature = "transpile")]
3392    pub fn transform(&self, expr: Expression) -> Result<Expression> {
3393        self.transform_with_guard(expr, self.default_complexity_guard())
3394    }
3395
3396    #[cfg(feature = "transpile")]
3397    fn transform_with_guard(
3398        &self,
3399        expr: Expression,
3400        complexity_guard: ComplexityGuardOptions,
3401    ) -> Result<Expression> {
3402        enforce_generate_ast(&expr, &complexity_guard)?;
3403        // Apply preprocessing transforms based on dialect
3404        let preprocessed = self.preprocess(expr)?;
3405        // Then apply recursive transformation
3406        transform_recursive(preprocessed, &|node| {
3407            // Calls with retained source semantics are lowered by the generator;
3408            // ordinary AST visitors and optimizer passes still see their shared node kind.
3409            if node.source_dialect().is_some()
3410                || matches!(&node, Expression::Function(f) if !f.qualified_name.is_empty())
3411            {
3412                Ok(node)
3413            } else {
3414                (self.transformer)(node)
3415            }
3416        })
3417    }
3418
3419    /// Apply dialect-specific preprocessing transforms
3420    #[cfg(feature = "transpile")]
3421    fn preprocess(&self, expr: Expression) -> Result<Expression> {
3422        // If a custom preprocess function is set, use it instead of the built-in logic
3423        if let Some(ref custom_preprocess) = self.custom_preprocess {
3424            return custom_preprocess(expr);
3425        }
3426
3427        #[cfg(any(
3428            feature = "dialect-mysql",
3429            feature = "dialect-postgresql",
3430            feature = "dialect-bigquery",
3431            feature = "dialect-snowflake",
3432            feature = "dialect-tsql",
3433            feature = "dialect-spark",
3434            feature = "dialect-databricks",
3435            feature = "dialect-hive",
3436            feature = "dialect-sqlite",
3437            feature = "dialect-trino",
3438            feature = "dialect-presto",
3439            feature = "dialect-duckdb",
3440            feature = "dialect-redshift",
3441            feature = "dialect-starrocks",
3442            feature = "dialect-oracle",
3443            feature = "dialect-clickhouse",
3444            feature = "dialect-fabric",
3445            feature = "dialect-vertica",
3446        ))]
3447        use crate::transforms;
3448
3449        match self.dialect_type {
3450            // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
3451            // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
3452            #[cfg(feature = "dialect-mysql")]
3453            DialectType::MySQL => {
3454                let expr = transforms::eliminate_qualify(expr)?;
3455                let expr = transforms::eliminate_full_outer_join(expr)?;
3456                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3457                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3458                Ok(expr)
3459            }
3460            // PostgreSQL doesn't support QUALIFY
3461            // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
3462            // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
3463            #[cfg(feature = "dialect-postgresql")]
3464            DialectType::PostgreSQL => {
3465                let expr = transforms::eliminate_qualify(expr)?;
3466                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3467                let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
3468                // Normalize SET ... TO to SET ... = in CREATE FUNCTION
3469                // Only normalize when sqlglot would fully parse (no body) —
3470                // sqlglot falls back to Command for complex function bodies,
3471                // preserving the original text including TO.
3472                let expr = if let Expression::CreateFunction(mut cf) = expr {
3473                    if cf.body.is_none() {
3474                        for opt in &mut cf.set_options {
3475                            if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
3476                                &mut opt.value
3477                            {
3478                                *use_to = false;
3479                            }
3480                        }
3481                    }
3482                    Expression::CreateFunction(cf)
3483                } else {
3484                    expr
3485                };
3486                Ok(expr)
3487            }
3488            // BigQuery doesn't support DISTINCT ON or CTE column aliases
3489            #[cfg(feature = "dialect-bigquery")]
3490            DialectType::BigQuery => {
3491                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3492                let expr = transforms::pushdown_cte_column_names(expr)?;
3493                let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
3494                Ok(expr)
3495            }
3496            // Snowflake
3497            #[cfg(feature = "dialect-snowflake")]
3498            DialectType::Snowflake => {
3499                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3500                let expr = transforms::eliminate_window_clause(expr)?;
3501                let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
3502                Ok(expr)
3503            }
3504            // TSQL doesn't support QUALIFY
3505            // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
3506            // TSQL doesn't support CTEs in subqueries (hoist to top level)
3507            // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
3508            // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
3509            #[cfg(feature = "dialect-tsql")]
3510            DialectType::TSQL => {
3511                let expr = transforms::eliminate_qualify(expr)?;
3512                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3513                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3514                let expr =
3515                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::TSQL)?;
3516                let expr = transforms::ensure_bools(expr)?;
3517                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3518                let expr = transforms::strip_cte_materialization(expr)?;
3519                let expr = transforms::move_ctes_to_top_level(expr)?;
3520                let expr = transforms::qualify_derived_table_outputs(expr)?;
3521                Ok(expr)
3522            }
3523            // Fabric shares T-SQL predicate rules and CTE placement restrictions,
3524            // but keeps Fabric-specific APPLY and derived-table behavior separate.
3525            #[cfg(feature = "dialect-fabric")]
3526            DialectType::Fabric => {
3527                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3528                let expr =
3529                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::Fabric)?;
3530                let expr = transforms::ensure_bools(expr)?;
3531                let expr = transforms::strip_cte_materialization(expr)?;
3532                let expr = transforms::move_ctes_to_top_level(expr)?;
3533                Ok(expr)
3534            }
3535            // Spark doesn't support QUALIFY (but Databricks does)
3536            // Spark doesn't support CTEs in subqueries (hoist to top level)
3537            #[cfg(feature = "dialect-spark")]
3538            DialectType::Spark => {
3539                let expr = transforms::eliminate_qualify(expr)?;
3540                let expr = transforms::add_auto_table_alias(expr)?;
3541                let expr = transforms::simplify_nested_paren_values(expr)?;
3542                let expr = transforms::move_ctes_to_top_level(expr)?;
3543                Ok(expr)
3544            }
3545            // Databricks supports QUALIFY natively
3546            // Databricks doesn't support CTEs in subqueries (hoist to top level)
3547            #[cfg(feature = "dialect-databricks")]
3548            DialectType::Databricks => {
3549                let expr = transforms::add_auto_table_alias(expr)?;
3550                let expr = transforms::simplify_nested_paren_values(expr)?;
3551                let expr = transforms::move_ctes_to_top_level(expr)?;
3552                Ok(expr)
3553            }
3554            // Hive doesn't support QUALIFY or CTEs in subqueries
3555            #[cfg(feature = "dialect-hive")]
3556            DialectType::Hive => {
3557                let expr = transforms::eliminate_qualify(expr)?;
3558                let expr = transforms::move_ctes_to_top_level(expr)?;
3559                Ok(expr)
3560            }
3561            // SQLite doesn't support QUALIFY
3562            #[cfg(feature = "dialect-sqlite")]
3563            DialectType::SQLite => {
3564                let expr = transforms::eliminate_qualify(expr)?;
3565                Ok(expr)
3566            }
3567            // Trino doesn't support QUALIFY
3568            #[cfg(feature = "dialect-trino")]
3569            DialectType::Trino => {
3570                let expr = transforms::eliminate_qualify(expr)?;
3571                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
3572                Ok(expr)
3573            }
3574            // Presto doesn't support QUALIFY or WINDOW clause
3575            #[cfg(feature = "dialect-presto")]
3576            DialectType::Presto => {
3577                let expr = transforms::eliminate_qualify(expr)?;
3578                let expr = transforms::eliminate_window_clause(expr)?;
3579                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
3580                Ok(expr)
3581            }
3582            // DuckDB supports QUALIFY - no elimination needed
3583            // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
3584            // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
3585            #[cfg(feature = "dialect-duckdb")]
3586            DialectType::DuckDB => {
3587                let expr = transforms::expand_posexplode_duckdb(expr)?;
3588                let expr = transforms::expand_like_any(expr)?;
3589                Ok(expr)
3590            }
3591            // Redshift doesn't support QUALIFY, WINDOW clause, or GENERATE_DATE_ARRAY
3592            #[cfg(feature = "dialect-redshift")]
3593            DialectType::Redshift => {
3594                let expr = transforms::eliminate_qualify(expr)?;
3595                let expr = transforms::eliminate_window_clause(expr)?;
3596                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3597                Ok(expr)
3598            }
3599            // StarRocks doesn't support BETWEEN in DELETE statements or QUALIFY
3600            #[cfg(feature = "dialect-starrocks")]
3601            DialectType::StarRocks => {
3602                let expr = transforms::eliminate_qualify(expr)?;
3603                let expr = transforms::expand_between_in_delete(expr)?;
3604                let expr = transforms::eliminate_distinct_on_for_dialect(
3605                    expr,
3606                    Some(DialectType::StarRocks),
3607                    Some(DialectType::StarRocks),
3608                )?;
3609                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3610                Ok(expr)
3611            }
3612            // DataFusion supports QUALIFY and semi/anti joins natively
3613            #[cfg(feature = "dialect-datafusion")]
3614            DialectType::DataFusion => Ok(expr),
3615            // Vertica doesn't support QUALIFY or semi/anti join syntax
3616            #[cfg(feature = "dialect-vertica")]
3617            DialectType::Vertica => {
3618                let expr = transforms::eliminate_qualify(expr)?;
3619                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3620                Ok(expr)
3621            }
3622            // Oracle doesn't support QUALIFY
3623            #[cfg(feature = "dialect-oracle")]
3624            DialectType::Oracle => {
3625                let expr = transforms::eliminate_qualify(expr)?;
3626                Ok(expr)
3627            }
3628            // Drill - no special preprocessing needed
3629            #[cfg(feature = "dialect-drill")]
3630            DialectType::Drill => Ok(expr),
3631            // Teradata - no special preprocessing needed
3632            #[cfg(feature = "dialect-teradata")]
3633            DialectType::Teradata => Ok(expr),
3634            // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
3635            #[cfg(feature = "dialect-clickhouse")]
3636            DialectType::ClickHouse => {
3637                let expr = transforms::no_limit_order_by_union(expr)?;
3638                Ok(expr)
3639            }
3640            // Other dialects - no preprocessing
3641            _ => Ok(expr),
3642        }
3643    }
3644
3645    /// Transpile SQL from this dialect to the given target dialect.
3646    ///
3647    /// The target may be specified as either a built-in [`DialectType`] enum variant
3648    /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
3649    ///
3650    /// ```rust,ignore
3651    /// let pg = Dialect::get(DialectType::PostgreSQL);
3652    /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?;   // enum
3653    /// pg.transpile("SELECT NOW()", &custom_dialect)?;         // handle
3654    /// ```
3655    ///
3656    /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
3657    #[cfg(feature = "transpile")]
3658    pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
3659        self.transpile_with(sql, target, TranspileOptions::default())
3660    }
3661
3662    /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
3663    #[cfg(feature = "transpile")]
3664    pub fn transpile_with<T: TranspileTarget>(
3665        &self,
3666        sql: &str,
3667        target: T,
3668        opts: TranspileOptions,
3669    ) -> Result<Vec<String>> {
3670        target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
3671    }
3672
3673    #[cfg(feature = "transpile")]
3674    fn transpile_inner(
3675        &self,
3676        sql: &str,
3677        target_dialect: &Dialect,
3678        opts: &TranspileOptions,
3679    ) -> Result<Vec<String>> {
3680        let mut effective_opts = opts.clone();
3681        effective_opts.complexity_guard =
3682            self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
3683        let opts = &effective_opts;
3684        let target = target_dialect.dialect_type;
3685        if matches!(self.dialect_type, DialectType::PostgreSQL)
3686            && matches!(target, DialectType::SQLite)
3687        {
3688            self.reject_pgvector_distance_operators_for_sqlite(sql)?;
3689        }
3690        let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
3691        let generic_identity =
3692            self.dialect_type == DialectType::Generic && target == DialectType::Generic;
3693
3694        if generic_identity {
3695            return expressions
3696                .into_iter()
3697                .map(|expr| {
3698                    Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
3699                    target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
3700                })
3701                .collect();
3702        }
3703
3704        expressions
3705            .into_iter()
3706            .map(|expr| {
3707                // Reject unsupported source semantics before normalization can
3708                // erase them. This AST-only pass shares conversion decisions with
3709                // the generator and never renders throwaway SQL.
3710                Self::reject_hana_source_semantics(
3711                    &expr,
3712                    self.dialect_type,
3713                    target_dialect.generator_config.dialect.unwrap_or_default(),
3714                )?;
3715
3716                let expr =
3717                    normalization::vertica::prepare_conversion(expr, self.dialect_type, target)?;
3718                normalization::vertica::validate_conversion(&expr, self.dialect_type, target)?;
3719                let expr = normalization::duckdb::prepare_integer_division(
3720                    expr,
3721                    self.dialect_type,
3722                    target,
3723                )?;
3724                // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
3725                // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
3726                // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
3727                //
3728                // Any source with native `ORDER BY ALL`: expand it to positional
3729                // `ORDER BY 1..n` for targets without it, on both plain SELECTs and
3730                // compound queries (whose ORDER BY lives on the set-op node).
3731                let source = self.dialect_type;
3732                let is_duckdb = matches!(source, DialectType::DuckDB);
3733                let expand_all = source.supports_order_by_all() && !target.supports_order_by_all();
3734                let expr = if source.supports_order_by_all() {
3735                    use crate::expressions::DataType as DT;
3736                    transform_recursive(expr, &|e| match e {
3737                        Expression::DataType(DT::VarChar { .. }) if is_duckdb => {
3738                            Ok(Expression::DataType(DT::Text))
3739                        }
3740                        Expression::DataType(DT::Char { .. }) if is_duckdb => {
3741                            Ok(Expression::DataType(DT::Text))
3742                        }
3743                        Expression::Select(sel) => {
3744                            expand_select_order_by_all(sel, source, target).map(Expression::Select)
3745                        }
3746                        Expression::Subquery(mut subquery) if expand_all => {
3747                            let width = query_output_width(&subquery.this);
3748                            expand_order_by_all(&mut subquery.order_by, width, source, target)?;
3749                            Ok(Expression::Subquery(subquery))
3750                        }
3751                        Expression::Union(mut u) if expand_all => {
3752                            let width = if u.by_name {
3753                                None
3754                            } else {
3755                                query_output_width(&u.left)
3756                            };
3757                            expand_order_by_all(&mut u.order_by, width, source, target)?;
3758                            Ok(Expression::Union(u))
3759                        }
3760                        Expression::Intersect(mut i) if expand_all => {
3761                            let width = query_output_width(&i.left);
3762                            expand_order_by_all(&mut i.order_by, width, source, target)?;
3763                            Ok(Expression::Intersect(i))
3764                        }
3765                        Expression::Except(mut ex) if expand_all => {
3766                            let width = query_output_width(&ex.left);
3767                            expand_order_by_all(&mut ex.order_by, width, source, target)?;
3768                            Ok(Expression::Except(ex))
3769                        }
3770                        _ => Ok(e),
3771                    })?
3772                } else {
3773                    expr
3774                };
3775
3776                Self::reject_clickhouse_session_semantics(&expr, self.dialect_type, target, opts)?;
3777                Self::reject_postgres_tsql_strict_regex_predicates(
3778                    &expr,
3779                    self.dialect_type,
3780                    target,
3781                    opts,
3782                )?;
3783                Self::reject_tsql_strict_json_constructor_return_types(
3784                    &expr,
3785                    self.dialect_type,
3786                    target,
3787                    opts,
3788                )?;
3789                Self::reject_postgres_tsql_strict_json_aggregate_modifiers(
3790                    &expr,
3791                    self.dialect_type,
3792                    target,
3793                    opts,
3794                )?;
3795
3796                // When source and target differ, first normalize the source dialect's
3797                // AST constructs to standard SQL, so that the target dialect can handle them.
3798                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
3799                let normalized =
3800                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
3801                        self.transform_with_guard(expr, opts.complexity_guard)?
3802                    } else {
3803                        expr
3804                    };
3805
3806                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
3807                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
3808                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
3809                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
3810                let normalized =
3811                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3812                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
3813                    {
3814                        transform_recursive(normalized, &|e| {
3815                            if let Expression::Function(ref f) = e {
3816                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
3817                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
3818                                    if let (
3819                                        Expression::Function(ref jq),
3820                                        Expression::Function(ref jv),
3821                                    ) = (&f.args[0], &f.args[1])
3822                                    {
3823                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
3824                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
3825                                        {
3826                                            // Unwrap: return just JSON_QUERY(...)
3827                                            return Ok(f.args[0].clone());
3828                                        }
3829                                    }
3830                                }
3831                            }
3832                            Ok(e)
3833                        })?
3834                    } else {
3835                        normalized
3836                    };
3837
3838                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME.
3839                // Preserve precision for ClickHouse's Time64 lowering; retain the existing
3840                // precision-less LOCALTIME compatibility output for other targets.
3841                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3842                    && !matches!(target, DialectType::Snowflake)
3843                {
3844                    transform_recursive(normalized, &|e| match e {
3845                        Expression::Function(ref f)
3846                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") =>
3847                        {
3848                            let precision = if matches!(target, DialectType::ClickHouse) {
3849                                f.args.first().cloned().map(Box::new)
3850                            } else {
3851                                None
3852                            };
3853                            Ok(Expression::Localtime(Box::new(
3854                                crate::expressions::Localtime { this: precision },
3855                            )))
3856                        }
3857                        Expression::Localtime(ref localtime)
3858                            if !matches!(target, DialectType::ClickHouse)
3859                                && localtime.this.is_some() =>
3860                        {
3861                            Ok(Expression::Localtime(Box::new(
3862                                crate::expressions::Localtime { this: None },
3863                            )))
3864                        }
3865                        _ => Ok(e),
3866                    })?
3867                } else {
3868                    normalized
3869                };
3870
3871                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
3872                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
3873                // transform. DuckDB requires the count argument to be BIGINT.
3874                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3875                    && matches!(target, DialectType::DuckDB)
3876                {
3877                    transform_recursive(normalized, &|e| {
3878                        if let Expression::Function(ref f) = e {
3879                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
3880                                // Check if first arg is space string literal
3881                                if let Expression::Literal(ref lit) = f.args[0] {
3882                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
3883                                    {
3884                                        if s == " " {
3885                                            // Wrap second arg in CAST(... AS BIGINT) if not already
3886                                            if !matches!(f.args[1], Expression::Cast(_)) {
3887                                                let mut new_args = f.args.clone();
3888                                                new_args[1] = Expression::Cast(Box::new(
3889                                                    crate::expressions::Cast {
3890                                                        this: new_args[1].clone(),
3891                                                        to: crate::expressions::DataType::BigInt {
3892                                                            length: None,
3893                                                        },
3894                                                        trailing_comments: Vec::new(),
3895                                                        double_colon_syntax: false,
3896                                                        format: None,
3897                                                        default: None,
3898                                                        inferred_type: None,
3899                                                    },
3900                                                ));
3901                                                return Ok(Expression::Function(Box::new(
3902                                                    crate::expressions::Function {
3903                                                        on_error: None,
3904                                                        qualified_name: Vec::new(),
3905                                                        source_dialect: None,
3906
3907                                                        name: f.name.clone(),
3908                                                        args: new_args,
3909                                                        distinct: f.distinct,
3910                                                        trailing_comments: f
3911                                                            .trailing_comments
3912                                                            .clone(),
3913                                                        use_bracket_syntax: f.use_bracket_syntax,
3914                                                        no_parens: f.no_parens,
3915                                                        quoted: f.quoted,
3916                                                        span: None,
3917                                                        inferred_type: None,
3918                                                    },
3919                                                )));
3920                                            }
3921                                        }
3922                                    }
3923                                }
3924                            }
3925                        }
3926                        Ok(e)
3927                    })?
3928                } else {
3929                    normalized
3930                };
3931
3932                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
3933                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
3934                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3935                    && !matches!(target, DialectType::BigQuery)
3936                {
3937                    crate::transforms::propagate_struct_field_names(normalized)?
3938                } else {
3939                    normalized
3940                };
3941
3942                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
3943                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
3944                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
3945                // functions handle their generator args differently (as float seeds).
3946                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3947                    && matches!(target, DialectType::DuckDB)
3948                {
3949                    fn make_scaled_random() -> Expression {
3950                        let lower =
3951                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3952                                "-9.223372036854776E+18".to_string(),
3953                            )));
3954                        let upper =
3955                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3956                                "9.223372036854776e+18".to_string(),
3957                            )));
3958                        let random_call = Expression::Random(crate::expressions::Random);
3959                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
3960                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
3961                                left: upper,
3962                                right: lower.clone(),
3963                                left_comments: vec![],
3964                                operator_comments: vec![],
3965                                trailing_comments: vec![],
3966                                inferred_type: None,
3967                            })),
3968                            trailing_comments: vec![],
3969                        }));
3970                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
3971                            left: random_call,
3972                            right: range_size,
3973                            left_comments: vec![],
3974                            operator_comments: vec![],
3975                            trailing_comments: vec![],
3976                            inferred_type: None,
3977                        }));
3978                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
3979                            left: lower,
3980                            right: scaled,
3981                            left_comments: vec![],
3982                            operator_comments: vec![],
3983                            trailing_comments: vec![],
3984                            inferred_type: None,
3985                        }));
3986                        Expression::Cast(Box::new(crate::expressions::Cast {
3987                            this: shifted,
3988                            to: crate::expressions::DataType::BigInt { length: None },
3989                            trailing_comments: vec![],
3990                            double_colon_syntax: false,
3991                            format: None,
3992                            default: None,
3993                            inferred_type: None,
3994                        }))
3995                    }
3996
3997                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
3998                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
3999                    // This prevents transform_recursive (which is bottom-up) from expanding
4000                    // seeded RANDOM into make_scaled_random() and losing the seed value.
4001                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4002                    // and then un-expanded back to Expression::Random by the code below.
4003                    let normalized = transform_recursive(normalized, &|e| {
4004                        if let Expression::Function(ref f) = e {
4005                            let n = f.name.to_ascii_uppercase();
4006                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4007                                if let Expression::Function(mut f) = e {
4008                                    for arg in f.args.iter_mut() {
4009                                        if let Expression::Rand(ref r) = arg {
4010                                            if r.lower.is_none() && r.upper.is_none() {
4011                                                if let Some(ref seed) = r.seed {
4012                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4013                                                    // so it won't be expanded by the RANDOM expansion below
4014                                                    *arg = Expression::Function(Box::new(
4015                                                        crate::expressions::Function::new(
4016                                                            "RANDOM".to_string(),
4017                                                            vec![*seed.clone()],
4018                                                        ),
4019                                                    ));
4020                                                }
4021                                            }
4022                                        }
4023                                    }
4024                                    return Ok(Expression::Function(f));
4025                                }
4026                            }
4027                        }
4028                        Ok(e)
4029                    })?;
4030
4031                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4032                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4033                    // we see the parent. We detect this and undo the expansion by replacing
4034                    // the expanded pattern back with Expression::Random.
4035                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4036                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4037                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4038                    transform_recursive(normalized, &|e| {
4039                        if let Expression::Function(ref f) = e {
4040                            let n = f.name.to_ascii_uppercase();
4041                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4042                                if let Expression::Function(mut f) = e {
4043                                    for arg in f.args.iter_mut() {
4044                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4045                                        if let Expression::Cast(ref cast) = arg {
4046                                            if matches!(
4047                                                cast.to,
4048                                                crate::expressions::DataType::BigInt { .. }
4049                                            ) {
4050                                                if let Expression::Add(ref add) = cast.this {
4051                                                    if let Expression::Literal(ref lit) = add.left {
4052                                                        if let crate::expressions::Literal::Number(
4053                                                            ref num,
4054                                                        ) = lit.as_ref()
4055                                                        {
4056                                                            if num == "-9.223372036854776E+18" {
4057                                                                *arg = Expression::Random(
4058                                                                    crate::expressions::Random,
4059                                                                );
4060                                                            }
4061                                                        }
4062                                                    }
4063                                                }
4064                                            }
4065                                        }
4066                                    }
4067                                    return Ok(Expression::Function(f));
4068                                }
4069                                return Ok(e);
4070                            }
4071                        }
4072                        match e {
4073                            Expression::Random(_) => Ok(make_scaled_random()),
4074                            // Rand(seed) with no bounds: drop seed and expand
4075                            // (DuckDB RANDOM doesn't support seeds)
4076                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4077                                Ok(make_scaled_random())
4078                            }
4079                            _ => Ok(e),
4080                        }
4081                    })?
4082                } else {
4083                    normalized
4084                };
4085
4086                // Apply cross-dialect semantic normalizations
4087                let normalized = normalization::normalize(
4088                    normalized,
4089                    self.dialect_type,
4090                    target,
4091                    matches!(
4092                        opts.unsupported_level,
4093                        UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4094                    ),
4095                )?;
4096
4097                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4098                    Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
4099                } else {
4100                    normalized
4101                };
4102
4103                let normalized =
4104                    if matches!(
4105                        self.dialect_type,
4106                        DialectType::PostgreSQL | DialectType::CockroachDB
4107                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4108                    {
4109                        Self::normalize_postgres_type_function_casts(normalized, target)?
4110                    } else {
4111                        normalized
4112                    };
4113
4114                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4115                    && !matches!(target, DialectType::SQLite)
4116                {
4117                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
4118                } else {
4119                    normalized
4120                };
4121
4122                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4123                    && matches!(target, DialectType::SQLite)
4124                {
4125                    Self::normalize_postgres_to_sqlite_types(normalized)?
4126                } else {
4127                    normalized
4128                };
4129
4130                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4131                    && matches!(target, DialectType::Fabric)
4132                {
4133                    Self::normalize_postgres_to_fabric_types(normalized)?
4134                } else {
4135                    normalized
4136                };
4137
4138                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4139                // (SELECT UNNEST(..., max_depth => 2)) subquery
4140                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4141                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4142                    && matches!(target, DialectType::DuckDB)
4143                {
4144                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4145                } else {
4146                    normalized
4147                };
4148
4149                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4150                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4151                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4152                    && matches!(
4153                        target,
4154                        DialectType::DuckDB
4155                            | DialectType::Presto
4156                            | DialectType::Trino
4157                            | DialectType::Athena
4158                            | DialectType::Spark
4159                            | DialectType::Databricks
4160                    ) {
4161                    crate::transforms::unnest_alias_to_column_alias(normalized)?
4162                } else if matches!(self.dialect_type, DialectType::BigQuery)
4163                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4164                {
4165                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4166                    // but don't convert alias format (no _t0 wrapper)
4167                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4168                    // For Redshift: strip UNNEST when arg is a column reference path
4169                    if matches!(target, DialectType::Redshift) {
4170                        crate::transforms::strip_unnest_column_refs(result)?
4171                    } else {
4172                        result
4173                    }
4174                } else {
4175                    normalized
4176                };
4177
4178                // For Presto/Trino targets from PostgreSQL/Redshift source:
4179                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4180                let normalized = if matches!(
4181                    self.dialect_type,
4182                    DialectType::PostgreSQL | DialectType::Redshift
4183                ) && matches!(
4184                    target,
4185                    DialectType::Presto | DialectType::Trino | DialectType::Athena
4186                ) {
4187                    crate::transforms::wrap_unnest_join_aliases(normalized)?
4188                } else {
4189                    normalized
4190                };
4191
4192                // Eliminate DISTINCT ON with target-dialect awareness
4193                // This must happen after source transform (which may produce DISTINCT ON)
4194                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4195                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4196                    normalized,
4197                    Some(target),
4198                    Some(self.dialect_type),
4199                )?;
4200
4201                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4202                let normalized = if matches!(target, DialectType::Snowflake) {
4203                    Self::transform_generate_date_array_snowflake(normalized)?
4204                } else {
4205                    normalized
4206                };
4207
4208                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4209                let normalized = if matches!(
4210                    target,
4211                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
4212                ) {
4213                    crate::transforms::unnest_to_explode_select(normalized)?
4214                } else {
4215                    normalized
4216                };
4217
4218                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4219                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4220                    crate::transforms::no_limit_order_by_union(normalized)?
4221                } else {
4222                    normalized
4223                };
4224
4225                let normalized = if matches!(
4226                    self.dialect_type,
4227                    DialectType::PostgreSQL | DialectType::CockroachDB
4228                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4229                {
4230                    Self::normalize_postgres_boolean_semantics_for_tsql(normalized)?
4231                } else {
4232                    normalized
4233                };
4234
4235                let normalized = if self.dialect_type == DialectType::PostgreSQL
4236                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4237                {
4238                    Self::normalize_postgres_bytea_literals_for_tsql(normalized)?
4239                } else {
4240                    normalized
4241                };
4242
4243                let normalized = if matches!(
4244                    self.dialect_type,
4245                    DialectType::PostgreSQL | DialectType::CockroachDB
4246                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4247                {
4248                    Self::normalize_postgres_string_semantics_for_tsql(normalized)?
4249                } else {
4250                    normalized
4251                };
4252
4253                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4254                // Python sqlglot does this in the TSQL generator, but we can't do it there
4255                // because it would break TSQL -> TSQL identity
4256                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4257                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4258                {
4259                    transform_recursive(normalized, &|e| {
4260                        if let Expression::Count(ref c) = e {
4261                            // Build COUNT_BIG(...) as an AggregateFunction
4262                            let args = if c.star {
4263                                vec![Expression::Star(crate::expressions::Star {
4264                                    table: None,
4265                                    except: None,
4266                                    replace: None,
4267                                    rename: None,
4268                                    trailing_comments: Vec::new(),
4269                                    span: None,
4270                                })]
4271                            } else if let Some(ref this) = c.this {
4272                                vec![this.clone()]
4273                            } else {
4274                                vec![]
4275                            };
4276                            Ok(Expression::AggregateFunction(Box::new(
4277                                crate::expressions::AggregateFunction {
4278                                    source_dialect: None,
4279
4280                                    name: "COUNT_BIG".to_string(),
4281                                    args,
4282                                    distinct: c.distinct,
4283                                    filter: c.filter.clone(),
4284                                    order_by: Vec::new(),
4285                                    limit: None,
4286                                    ignore_nulls: None,
4287                                    inferred_type: None,
4288                                },
4289                            )))
4290                        } else {
4291                            Ok(e)
4292                        }
4293                    })?
4294                } else {
4295                    normalized
4296                };
4297
4298                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4299                // contexts intact, but materialize boolean-valued expressions used
4300                // as values before target transforms add ORDER BY null sort keys.
4301                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4302                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4303                {
4304                    let normalized = if self.dialect_type == DialectType::PostgreSQL {
4305                        Self::rewrite_postgres_row_value_equality_for_tsql(normalized)?
4306                    } else {
4307                        normalized
4308                    };
4309                    Self::rewrite_boolean_values_for_tsql(normalized)?
4310                } else {
4311                    normalized
4312                };
4313
4314                let normalized = if matches!(
4315                    self.dialect_type,
4316                    DialectType::PostgreSQL | DialectType::CockroachDB
4317                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4318                {
4319                    Self::rewrite_postgres_format_for_tsql(normalized, target)?
4320                } else {
4321                    normalized
4322                };
4323
4324                let normalized = if self.dialect_type == DialectType::PostgreSQL
4325                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4326                {
4327                    Self::normalize_postgres_only_for_tsql(normalized)?
4328                } else {
4329                    normalized
4330                };
4331
4332                let transformed =
4333                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4334
4335                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4336                // remaining filters after target transforms so special aggregate rewrites
4337                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4338                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4339                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4340                } else {
4341                    transformed
4342                };
4343
4344                let transformed = if matches!(
4345                    self.dialect_type,
4346                    DialectType::PostgreSQL | DialectType::CockroachDB
4347                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4348                {
4349                    crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4350                } else {
4351                    transformed
4352                };
4353
4354                let transformed = if matches!(
4355                    self.dialect_type,
4356                    DialectType::PostgreSQL | DialectType::CockroachDB
4357                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4358                {
4359                    Self::normalize_postgres_trim_for_tsql(transformed)?
4360                } else {
4361                    transformed
4362                };
4363
4364                let transformed = if matches!(
4365                    self.dialect_type,
4366                    DialectType::PostgreSQL | DialectType::CockroachDB
4367                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4368                {
4369                    Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4370                } else {
4371                    transformed
4372                };
4373
4374                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4375                let transformed = if matches!(target, DialectType::DuckDB) {
4376                    Self::seq_rownum_to_range(transformed)?
4377                } else {
4378                    transformed
4379                };
4380
4381                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4382                    Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4383                }
4384
4385                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4386                    Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4387                } else {
4388                    transformed
4389                };
4390
4391                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4392                    Self::legalize_tsql_nested_order_by(transformed)?
4393                } else {
4394                    transformed
4395                };
4396
4397                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4398
4399                let mut sql = target_dialect.generate_with_transpile_options(
4400                    &transformed,
4401                    self.dialect_type,
4402                    opts,
4403                )?;
4404
4405                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
4406                if opts.pretty && target == DialectType::Snowflake {
4407                    sql = Self::normalize_snowflake_pretty(sql);
4408                }
4409
4410                Ok(sql)
4411            })
4412            .collect()
4413    }
4414}
4415
4416// Transpile-only methods: cross-dialect normalization and helpers
4417#[cfg(feature = "transpile")]
4418impl Dialect {
4419    fn legalize_tsql_nested_order_by(expr: Expression) -> Result<Expression> {
4420        let preserve_root_order = matches!(&expr, Expression::Select(select) if Self::tsql_select_needs_order_offset(select));
4421
4422        let mut transformed = transform_recursive(expr, &|node| match node {
4423            Expression::Select(mut select) => {
4424                Self::legalize_tsql_select_offset(&mut select);
4425                if Self::tsql_select_needs_order_offset(&select) {
4426                    select.offset = Some(Offset {
4427                        this: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
4428                        rows: Some(true),
4429                    });
4430                }
4431                Ok(Expression::Select(select))
4432            }
4433            Expression::Subquery(mut subquery) => {
4434                Self::legalize_tsql_offset(&mut subquery.order_by, &mut subquery.offset, false);
4435                Ok(Expression::Subquery(subquery))
4436            }
4437            Expression::Union(mut union) => {
4438                Self::legalize_tsql_set_offset(&mut union.order_by, &mut union.offset);
4439                Ok(Expression::Union(union))
4440            }
4441            Expression::Intersect(mut intersect) => {
4442                Self::legalize_tsql_set_offset(&mut intersect.order_by, &mut intersect.offset);
4443                Ok(Expression::Intersect(intersect))
4444            }
4445            Expression::Except(mut except) => {
4446                Self::legalize_tsql_set_offset(&mut except.order_by, &mut except.offset);
4447                Ok(Expression::Except(except))
4448            }
4449            other => Ok(other),
4450        })?;
4451
4452        if preserve_root_order {
4453            if let Expression::Select(select) = &mut transformed {
4454                select.offset = None;
4455            }
4456        }
4457
4458        Self::drop_tsql_unbounded_nested_set_order_by(transformed)
4459    }
4460
4461    fn drop_tsql_unbounded_nested_set_order_by(mut expr: Expression) -> Result<Expression> {
4462        let root_order_by = Self::take_tsql_root_set_order_by(&mut expr);
4463
4464        let mut transformed = transform_recursive(expr, &|node| match node {
4465            Expression::Union(mut union) => {
4466                if union.limit.is_none() && union.offset.is_none() {
4467                    union.order_by = None;
4468                }
4469                Ok(Expression::Union(union))
4470            }
4471            Expression::Intersect(mut intersect) => {
4472                if intersect.limit.is_none() && intersect.offset.is_none() {
4473                    intersect.order_by = None;
4474                }
4475                Ok(Expression::Intersect(intersect))
4476            }
4477            Expression::Except(mut except) => {
4478                if except.limit.is_none() && except.offset.is_none() {
4479                    except.order_by = None;
4480                }
4481                Ok(Expression::Except(except))
4482            }
4483            other => Ok(other),
4484        })?;
4485
4486        if let Some(order_by) = root_order_by {
4487            Self::restore_tsql_root_set_order_by(&mut transformed, order_by);
4488        }
4489
4490        Ok(transformed)
4491    }
4492
4493    fn take_tsql_root_set_order_by(expr: &mut Expression) -> Option<OrderBy> {
4494        match expr {
4495            Expression::Union(union) => union.order_by.take(),
4496            Expression::Intersect(intersect) => intersect.order_by.take(),
4497            Expression::Except(except) => except.order_by.take(),
4498            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4499                Self::take_tsql_root_set_order_by(&mut subquery.this)
4500            }
4501            Expression::Paren(paren) => Self::take_tsql_root_set_order_by(&mut paren.this),
4502            _ => None,
4503        }
4504    }
4505
4506    fn restore_tsql_root_set_order_by(expr: &mut Expression, order_by: OrderBy) {
4507        match expr {
4508            Expression::Union(union) => union.order_by = Some(order_by),
4509            Expression::Intersect(intersect) => intersect.order_by = Some(order_by),
4510            Expression::Except(except) => except.order_by = Some(order_by),
4511            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4512                Self::restore_tsql_root_set_order_by(&mut subquery.this, order_by);
4513            }
4514            Expression::Paren(paren) => {
4515                Self::restore_tsql_root_set_order_by(&mut paren.this, order_by);
4516            }
4517            _ => {}
4518        }
4519    }
4520
4521    fn legalize_tsql_select_offset(select: &mut crate::expressions::Select) {
4522        let has_fetch = select.fetch.is_some();
4523        Self::legalize_tsql_offset(&mut select.order_by, &mut select.offset, has_fetch);
4524    }
4525
4526    fn legalize_tsql_offset(
4527        order_by: &mut Option<OrderBy>,
4528        offset: &mut Option<Offset>,
4529        retain_inert_offset: bool,
4530    ) {
4531        if order_by.is_some() {
4532            return;
4533        }
4534
4535        if offset
4536            .as_ref()
4537            .is_some_and(|offset| Self::tsql_offset_is_inert(&offset.this))
4538            && !retain_inert_offset
4539        {
4540            *offset = None;
4541        } else if offset.is_some() {
4542            *order_by = Some(Generator::dummy_tsql_order_by());
4543        }
4544    }
4545
4546    fn legalize_tsql_set_offset(
4547        order_by: &mut Option<OrderBy>,
4548        offset: &mut Option<Box<Expression>>,
4549    ) {
4550        if order_by.is_some() {
4551            return;
4552        }
4553
4554        if offset.as_deref().is_some_and(Self::tsql_offset_is_inert) {
4555            *offset = None;
4556        } else if offset.is_some() {
4557            *order_by = Some(Generator::dummy_tsql_order_by());
4558        }
4559    }
4560
4561    fn tsql_offset_is_inert(expr: &Expression) -> bool {
4562        match expr {
4563            Expression::Null(_) => true,
4564            Expression::Literal(literal) => match literal.as_ref() {
4565                Literal::Number(value) => value.parse::<i128>().is_ok_and(|value| value == 0),
4566                _ => false,
4567            },
4568            _ => false,
4569        }
4570    }
4571
4572    fn tsql_select_needs_order_offset(select: &crate::expressions::Select) -> bool {
4573        select.order_by.is_some()
4574            && select.top.is_none()
4575            && select.limit.is_none()
4576            && select.offset.is_none()
4577            && select.fetch.is_none()
4578            && select.for_xml.is_empty()
4579            && select.for_json.is_empty()
4580    }
4581
4582    fn reject_hana_source_semantics(
4583        expression: &Expression,
4584        source: DialectType,
4585        target: DialectType,
4586    ) -> Result<()> {
4587        if source != DialectType::HANA || target == DialectType::HANA {
4588            return Ok(());
4589        }
4590        let mut pending = vec![expression];
4591        while let Some(node) = pending.pop() {
4592            if node.source_dialect() == Some(DialectType::HANA) {
4593                Generator::validate_hana_source_node(node, target)?;
4594            }
4595            // A supported outer call does not establish support for its arguments.
4596            // Types in column definitions, casts, routine signatures, etc. are
4597            // embedded syntax, not Expression children. Validate each type once,
4598            // before normalization can replace its enclosing ARRAY/STRUCT type.
4599            // A scalar native CAST already uses a value-aware conversion check
4600            // (e.g. a HANA TINYINT literal can fit a target signed integer).
4601            let cast_type_validated = matches!(node, Expression::Cast(c)
4602                if matches!(c.to, crate::expressions::DataType::Hana { .. }));
4603            let mut type_result = Ok(());
4604            crate::ast_children::for_each_child_and_type_untracked(
4605                node,
4606                |child| pending.push(child),
4607                |data_type| {
4608                    if !cast_type_validated && type_result.is_ok() {
4609                        type_result = Generator::validate_hana_source_type(data_type, target);
4610                    }
4611                },
4612            );
4613            type_result?;
4614        }
4615        Ok(())
4616    }
4617
4618    fn reject_clickhouse_session_semantics(
4619        expr: &Expression,
4620        source: DialectType,
4621        target: DialectType,
4622        opts: &TranspileOptions,
4623    ) -> Result<()> {
4624        if !matches!(
4625            opts.unsupported_level,
4626            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4627        ) || target != DialectType::ClickHouse
4628            || source == DialectType::ClickHouse
4629        {
4630            return Ok(());
4631        }
4632
4633        const JOIN_USE_NULLS_DIAGNOSTIC: &str = "ClickHouse outer joins require the target setting join_use_nulls = 1 to preserve unmatched-row NULL semantics";
4634        const AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC: &str = "ClickHouse non-count aggregates require the target setting aggregate_functions_null_for_empty = 1 to preserve empty-input NULL semantics";
4635
4636        fn is_current_session_time(node: &Expression) -> bool {
4637            match node {
4638                Expression::CurrentDate(_)
4639                | Expression::CurrentTime(_)
4640                | Expression::CurrentTimestamp(_)
4641                | Expression::CurrentTimestampLTZ(_)
4642                | Expression::Localtime(_)
4643                | Expression::Localtimestamp(_)
4644                | Expression::Systimestamp(_) => true,
4645                Expression::Function(function) if !function.quoted => matches!(
4646                    function.name.to_ascii_uppercase().as_str(),
4647                    "CURRENT_DATE"
4648                        | "CURRENT_TIME"
4649                        | "LOCALTIME"
4650                        | "CURRENT_TIMESTAMP"
4651                        | "CURRENT_TIMESTAMP_LTZ"
4652                        | "LOCALTIMESTAMP"
4653                        | "NOW"
4654                        | "GETDATE"
4655                        | "SYSTIMESTAMP"
4656                ),
4657                _ => false,
4658            }
4659        }
4660
4661        fn is_session_week_part(expression: &Expression) -> bool {
4662            let part = match expression {
4663                Expression::Literal(literal) => match literal.as_ref() {
4664                    Literal::String(value) => Some(value.as_str()),
4665                    _ => None,
4666                },
4667                Expression::Identifier(identifier) => Some(identifier.name.as_str()),
4668                Expression::Var(var) => Some(var.this.as_str()),
4669                Expression::Column(column) if column.table.is_none() => {
4670                    Some(column.name.name.as_str())
4671                }
4672                _ => None,
4673            };
4674            part.is_some_and(|part| {
4675                matches!(
4676                    part.to_ascii_uppercase().as_str(),
4677                    "WEEK" | "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY"
4678                )
4679            })
4680        }
4681
4682        fn is_session_week_trunc(node: &Expression) -> bool {
4683            match node {
4684                Expression::DateTrunc(date_trunc) => {
4685                    matches!(date_trunc.unit, DateTimeField::Week)
4686                }
4687                Expression::Function(function)
4688                    if !function.quoted && function.name.eq_ignore_ascii_case("DATE_TRUNC") =>
4689                {
4690                    function.args.first().is_some_and(is_session_week_part)
4691                }
4692                _ => false,
4693            }
4694        }
4695
4696        fn is_null_extending_clickhouse_join(node: &Expression) -> bool {
4697            fn join_is_null_extending(join: &Join) -> bool {
4698                matches!(
4699                    join.kind,
4700                    JoinKind::Left
4701                        | JoinKind::Right
4702                        | JoinKind::Full
4703                        | JoinKind::NaturalLeft
4704                        | JoinKind::NaturalRight
4705                        | JoinKind::NaturalFull
4706                        | JoinKind::AsOfLeft
4707                        | JoinKind::AsOfRight
4708                )
4709            }
4710
4711            fn joins_are_null_extending(joins: &[Join]) -> bool {
4712                joins.iter().any(join_is_null_extending)
4713            }
4714
4715            match node {
4716                Expression::Join(join) => join_is_null_extending(join),
4717                Expression::Select(select) => joins_are_null_extending(&select.joins),
4718                Expression::JoinedTable(joined) => joins_are_null_extending(&joined.joins),
4719                Expression::Update(update) => {
4720                    joins_are_null_extending(&update.table_joins)
4721                        || joins_are_null_extending(&update.from_joins)
4722                }
4723                Expression::Delete(delete) => joins_are_null_extending(&delete.joins),
4724                _ => false,
4725            }
4726        }
4727
4728        fn is_count_like_aggregate(node: &Expression) -> bool {
4729            match node {
4730                Expression::Count(_)
4731                | Expression::CountIf(_)
4732                | Expression::ApproxDistinct(_)
4733                | Expression::ApproxCountDistinct(_) => true,
4734                Expression::AggregateFunction(function) => matches!(
4735                    function.name.to_ascii_uppercase().as_str(),
4736                    "COUNT" | "COUNT_IF" | "COUNTIF" | "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT"
4737                ),
4738                _ => false,
4739            }
4740        }
4741
4742        fn is_empty_input_sensitive_aggregate(node: &Expression, source: DialectType) -> bool {
4743            Dialect::node_is_aggregate_function(node)
4744                && !is_count_like_aggregate(node)
4745                && !matches!(node, Expression::BitwiseCount(_))
4746                && !matches!(node, Expression::Median(_) if source == DialectType::Snowflake)
4747                && !matches!(
4748                    node,
4749                    Expression::AggregateFunction(function)
4750                        if function.name.to_ascii_lowercase().ends_with("ornull")
4751                )
4752        }
4753
4754        fn setting_is_enabled(setting: &Expression, name: &str) -> bool {
4755            let Expression::Eq(equality) = setting else {
4756                return false;
4757            };
4758            let setting_name = match &equality.left {
4759                Expression::Identifier(identifier) => identifier.name.as_str(),
4760                Expression::Column(column) if column.table.is_none() => column.name.name.as_str(),
4761                Expression::Var(var) => var.this.as_str(),
4762                _ => return false,
4763            };
4764            let enabled = match &equality.right {
4765                Expression::Literal(literal) => {
4766                    matches!(literal.as_ref(), Literal::Number(value) if value.parse::<i128>().is_ok_and(|value| value == 1))
4767                }
4768                Expression::Boolean(boolean) => boolean.value,
4769                _ => false,
4770            };
4771            setting_name.eq_ignore_ascii_case(name) && enabled
4772        }
4773
4774        fn query_has_enabled_setting(expr: &Expression, name: &str) -> bool {
4775            expr.dfs().any(|node| {
4776                matches!(
4777                    node,
4778                    Expression::Select(select)
4779                        if select.settings.as_ref().is_some_and(|settings| {
4780                            settings.iter().any(|setting| setting_is_enabled(setting, name))
4781                        })
4782                )
4783            })
4784        }
4785
4786        let mut diagnostics = Vec::new();
4787        let join_use_nulls_enabled = query_has_enabled_setting(expr, "join_use_nulls");
4788        let aggregate_null_for_empty_enabled =
4789            query_has_enabled_setting(expr, "aggregate_functions_null_for_empty");
4790        for node in expr.dfs() {
4791            if source == DialectType::Snowflake && is_current_session_time(node) {
4792                Self::push_unsupported_diagnostic(
4793                    &mut diagnostics,
4794                    "Snowflake current date/time expressions depend on the session TIMEZONE, which cannot be preserved for ClickHouse",
4795                );
4796            }
4797            if source == DialectType::Snowflake && is_session_week_trunc(node) {
4798                Self::push_unsupported_diagnostic(
4799                    &mut diagnostics,
4800                    "Snowflake DATE_TRUNC with a week unit depends on the session WEEK_START, which cannot be preserved for ClickHouse",
4801                );
4802            }
4803            if !join_use_nulls_enabled && is_null_extending_clickhouse_join(node) {
4804                Self::push_unsupported_diagnostic(&mut diagnostics, JOIN_USE_NULLS_DIAGNOSTIC);
4805            }
4806            if !aggregate_null_for_empty_enabled && is_empty_input_sensitive_aggregate(node, source)
4807            {
4808                Self::push_unsupported_diagnostic(
4809                    &mut diagnostics,
4810                    AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC,
4811                );
4812            }
4813            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4814                break;
4815            }
4816        }
4817
4818        if diagnostics.is_empty() {
4819            return Ok(());
4820        }
4821
4822        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4823            1
4824        } else {
4825            opts.max_unsupported.max(1)
4826        };
4827        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4828        if diagnostics.len() > limit {
4829            messages.push(format!("... and {} more", diagnostics.len() - limit));
4830        }
4831
4832        Err(crate::error::Error::unsupported(
4833            messages.join("; "),
4834            target.to_string(),
4835        ))
4836    }
4837
4838    fn reject_strict_unsupported(
4839        expr: &Expression,
4840        source: DialectType,
4841        target: DialectType,
4842        opts: &TranspileOptions,
4843    ) -> Result<()> {
4844        if !matches!(
4845            opts.unsupported_level,
4846            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4847        ) {
4848            return Ok(());
4849        }
4850
4851        let mut diagnostics = Vec::new();
4852        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4853            && matches!(target, DialectType::TSQL | DialectType::Fabric)
4854            && Self::tsql_apply_has_invalid_outer_aggregate(expr)
4855        {
4856            Self::push_unsupported_diagnostic(
4857                &mut diagnostics,
4858                "APPLY aggregate expressions that combine an outer reference with another column reference",
4859            );
4860        }
4861        let structural_grouping_tuples =
4862            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4863                && matches!(target, DialectType::TSQL | DialectType::Fabric)
4864            {
4865                Self::collect_tsql_grouping_tuple_nodes(expr)
4866            } else {
4867                HashSet::new()
4868            };
4869
4870        for node in expr.dfs() {
4871            if source == DialectType::Snowflake && target == DialectType::ClickHouse {
4872                if Self::node_is_function_named(node, "TO_CHAR") {
4873                    Self::push_unsupported_diagnostic(
4874                        &mut diagnostics,
4875                        "Snowflake TO_CHAR overload or dynamic format",
4876                    );
4877                }
4878                if Self::node_is_function_named(node, "TRY_TO_DOUBLE")
4879                    || matches!(node, Expression::ToDouble(to_double) if to_double.safe.is_some() && to_double.format.is_some())
4880                {
4881                    Self::push_unsupported_diagnostic(
4882                        &mut diagnostics,
4883                        "Snowflake TRY_TO_DOUBLE with a format model",
4884                    );
4885                }
4886                if Self::node_is_function_named(node, "TRY_TO_NUMBER")
4887                    || Self::node_is_function_named(node, "TRY_TO_NUMERIC")
4888                    || Self::node_is_function_named(node, "TRY_TO_DECIMAL")
4889                    || matches!(node, Expression::ToNumber(to_number) if to_number.safe.is_some())
4890                {
4891                    Self::push_unsupported_diagnostic(
4892                        &mut diagnostics,
4893                        "Snowflake TRY_TO_NUMBER decimal conversion semantics",
4894                    );
4895                }
4896                if Self::node_is_function_named(node, "FLATTEN") {
4897                    Self::push_unsupported_diagnostic(
4898                        &mut diagnostics,
4899                        "Snowflake FLATTEN table-function semantics",
4900                    );
4901                }
4902            }
4903
4904            if matches!(target, DialectType::Fabric | DialectType::Hive)
4905                && Self::node_has_recursive_with(node)
4906            {
4907                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
4908            }
4909
4910            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4911                && Self::node_has_lateral(node)
4912            {
4913                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
4914            }
4915
4916            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4917                if Self::node_has_join_using(node) {
4918                    Self::push_unsupported_diagnostic(&mut diagnostics, "JOIN USING clauses");
4919                }
4920                if Self::node_has_natural_join(node) {
4921                    Self::push_unsupported_diagnostic(&mut diagnostics, "NATURAL JOIN");
4922                }
4923                if Self::node_has_unsupported_relation_column_aliases(node) {
4924                    Self::push_unsupported_diagnostic(
4925                        &mut diagnostics,
4926                        "column alias lists on base or joined table references",
4927                    );
4928                }
4929                if Self::node_has_qualified_whole_row_aggregate_argument(node) {
4930                    Self::push_unsupported_diagnostic(
4931                        &mut diagnostics,
4932                        "qualified whole-row aggregate arguments",
4933                    );
4934                }
4935                if Self::node_has_subquery_in_aggregate_argument(node) {
4936                    Self::push_unsupported_diagnostic(
4937                        &mut diagnostics,
4938                        "aggregate arguments containing subqueries",
4939                    );
4940                }
4941            }
4942
4943            if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
4944                Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
4945            }
4946
4947            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
4948                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
4949            }
4950
4951            if !Self::target_supports_remaining_explode(target)
4952                && Self::node_is_explode(node)
4953                && !(source == DialectType::Vertica && target == DialectType::Vertica)
4954            {
4955                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
4956            }
4957
4958            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
4959                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
4960            }
4961
4962            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4963                && Self::node_is_distinct_string_agg(node)
4964            {
4965                Self::push_unsupported_diagnostic(&mut diagnostics, "STRING_AGG with DISTINCT");
4966            }
4967
4968            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4969                && matches!(node, Expression::NthValue(_))
4970            {
4971                Self::push_unsupported_diagnostic(&mut diagnostics, "NTH_VALUE");
4972            }
4973
4974            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4975                if let Some(frame) = Self::node_window_frame(node) {
4976                    if matches!(frame.kind, WindowFrameKind::Groups) {
4977                        Self::push_unsupported_diagnostic(&mut diagnostics, "GROUPS window frames");
4978                    }
4979                    if matches!(frame.kind, WindowFrameKind::Range)
4980                        && (Self::window_frame_bound_has_value_offset(&frame.start)
4981                            || frame
4982                                .end
4983                                .as_ref()
4984                                .is_some_and(Self::window_frame_bound_has_value_offset))
4985                    {
4986                        Self::push_unsupported_diagnostic(
4987                            &mut diagnostics,
4988                            "value-offset RANGE window frames",
4989                        );
4990                    }
4991                    if frame.exclude.is_some() {
4992                        Self::push_unsupported_diagnostic(
4993                            &mut diagnostics,
4994                            "window frame EXCLUDE clauses",
4995                        );
4996                    }
4997                }
4998            }
4999
5000            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5001                && Self::node_is_regex_predicate(node)
5002            {
5003                Self::push_unsupported_diagnostic(
5004                    &mut diagnostics,
5005                    "regular expression predicates",
5006                );
5007            }
5008
5009            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5010                && Self::node_is_non_subquery_any(node)
5011            {
5012                Self::push_unsupported_diagnostic(
5013                    &mut diagnostics,
5014                    "ANY over non-subquery expressions",
5015                );
5016            }
5017
5018            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5019                && Self::node_is_row_value_subquery_comparison(node)
5020            {
5021                Self::push_unsupported_diagnostic(
5022                    &mut diagnostics,
5023                    "row-value subquery comparisons",
5024                );
5025            }
5026
5027            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5028                && Self::node_is_row_value_values_membership(node)
5029            {
5030                Self::push_unsupported_diagnostic(
5031                    &mut diagnostics,
5032                    "row-value VALUES membership comparisons",
5033                );
5034            }
5035
5036            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5037                && Self::node_has_fetch_with_ties(node)
5038            {
5039                Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
5040            }
5041
5042            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5043                && Self::node_is_overlaps(node)
5044            {
5045                Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
5046            }
5047
5048            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5049                && Self::node_is_date_bin(node)
5050            {
5051                Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
5052            }
5053
5054            if source == DialectType::PostgreSQL
5055                && matches!(target, DialectType::TSQL | DialectType::Fabric)
5056                && Self::node_is_unresolved_postgres_date_subtraction(node)
5057            {
5058                Self::push_unsupported_diagnostic(
5059                    &mut diagnostics,
5060                    "PostgreSQL date subtraction with an unresolved column type",
5061                );
5062            }
5063
5064            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5065                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
5066            {
5067                if Self::node_is_postgres_json_build_object(node)
5068                    && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
5069                        && Self::postgres_json_build_object_can_lower_to_json_object(node))
5070                {
5071                    Self::push_unsupported_diagnostic(
5072                        &mut diagnostics,
5073                        "PostgreSQL JSON_BUILD_OBJECT",
5074                    );
5075                }
5076                if Self::node_is_function_named(node, "TO_TSVECTOR") {
5077                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5078                }
5079                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5080                    if let Some(composite_semantics) =
5081                        Self::postgres_tsql_unsupported_composite_semantics(
5082                            node,
5083                            structural_grouping_tuples.contains(&(node as *const Expression)),
5084                        )
5085                    {
5086                        Self::push_unsupported_diagnostic(
5087                            &mut diagnostics,
5088                            &format!("PostgreSQL {composite_semantics}"),
5089                        );
5090                    }
5091                    if Self::node_is_postgres_unknown_cast(node) {
5092                        Self::push_unsupported_diagnostic(
5093                            &mut diagnostics,
5094                            "PostgreSQL unresolved UNKNOWN casts",
5095                        );
5096                    }
5097                    if let Some(collation_name) =
5098                        Self::postgres_tsql_unsupported_collation_name(node)
5099                    {
5100                        Self::push_unsupported_diagnostic(
5101                            &mut diagnostics,
5102                            &format!("PostgreSQL collation \"{collation_name}\""),
5103                        );
5104                    }
5105                    if let Some(array_semantics) =
5106                        Self::postgres_tsql_unsupported_array_semantics(node)
5107                    {
5108                        Self::push_unsupported_diagnostic(
5109                            &mut diagnostics,
5110                            &format!("PostgreSQL {array_semantics}"),
5111                        );
5112                    }
5113                    if let Some(string_semantics) =
5114                        Self::postgres_tsql_unsupported_string_semantics(node)
5115                    {
5116                        Self::push_unsupported_diagnostic(
5117                            &mut diagnostics,
5118                            &format!("PostgreSQL {string_semantics}"),
5119                        );
5120                    }
5121                    if source == DialectType::PostgreSQL {
5122                        if let Some(binary_semantics) =
5123                            Self::postgres_tsql_unsupported_binary_semantics(node)
5124                        {
5125                            Self::push_unsupported_diagnostic(
5126                                &mut diagnostics,
5127                                &format!("PostgreSQL {binary_semantics}"),
5128                            );
5129                        }
5130                    }
5131                    if let Some(function_name) =
5132                        Self::postgres_tsql_unsupported_function_name(node, target)
5133                    {
5134                        Self::push_unsupported_diagnostic(
5135                            &mut diagnostics,
5136                            &format!("PostgreSQL {function_name}"),
5137                        );
5138                    }
5139                }
5140                if matches!(target, DialectType::TSQL | DialectType::Fabric)
5141                    && Self::node_is_postgres_type_function_cast(node)
5142                {
5143                    Self::push_unsupported_diagnostic(
5144                        &mut diagnostics,
5145                        "PostgreSQL type-name function casts",
5146                    );
5147                }
5148            }
5149
5150            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5151                break;
5152            }
5153        }
5154
5155        if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5156            Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
5157            Self::collect_tsql_windows_missing_order(expr, &HashMap::new(), &mut diagnostics);
5158        }
5159
5160        if diagnostics.is_empty() {
5161            return Ok(());
5162        }
5163
5164        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5165            1
5166        } else {
5167            opts.max_unsupported.max(1)
5168        };
5169        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5170        if diagnostics.len() > limit {
5171            messages.push(format!("... and {} more", diagnostics.len() - limit));
5172        }
5173
5174        Err(crate::error::Error::unsupported(
5175            messages.join("; "),
5176            target.to_string(),
5177        ))
5178    }
5179
5180    fn reject_postgres_tsql_strict_regex_predicates(
5181        expr: &Expression,
5182        source: DialectType,
5183        target: DialectType,
5184        opts: &TranspileOptions,
5185    ) -> Result<()> {
5186        if !matches!(
5187            opts.unsupported_level,
5188            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5189        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5190            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5191        {
5192            return Ok(());
5193        }
5194
5195        if expr.dfs().any(Self::node_is_regex_predicate) {
5196            return Err(crate::error::Error::unsupported(
5197                "regular expression predicates",
5198                target.to_string(),
5199            ));
5200        }
5201
5202        Ok(())
5203    }
5204
5205    fn reject_tsql_strict_json_constructor_return_types(
5206        expr: &Expression,
5207        source: DialectType,
5208        target: DialectType,
5209        opts: &TranspileOptions,
5210    ) -> Result<()> {
5211        if !matches!(
5212            opts.unsupported_level,
5213            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5214        ) || source == target
5215            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5216        {
5217            return Ok(());
5218        }
5219
5220        let mut diagnostics = Vec::new();
5221        for node in expr.dfs() {
5222            if let Some(return_type) =
5223                normalization::unsupported_tsql_json_constructor_return_type(node)
5224            {
5225                let message =
5226                    format!("SQL/JSON constructor RETURNING {return_type} cannot be preserved");
5227                Self::push_unsupported_diagnostic(&mut diagnostics, &message);
5228                if opts.unsupported_level == UnsupportedLevel::Immediate {
5229                    break;
5230                }
5231            }
5232        }
5233
5234        if diagnostics.is_empty() {
5235            return Ok(());
5236        }
5237
5238        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5239            1
5240        } else {
5241            opts.max_unsupported.max(1)
5242        };
5243        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5244        if diagnostics.len() > limit {
5245            messages.push(format!("... and {} more", diagnostics.len() - limit));
5246        }
5247
5248        Err(crate::error::Error::unsupported(
5249            messages.join("; "),
5250            target.to_string(),
5251        ))
5252    }
5253
5254    fn reject_postgres_tsql_strict_json_aggregate_modifiers(
5255        expr: &Expression,
5256        source: DialectType,
5257        target: DialectType,
5258        opts: &TranspileOptions,
5259    ) -> Result<()> {
5260        if !matches!(
5261            opts.unsupported_level,
5262            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5263        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5264            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5265        {
5266            return Ok(());
5267        }
5268
5269        let mut diagnostics = Vec::new();
5270        for node in expr.dfs() {
5271            match node {
5272                Expression::Function(function)
5273                    if !function.quoted
5274                        && matches!(
5275                            function.name.to_ascii_uppercase().as_str(),
5276                            "JSON_AGG" | "JSONB_AGG"
5277                        ) =>
5278                {
5279                    let name = function.name.to_ascii_uppercase();
5280                    if function.args.len() != 1 {
5281                        Self::push_unsupported_diagnostic(
5282                            &mut diagnostics,
5283                            &format!("PostgreSQL {name} with invalid argument count"),
5284                        );
5285                    }
5286                    if function.distinct {
5287                        Self::push_unsupported_diagnostic(
5288                            &mut diagnostics,
5289                            &format!("PostgreSQL {name} with DISTINCT"),
5290                        );
5291                    }
5292                }
5293                Expression::AggregateFunction(function)
5294                    if matches!(
5295                        function.name.to_ascii_uppercase().as_str(),
5296                        "JSON_AGG" | "JSONB_AGG"
5297                    ) =>
5298                {
5299                    let name = function.name.to_ascii_uppercase();
5300                    if function.args.len() != 1 {
5301                        Self::push_unsupported_diagnostic(
5302                            &mut diagnostics,
5303                            &format!("PostgreSQL {name} with invalid argument count"),
5304                        );
5305                    }
5306                    if function.distinct {
5307                        Self::push_unsupported_diagnostic(
5308                            &mut diagnostics,
5309                            &format!("PostgreSQL {name} with DISTINCT"),
5310                        );
5311                    }
5312                    if function.filter.is_some() {
5313                        Self::push_unsupported_diagnostic(
5314                            &mut diagnostics,
5315                            &format!("PostgreSQL {name} with FILTER"),
5316                        );
5317                    }
5318                    if function.limit.is_some() || function.ignore_nulls.is_some() {
5319                        Self::push_unsupported_diagnostic(
5320                            &mut diagnostics,
5321                            &format!("PostgreSQL {name} with unsupported aggregate modifiers"),
5322                        );
5323                    }
5324                }
5325                Expression::Filter(filter) => {
5326                    if let Some(name) = Self::postgres_json_aggregate_name(&filter.this) {
5327                        Self::push_unsupported_diagnostic(
5328                            &mut diagnostics,
5329                            &format!("PostgreSQL {name} with FILTER"),
5330                        );
5331                    }
5332                }
5333                _ => {}
5334            }
5335
5336            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5337                break;
5338            }
5339        }
5340
5341        if diagnostics.is_empty() {
5342            return Ok(());
5343        }
5344
5345        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5346            1
5347        } else {
5348            opts.max_unsupported.max(1)
5349        };
5350        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5351        if diagnostics.len() > limit {
5352            messages.push(format!("... and {} more", diagnostics.len() - limit));
5353        }
5354
5355        Err(crate::error::Error::unsupported(
5356            messages.join("; "),
5357            target.to_string(),
5358        ))
5359    }
5360
5361    fn postgres_json_aggregate_name(expr: &Expression) -> Option<String> {
5362        let name = match expr {
5363            Expression::Function(function) if !function.quoted => &function.name,
5364            Expression::AggregateFunction(function) => &function.name,
5365            _ => return None,
5366        };
5367        let name = name.to_ascii_uppercase();
5368        matches!(name.as_str(), "JSON_AGG" | "JSONB_AGG").then_some(name)
5369    }
5370
5371    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5372        if !diagnostics.iter().any(|existing| existing == message) {
5373            diagnostics.push(message.to_string());
5374        }
5375    }
5376
5377    fn node_is_unresolved_postgres_date_subtraction(expr: &Expression) -> bool {
5378        let Expression::Sub(op) = expr else {
5379            return false;
5380        };
5381
5382        (Self::is_explicit_date_expr(&op.left) && Self::is_column_expr(&op.right))
5383            || (Self::is_column_expr(&op.left) && Self::is_explicit_date_expr(&op.right))
5384    }
5385
5386    fn is_column_expr(expr: &Expression) -> bool {
5387        match expr {
5388            Expression::Column(_) => true,
5389            Expression::Paren(paren) => Self::is_column_expr(&paren.this),
5390            _ => false,
5391        }
5392    }
5393
5394    fn node_window_frame(expr: &Expression) -> Option<&WindowFrame> {
5395        match expr {
5396            Expression::WindowFunction(window) => window.over.frame.as_ref(),
5397            Expression::Window(window) | Expression::WindowSpec(window) => window.frame.as_ref(),
5398            _ => None,
5399        }
5400    }
5401
5402    fn window_frame_bound_has_value_offset(bound: &WindowFrameBound) -> bool {
5403        matches!(
5404            bound,
5405            WindowFrameBound::Preceding(_)
5406                | WindowFrameBound::Following(_)
5407                | WindowFrameBound::Value(_)
5408                | WindowFrameBound::BarePreceding
5409                | WindowFrameBound::BareFollowing
5410        )
5411    }
5412
5413    fn collect_tsql_windows_missing_order(
5414        expr: &Expression,
5415        active_windows: &HashMap<String, Over>,
5416        diagnostics: &mut Vec<String>,
5417    ) {
5418        if let Expression::Select(select) = expr {
5419            let local_windows = select
5420                .windows
5421                .as_ref()
5422                .map(|windows| {
5423                    windows
5424                        .iter()
5425                        .map(|window| (window.name.name.to_ascii_lowercase(), window.spec.clone()))
5426                        .collect()
5427                })
5428                .unwrap_or_default();
5429
5430            for child in expr.children() {
5431                Self::collect_tsql_windows_missing_order(child, &local_windows, diagnostics);
5432            }
5433            return;
5434        }
5435
5436        if let Expression::WindowFunction(window) = expr {
5437            let (has_order, has_frame) = Self::effective_window_order_and_frame(
5438                &window.over,
5439                active_windows,
5440                &mut Vec::new(),
5441            );
5442
5443            if !has_order {
5444                if has_frame {
5445                    Self::push_unsupported_diagnostic(
5446                        diagnostics,
5447                        "window frames without ORDER BY",
5448                    );
5449                }
5450                if let Some(function_name) =
5451                    Self::tsql_window_function_requiring_order(&window.this)
5452                {
5453                    Self::push_unsupported_diagnostic(
5454                        diagnostics,
5455                        &format!("{function_name} without ORDER BY"),
5456                    );
5457                }
5458            }
5459        }
5460
5461        for child in expr.children() {
5462            Self::collect_tsql_windows_missing_order(child, active_windows, diagnostics);
5463        }
5464    }
5465
5466    fn effective_window_order_and_frame(
5467        over: &Over,
5468        active_windows: &HashMap<String, Over>,
5469        seen: &mut Vec<String>,
5470    ) -> (bool, bool) {
5471        let inherited = over
5472            .window_name
5473            .as_ref()
5474            .and_then(|name| {
5475                let key = name.name.to_ascii_lowercase();
5476                if seen.iter().any(|seen_name| seen_name == &key) {
5477                    return None;
5478                }
5479                let named = active_windows.get(&key)?;
5480                seen.push(key);
5481                let properties =
5482                    Self::effective_window_order_and_frame(named, active_windows, seen);
5483                seen.pop();
5484                Some(properties)
5485            })
5486            .unwrap_or((false, false));
5487
5488        (
5489            !over.order_by.is_empty() || inherited.0,
5490            over.frame.is_some() || inherited.1,
5491        )
5492    }
5493
5494    fn tsql_window_function_requiring_order(expr: &Expression) -> Option<&'static str> {
5495        match expr {
5496            Expression::FirstValue(_) => Some("FIRST_VALUE"),
5497            Expression::LastValue(_) => Some("LAST_VALUE"),
5498            Expression::Function(function) if function.name.eq_ignore_ascii_case("FIRST_VALUE") => {
5499                Some("FIRST_VALUE")
5500            }
5501            Expression::Function(function) if function.name.eq_ignore_ascii_case("LAST_VALUE") => {
5502                Some("LAST_VALUE")
5503            }
5504            _ => None,
5505        }
5506    }
5507
5508    fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5509        match expr {
5510            Expression::WindowFunction(window) => {
5511                if let Expression::WithinGroup(within_group) = &window.this {
5512                    if Self::within_group_is_hypothetical_set(within_group) {
5513                        Self::push_unsupported_diagnostic(
5514                            diagnostics,
5515                            "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5516                        );
5517                        return;
5518                    }
5519
5520                    if Self::within_group_is_mode(within_group) {
5521                        Self::push_unsupported_diagnostic(
5522                            diagnostics,
5523                            "MODE ordered-set aggregates",
5524                        );
5525                        return;
5526                    }
5527
5528                    if Self::within_group_is_percentile(within_group) {
5529                        if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5530                            Self::push_unsupported_diagnostic(
5531                                diagnostics,
5532                                "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5533                            );
5534                        }
5535                        return;
5536                    }
5537                }
5538            }
5539            Expression::WithinGroup(within_group) => {
5540                if Self::within_group_is_hypothetical_set(within_group) {
5541                    Self::push_unsupported_diagnostic(
5542                        diagnostics,
5543                        "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5544                    );
5545                    return;
5546                }
5547
5548                if Self::within_group_is_mode(within_group) {
5549                    Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5550                    return;
5551                }
5552
5553                if Self::within_group_is_percentile(within_group) {
5554                    Self::push_unsupported_diagnostic(
5555                        diagnostics,
5556                        "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5557                    );
5558                    return;
5559                }
5560            }
5561            _ => {}
5562        }
5563
5564        for child in expr.children() {
5565            Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5566        }
5567    }
5568
5569    fn within_group_is_hypothetical_set(within_group: &crate::expressions::WithinGroup) -> bool {
5570        match &within_group.this {
5571            Expression::Function(function) => Self::is_hypothetical_set_name(&function.name),
5572            Expression::AggregateFunction(function) => {
5573                Self::is_hypothetical_set_name(&function.name)
5574            }
5575            Expression::Rank(_)
5576            | Expression::DenseRank(_)
5577            | Expression::CumeDist(_)
5578            | Expression::PercentRank(_) => true,
5579            _ => false,
5580        }
5581    }
5582
5583    fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5584        match &within_group.this {
5585            Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5586            Expression::AggregateFunction(function) => {
5587                Self::is_percentile_ordered_set_name(&function.name)
5588            }
5589            Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5590            _ => false,
5591        }
5592    }
5593
5594    fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5595        match &within_group.this {
5596            Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5597            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5598            Expression::Mode(_) => true,
5599            _ => false,
5600        }
5601    }
5602
5603    fn is_percentile_ordered_set_name(name: &str) -> bool {
5604        name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5605    }
5606
5607    fn is_hypothetical_set_name(name: &str) -> bool {
5608        name.eq_ignore_ascii_case("RANK")
5609            || name.eq_ignore_ascii_case("DENSE_RANK")
5610            || name.eq_ignore_ascii_case("CUME_DIST")
5611            || name.eq_ignore_ascii_case("PERCENT_RANK")
5612    }
5613
5614    fn target_supports_distinct_on(target: DialectType) -> bool {
5615        matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5616    }
5617
5618    fn node_has_distinct_on(expr: &Expression) -> bool {
5619        matches!(
5620            expr,
5621            Expression::Select(select)
5622                if select
5623                    .distinct_on
5624                    .as_ref()
5625                    .is_some_and(|distinct_on| !distinct_on.is_empty())
5626        )
5627    }
5628
5629    fn node_has_recursive_with(expr: &Expression) -> bool {
5630        fn recursive(with: &Option<With>) -> bool {
5631            with.as_ref().is_some_and(|with| with.recursive)
5632        }
5633
5634        match expr {
5635            Expression::With(with) => with.recursive,
5636            Expression::Select(select) => recursive(&select.with),
5637            Expression::Union(union) => recursive(&union.with),
5638            Expression::Intersect(intersect) => recursive(&intersect.with),
5639            Expression::Except(except) => recursive(&except.with),
5640            Expression::Pivot(pivot) => recursive(&pivot.with),
5641            Expression::Insert(insert) => recursive(&insert.with),
5642            Expression::Update(update) => recursive(&update.with),
5643            Expression::Delete(delete) => recursive(&delete.with),
5644            _ => false,
5645        }
5646    }
5647
5648    fn node_has_lateral(expr: &Expression) -> bool {
5649        fn join_has_lateral(join: &Join) -> bool {
5650            matches!(
5651                join.kind,
5652                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5653            ) || Dialect::node_has_lateral(&join.this)
5654                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5655                || join
5656                    .match_condition
5657                    .as_ref()
5658                    .is_some_and(Dialect::node_has_lateral)
5659                || join.pivots.iter().any(Dialect::node_has_lateral)
5660        }
5661
5662        fn joins_have_lateral(joins: &[Join]) -> bool {
5663            joins.iter().any(join_has_lateral)
5664        }
5665
5666        match expr {
5667            Expression::Subquery(subquery) => {
5668                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5669            }
5670            Expression::Lateral(_) | Expression::LateralView(_) => true,
5671            Expression::Join(join) => join_has_lateral(join),
5672            Expression::Select(select) => {
5673                !select.lateral_views.is_empty()
5674                    || joins_have_lateral(&select.joins)
5675                    || select
5676                        .from
5677                        .as_ref()
5678                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5679            }
5680            Expression::JoinedTable(joined) => {
5681                !joined.lateral_views.is_empty()
5682                    || Dialect::node_has_lateral(&joined.left)
5683                    || joins_have_lateral(&joined.joins)
5684            }
5685            Expression::Update(update) => {
5686                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5687            }
5688            _ => false,
5689        }
5690    }
5691
5692    fn node_has_join_using(expr: &Expression) -> bool {
5693        fn has_using(joins: &[Join]) -> bool {
5694            joins.iter().any(|join| !join.using.is_empty())
5695        }
5696
5697        match expr {
5698            Expression::Join(join) => !join.using.is_empty(),
5699            Expression::Select(select) => has_using(&select.joins),
5700            Expression::JoinedTable(joined) => has_using(&joined.joins),
5701            Expression::Update(update) => {
5702                has_using(&update.table_joins) || has_using(&update.from_joins)
5703            }
5704            Expression::Delete(delete) => has_using(&delete.joins),
5705            _ => false,
5706        }
5707    }
5708
5709    fn node_has_natural_join(expr: &Expression) -> bool {
5710        fn is_natural(join: &Join) -> bool {
5711            matches!(
5712                join.kind,
5713                crate::expressions::JoinKind::Natural
5714                    | crate::expressions::JoinKind::NaturalLeft
5715                    | crate::expressions::JoinKind::NaturalRight
5716                    | crate::expressions::JoinKind::NaturalFull
5717            )
5718        }
5719
5720        fn has_natural(joins: &[Join]) -> bool {
5721            joins.iter().any(is_natural)
5722        }
5723
5724        match expr {
5725            Expression::Join(join) => is_natural(join),
5726            Expression::Select(select) => has_natural(&select.joins),
5727            Expression::JoinedTable(joined) => has_natural(&joined.joins),
5728            Expression::Update(update) => {
5729                has_natural(&update.table_joins) || has_natural(&update.from_joins)
5730            }
5731            Expression::Delete(delete) => has_natural(&delete.joins),
5732            _ => false,
5733        }
5734    }
5735
5736    fn node_has_unsupported_relation_column_aliases(expr: &Expression) -> bool {
5737        match expr {
5738            Expression::Table(table) => !table.column_aliases.is_empty(),
5739            Expression::Alias(alias) => {
5740                !alias.column_aliases.is_empty()
5741                    && matches!(
5742                        alias.this,
5743                        Expression::Table(_) | Expression::JoinedTable(_)
5744                    )
5745            }
5746            _ => false,
5747        }
5748    }
5749
5750    fn node_is_aggregate_function(expr: &Expression) -> bool {
5751        is_aggregate(expr)
5752    }
5753
5754    fn node_has_qualified_whole_row_aggregate_argument(expr: &Expression) -> bool {
5755        fn contains_qualified_star(expr: &Expression) -> bool {
5756            match expr {
5757                Expression::Star(star) => star.table.is_some(),
5758                // A star projected by an embedded query is not an argument of
5759                // the surrounding aggregate (for example, inside EXISTS).
5760                Expression::Select(_)
5761                | Expression::Subquery(_)
5762                | Expression::Union(_)
5763                | Expression::Intersect(_)
5764                | Expression::Except(_) => false,
5765                _ => expr.children().into_iter().any(contains_qualified_star),
5766            }
5767        }
5768
5769        Self::node_is_aggregate_function(expr)
5770            && expr.children().into_iter().any(contains_qualified_star)
5771    }
5772
5773    fn node_has_subquery_in_aggregate_argument(expr: &Expression) -> bool {
5774        fn contains_query(expr: &Expression) -> bool {
5775            matches!(
5776                expr,
5777                Expression::Select(_)
5778                    | Expression::Subquery(_)
5779                    | Expression::Union(_)
5780                    | Expression::Intersect(_)
5781                    | Expression::Except(_)
5782            ) || expr.children().into_iter().any(contains_query)
5783        }
5784
5785        Self::node_is_aggregate_function(expr) && expr.children().into_iter().any(contains_query)
5786    }
5787
5788    fn tsql_apply_has_invalid_outer_aggregate(expr: &Expression) -> bool {
5789        // SQL Server error 8124: if an aggregate expression contains an outer
5790        // reference, that reference must be the only column used by the expression.
5791        fn collect_source_names(expr: &Expression, names: &mut HashSet<String>) {
5792            let mut insert = |name: &Identifier| {
5793                if !name.name.is_empty() {
5794                    names.insert(name.name.to_ascii_lowercase());
5795                }
5796            };
5797
5798            match expr {
5799                Expression::Table(table) => {
5800                    insert(table.alias.as_ref().unwrap_or(&table.name));
5801                }
5802                Expression::Subquery(subquery) => {
5803                    if let Some(alias) = &subquery.alias {
5804                        insert(alias);
5805                    }
5806                }
5807                Expression::Alias(alias) => insert(&alias.alias),
5808                Expression::JoinedTable(joined) => {
5809                    if let Some(alias) = &joined.alias {
5810                        insert(alias);
5811                    } else {
5812                        collect_source_names(&joined.left, names);
5813                        for join in &joined.joins {
5814                            collect_source_names(&join.this, names);
5815                        }
5816                    }
5817                }
5818                Expression::Paren(paren) => collect_source_names(&paren.this, names),
5819                Expression::Pivot(pivot) => {
5820                    if let Some(alias) = &pivot.alias {
5821                        insert(alias);
5822                    } else {
5823                        collect_source_names(&pivot.this, names);
5824                    }
5825                }
5826                Expression::Unpivot(unpivot) => {
5827                    if let Some(alias) = &unpivot.alias {
5828                        insert(alias);
5829                    } else {
5830                        collect_source_names(&unpivot.this, names);
5831                    }
5832                }
5833                _ => {}
5834            }
5835        }
5836
5837        fn collect_columns<'a>(
5838            expr: &'a Expression,
5839            columns: &mut Vec<&'a crate::expressions::Column>,
5840        ) {
5841            match expr {
5842                Expression::Column(column) => columns.push(column),
5843                // Query expressions introduce a new name-resolution scope. Their
5844                // columns are checked when their own SELECT node is visited.
5845                Expression::Select(_)
5846                | Expression::Subquery(_)
5847                | Expression::Union(_)
5848                | Expression::Intersect(_)
5849                | Expression::Except(_) => {}
5850                _ => {
5851                    for child in expr.children() {
5852                        collect_columns(child, columns);
5853                    }
5854                }
5855            }
5856        }
5857
5858        fn same_column(
5859            left: &crate::expressions::Column,
5860            right: &crate::expressions::Column,
5861        ) -> bool {
5862            let same_identifier =
5863                |left: &Identifier, right: &Identifier| left.name.eq_ignore_ascii_case(&right.name);
5864
5865            same_identifier(&left.name, &right.name)
5866                && match (&left.table, &right.table) {
5867                    (Some(left), Some(right)) => same_identifier(left, right),
5868                    (None, None) => true,
5869                    _ => false,
5870                }
5871        }
5872
5873        fn aggregate_is_invalid(expr: &Expression, local_sources: &HashSet<String>) -> bool {
5874            let mut columns = Vec::new();
5875            collect_columns(expr, &mut columns);
5876
5877            let outer_column = columns.iter().copied().find(|column| match &column.table {
5878                Some(table) => !local_sources.contains(&table.name.to_ascii_lowercase()),
5879                // Without a local source, an unqualified reference in a lateral
5880                // query can only resolve against an outer scope.
5881                None => local_sources.is_empty(),
5882            });
5883
5884            outer_column
5885                .is_some_and(|outer| columns.iter().any(|column| !same_column(outer, column)))
5886        }
5887
5888        fn expression_has_invalid_aggregate(
5889            expr: &Expression,
5890            local_sources: &HashSet<String>,
5891        ) -> bool {
5892            if Dialect::node_is_aggregate_function(expr)
5893                && aggregate_is_invalid(expr, local_sources)
5894            {
5895                return true;
5896            }
5897
5898            match expr {
5899                Expression::Select(_)
5900                | Expression::Subquery(_)
5901                | Expression::Union(_)
5902                | Expression::Intersect(_)
5903                | Expression::Except(_) => false,
5904                _ => expr
5905                    .children()
5906                    .into_iter()
5907                    .any(|child| expression_has_invalid_aggregate(child, local_sources)),
5908            }
5909        }
5910
5911        fn select_has_invalid_aggregate(select: &Select) -> bool {
5912            let mut local_sources = HashSet::new();
5913            if let Some(from) = &select.from {
5914                for source in &from.expressions {
5915                    collect_source_names(source, &mut local_sources);
5916                }
5917            }
5918            for join in &select.joins {
5919                collect_source_names(&join.this, &mut local_sources);
5920            }
5921
5922            let invalid =
5923                |expr: &Expression| expression_has_invalid_aggregate(expr, &local_sources);
5924
5925            select.expressions.iter().any(invalid)
5926                || select.prewhere.as_ref().is_some_and(invalid)
5927                || select
5928                    .where_clause
5929                    .as_ref()
5930                    .is_some_and(|where_clause| invalid(&where_clause.this))
5931                || select
5932                    .group_by
5933                    .as_ref()
5934                    .is_some_and(|group_by| group_by.expressions.iter().any(invalid))
5935                || select
5936                    .having
5937                    .as_ref()
5938                    .is_some_and(|having| invalid(&having.this))
5939                || select
5940                    .qualify
5941                    .as_ref()
5942                    .is_some_and(|qualify| invalid(&qualify.this))
5943                || select.order_by.as_ref().is_some_and(|order_by| {
5944                    order_by
5945                        .expressions
5946                        .iter()
5947                        .any(|ordered| invalid(&ordered.this))
5948                })
5949        }
5950
5951        fn apply_rhs_is_invalid(rhs: &Expression) -> bool {
5952            rhs.dfs().any(|node| match node {
5953                Expression::Select(select) => select_has_invalid_aggregate(select),
5954                _ => false,
5955            })
5956        }
5957
5958        fn joins_have_invalid_aggregate(joins: &[Join]) -> bool {
5959            joins.iter().any(|join| {
5960                matches!(
5961                    join.kind,
5962                    crate::expressions::JoinKind::CrossApply
5963                        | crate::expressions::JoinKind::OuterApply
5964                ) && apply_rhs_is_invalid(&join.this)
5965            })
5966        }
5967
5968        expr.dfs().any(|node| match node {
5969            Expression::Select(select) => joins_have_invalid_aggregate(&select.joins),
5970            Expression::JoinedTable(joined) => joins_have_invalid_aggregate(&joined.joins),
5971            Expression::Update(update) => {
5972                joins_have_invalid_aggregate(&update.table_joins)
5973                    || joins_have_invalid_aggregate(&update.from_joins)
5974            }
5975            Expression::Delete(delete) => joins_have_invalid_aggregate(&delete.joins),
5976            _ => false,
5977        })
5978    }
5979
5980    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5981        matches!(
5982            target,
5983            DialectType::PostgreSQL
5984                | DialectType::BigQuery
5985                | DialectType::DuckDB
5986                | DialectType::Presto
5987                | DialectType::Trino
5988                | DialectType::Athena
5989        )
5990    }
5991
5992    fn target_supports_remaining_explode(target: DialectType) -> bool {
5993        matches!(
5994            target,
5995            DialectType::Spark | DialectType::Databricks | DialectType::Hive
5996        )
5997    }
5998
5999    fn target_lacks_array_agg(target: DialectType) -> bool {
6000        matches!(
6001            target,
6002            DialectType::Fabric
6003                | DialectType::TSQL
6004                | DialectType::MySQL
6005                | DialectType::SQLite
6006                | DialectType::Oracle
6007        )
6008    }
6009
6010    fn node_is_unnest(expr: &Expression) -> bool {
6011        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
6012    }
6013
6014    fn node_is_explode(expr: &Expression) -> bool {
6015        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
6016            || Self::node_is_function_named(expr, "EXPLODE")
6017            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
6018    }
6019
6020    fn node_is_array_agg(expr: &Expression) -> bool {
6021        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
6022    }
6023
6024    fn node_is_distinct_string_agg(expr: &Expression) -> bool {
6025        match expr {
6026            Expression::StringAgg(agg) => agg.distinct,
6027            Expression::Function(function) => {
6028                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6029            }
6030            Expression::AggregateFunction(function) => {
6031                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6032            }
6033            _ => false,
6034        }
6035    }
6036
6037    fn postgres_tsql_unsupported_collation_name(expr: &Expression) -> Option<&'static str> {
6038        let Expression::Collation(collation) = expr else {
6039            return None;
6040        };
6041
6042        if collation.collation.eq_ignore_ascii_case("C") {
6043            Some("C")
6044        } else if collation.collation.eq_ignore_ascii_case("POSIX") {
6045            Some("POSIX")
6046        } else {
6047            None
6048        }
6049    }
6050
6051    fn collect_tsql_grouping_tuple_nodes(expr: &Expression) -> HashSet<*const Expression> {
6052        let mut tuples = HashSet::new();
6053
6054        for node in expr.dfs() {
6055            let Expression::Select(select) = node else {
6056                continue;
6057            };
6058            let Some(group_by) = &select.group_by else {
6059                continue;
6060            };
6061
6062            for expression in &group_by.expressions {
6063                Self::collect_tsql_grouping_element_tuples(expression, &mut tuples);
6064            }
6065        }
6066
6067        tuples
6068    }
6069
6070    fn collect_tsql_grouping_element_tuples(
6071        expr: &Expression,
6072        tuples: &mut HashSet<*const Expression>,
6073    ) {
6074        match expr {
6075            Expression::GroupingSets(grouping_sets) => {
6076                for expression in &grouping_sets.expressions {
6077                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6078                }
6079            }
6080            Expression::Rollup(rollup) => {
6081                for expression in &rollup.expressions {
6082                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6083                }
6084            }
6085            Expression::Cube(cube) => {
6086                for expression in &cube.expressions {
6087                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6088                }
6089            }
6090            Expression::Function(function)
6091                if !function.quoted
6092                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6093                        || function.name.eq_ignore_ascii_case("ROLLUP")
6094                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6095            {
6096                for expression in &function.args {
6097                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6098                }
6099            }
6100            _ => {}
6101        }
6102    }
6103
6104    fn collect_tsql_grouping_unit_tuples(
6105        expr: &Expression,
6106        tuples: &mut HashSet<*const Expression>,
6107    ) {
6108        match expr {
6109            Expression::Tuple(tuple) => {
6110                tuples.insert(expr as *const Expression);
6111                for expression in &tuple.expressions {
6112                    match expression {
6113                        Expression::Tuple(_) | Expression::Paren(_) => {
6114                            Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6115                        }
6116                        Expression::GroupingSets(_)
6117                        | Expression::Rollup(_)
6118                        | Expression::Cube(_) => {
6119                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6120                        }
6121                        Expression::Function(function)
6122                            if !function.quoted
6123                                && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6124                                    || function.name.eq_ignore_ascii_case("ROLLUP")
6125                                    || function.name.eq_ignore_ascii_case("CUBE")) =>
6126                        {
6127                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6128                        }
6129                        _ => {}
6130                    }
6131                }
6132            }
6133            Expression::Paren(paren) => {
6134                Self::collect_tsql_grouping_unit_tuples(&paren.this, tuples);
6135            }
6136            Expression::GroupingSets(_) | Expression::Rollup(_) | Expression::Cube(_) => {
6137                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6138            }
6139            Expression::Function(function)
6140                if !function.quoted
6141                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6142                        || function.name.eq_ignore_ascii_case("ROLLUP")
6143                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6144            {
6145                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6146            }
6147            _ => {}
6148        }
6149    }
6150
6151    fn postgres_tsql_unsupported_composite_semantics(
6152        expr: &Expression,
6153        structural_grouping_tuple: bool,
6154    ) -> Option<&'static str> {
6155        match expr {
6156            Expression::Tuple(_) if !structural_grouping_tuple => Some("row/composite values"),
6157            Expression::Struct(_) | Expression::StructFunc(_) => Some("row/composite values"),
6158            Expression::Function(function)
6159                if !function.quoted && function.name.eq_ignore_ascii_case("ROW") =>
6160            {
6161                Some("row/composite values")
6162            }
6163            Expression::StructExtract(_) => Some("row/composite field access"),
6164            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) if matches!(&cast.this, Expression::Star(star) if star.table.is_some()) => {
6165                Some("qualified whole-row casts")
6166            }
6167            _ => None,
6168        }
6169    }
6170
6171    fn node_is_postgres_unknown_cast(expr: &Expression) -> bool {
6172        match expr {
6173            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
6174                normalization::is_postgres_unknown_type(&cast.to)
6175            }
6176            _ => false,
6177        }
6178    }
6179
6180    fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
6181        match expr {
6182            Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
6183            Expression::Subscript(_) => Some("array subscripts"),
6184            Expression::ArraySlice(_) => Some("array slices"),
6185            Expression::DataType(DataType::Array { .. }) => Some("array data types"),
6186            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
6187                if matches!(&cast.to, DataType::Array { .. }) =>
6188            {
6189                Some("array data types")
6190            }
6191            Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
6192            Expression::Cardinality(_) => Some("CARDINALITY"),
6193            Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
6194            Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
6195            Expression::ArrayContains(_)
6196            | Expression::ArrayPosition(_)
6197            | Expression::ArrayAppend(_)
6198            | Expression::ArrayPrepend(_)
6199            | Expression::ArrayConcat(_)
6200            | Expression::ArraySort(_)
6201            | Expression::ArrayReverse(_)
6202            | Expression::ArrayDistinct(_)
6203            | Expression::ArrayFilter(_)
6204            | Expression::ArrayTransform(_)
6205            | Expression::ArrayFlatten(_)
6206            | Expression::ArrayCompact(_)
6207            | Expression::ArrayIntersect(_)
6208            | Expression::ArrayUnion(_)
6209            | Expression::ArrayExcept(_)
6210            | Expression::ArrayRemove(_)
6211            | Expression::ArrayZip(_)
6212            | Expression::ArrayAll(_)
6213            | Expression::ArrayAny(_)
6214            | Expression::ArrayConstructCompact(_)
6215            | Expression::ArraySum(_) => Some("array functions"),
6216            Expression::ArrayContainsAll(_)
6217            | Expression::ArrayContainedBy(_)
6218            | Expression::ArrayOverlaps(_) => Some("array operators"),
6219            Expression::Function(function) => {
6220                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6221            }
6222            Expression::AggregateFunction(function) => {
6223                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6224            }
6225            _ => None,
6226        }
6227    }
6228
6229    fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
6230        if name.eq_ignore_ascii_case("ARRAY") {
6231            Some("array literals")
6232        } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
6233            || name.eq_ignore_ascii_case("ARRAY_SIZE")
6234        {
6235            Some("ARRAY_LENGTH")
6236        } else if name.eq_ignore_ascii_case("CARDINALITY") {
6237            Some("CARDINALITY")
6238        } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
6239            || name.eq_ignore_ascii_case("ARRAY_JOIN")
6240        {
6241            Some("ARRAY_TO_STRING")
6242        } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
6243            Some("STRING_TO_ARRAY")
6244        } else {
6245            None
6246        }
6247    }
6248
6249    fn node_is_regex_predicate(expr: &Expression) -> bool {
6250        matches!(
6251            expr,
6252            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
6253        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
6254            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
6255            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
6256    }
6257
6258    fn node_is_non_subquery_any(expr: &Expression) -> bool {
6259        matches!(
6260            expr,
6261            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
6262        )
6263    }
6264
6265    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
6266        match expr {
6267            Expression::Select(_) | Expression::Subquery(_) => true,
6268            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
6269            _ => false,
6270        }
6271    }
6272
6273    fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
6274        match expr {
6275            Expression::In(in_expr) => {
6276                Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
6277            }
6278            Expression::Eq(op) | Expression::Neq(op) => {
6279                (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
6280                    || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
6281            }
6282            _ => false,
6283        }
6284    }
6285
6286    fn node_is_row_value_values_membership(expr: &Expression) -> bool {
6287        matches!(
6288            expr,
6289            Expression::In(in_expr)
6290                if Self::expr_is_row_value(&in_expr.this)
6291                    && Self::in_rhs_is_values_like(in_expr)
6292        )
6293    }
6294
6295    fn expr_is_row_value(expr: &Expression) -> bool {
6296        match expr {
6297            Expression::Tuple(tuple) => tuple.expressions.len() > 1,
6298            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
6299                function.args.len() > 1
6300            }
6301            Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
6302            _ => false,
6303        }
6304    }
6305
6306    fn expr_is_subquery_like(expr: &Expression) -> bool {
6307        match expr {
6308            Expression::Select(_) | Expression::Subquery(_) => true,
6309            Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
6310            _ => false,
6311        }
6312    }
6313
6314    fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
6315        if in_expr
6316            .query
6317            .as_ref()
6318            .is_some_and(Self::expr_is_subquery_like)
6319        {
6320            return true;
6321        }
6322
6323        in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
6324    }
6325
6326    fn in_rhs_is_values_like(in_expr: &crate::expressions::In) -> bool {
6327        if in_expr
6328            .query
6329            .as_ref()
6330            .is_some_and(Self::expr_is_values_like)
6331        {
6332            return true;
6333        }
6334
6335        (in_expr.expressions.len() == 1
6336            && Self::expr_is_values_like(&in_expr.expressions[0]))
6337            || in_expr.expressions.first().is_some_and(|expr| {
6338                matches!(expr, Expression::Function(function) if function.name.eq_ignore_ascii_case("VALUES"))
6339            })
6340    }
6341
6342    fn expr_is_values_like(expr: &Expression) -> bool {
6343        match expr {
6344            Expression::Values(_) => true,
6345            Expression::Paren(paren) => Self::expr_is_values_like(&paren.this),
6346            Expression::Subquery(subquery) => Self::expr_is_values_like(&subquery.this),
6347            _ => false,
6348        }
6349    }
6350
6351    fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
6352        transform_recursive(expr, &|e| match e {
6353            Expression::Select(mut select) => {
6354                if select.top.is_none() && select.offset.is_none() {
6355                    if let Some(fetch) = select.fetch.take() {
6356                        if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
6357                            select.top = Some(top);
6358                        } else {
6359                            select.fetch = Some(fetch);
6360                        }
6361                    }
6362                }
6363                Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
6364                Ok(Expression::Select(select))
6365            }
6366            Expression::DateBin(date_bin) => {
6367                let date_bin = *date_bin;
6368                if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
6369                    Ok(rewritten)
6370                } else {
6371                    Ok(Expression::DateBin(Box::new(date_bin)))
6372                }
6373            }
6374            Expression::Function(function) => {
6375                let function = *function;
6376                if function.name.eq_ignore_ascii_case("DATE_BIN") {
6377                    if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
6378                        Ok(rewritten)
6379                    } else {
6380                        Ok(Expression::Function(Box::new(function)))
6381                    }
6382                } else {
6383                    Ok(Expression::Function(Box::new(function)))
6384                }
6385            }
6386            _ => Ok(e),
6387        })
6388    }
6389
6390    fn rewrite_tsql_overlaps_in_select_predicates(
6391        select: &mut crate::expressions::Select,
6392    ) -> Result<()> {
6393        if let Some(where_clause) = &mut select.where_clause {
6394            where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
6395        }
6396        if let Some(having) = &mut select.having {
6397            having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
6398        }
6399        if let Some(qualify) = &mut select.qualify {
6400            qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
6401        }
6402        for join in &mut select.joins {
6403            if let Some(on) = join.on.take() {
6404                join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
6405            }
6406            if let Some(match_condition) = join.match_condition.take() {
6407                join.match_condition =
6408                    Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
6409            }
6410        }
6411        Ok(())
6412    }
6413
6414    fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
6415        transform_recursive(expr, &|e| match e {
6416            Expression::Overlaps(overlaps) => {
6417                let overlaps = *overlaps;
6418                if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
6419                    Ok(rewritten)
6420                } else {
6421                    Ok(Expression::Overlaps(Box::new(overlaps)))
6422                }
6423            }
6424            _ => Ok(e),
6425        })
6426    }
6427
6428    fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
6429        if !fetch.with_ties {
6430            return None;
6431        }
6432
6433        fetch.count.map(|count| Top {
6434            this: count,
6435            percent: fetch.percent,
6436            with_ties: true,
6437            parenthesized: true,
6438        })
6439    }
6440
6441    fn rewrite_full_overlaps_for_tsql(
6442        overlaps: &crate::expressions::OverlapsExpr,
6443    ) -> Option<Expression> {
6444        let (left_start, left_end, right_start, right_end) =
6445            if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
6446                overlaps.left_start.as_ref(),
6447                overlaps.left_end.as_ref(),
6448                overlaps.right_start.as_ref(),
6449                overlaps.right_end.as_ref(),
6450            ) {
6451                (left_start, left_end, right_start, right_end)
6452            } else if let (
6453                Some(Expression::Tuple(left_tuple)),
6454                Some(Expression::Tuple(right_tuple)),
6455            ) = (&overlaps.this, &overlaps.expression)
6456            {
6457                if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
6458                    return None;
6459                }
6460                (
6461                    &left_tuple.expressions[0],
6462                    &left_tuple.expressions[1],
6463                    &right_tuple.expressions[0],
6464                    &right_tuple.expressions[1],
6465                )
6466            } else {
6467                return None;
6468            };
6469
6470        let left_min = Self::case_min(left_start.clone(), left_end.clone());
6471        let left_max = Self::case_max(left_start.clone(), left_end.clone());
6472        let right_min = Self::case_min(right_start.clone(), right_end.clone());
6473        let right_max = Self::case_max(right_start.clone(), right_end.clone());
6474
6475        Some(Expression::And(Box::new(BinaryOp::new(
6476            Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
6477            Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
6478        ))))
6479    }
6480
6481    fn case_min(left: Expression, right: Expression) -> Expression {
6482        Expression::Case(Box::new(Case {
6483            operand: None,
6484            whens: vec![(
6485                Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6486                left,
6487            )],
6488            else_: Some(right),
6489            comments: Vec::new(),
6490            inferred_type: None,
6491        }))
6492    }
6493
6494    fn case_max(left: Expression, right: Expression) -> Expression {
6495        Expression::Case(Box::new(Case {
6496            operand: None,
6497            whens: vec![(
6498                Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6499                left,
6500            )],
6501            else_: Some(right),
6502            comments: Vec::new(),
6503            inferred_type: None,
6504        }))
6505    }
6506
6507    fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
6508        if date_bin.unit.is_some() || date_bin.zone.is_some() {
6509            return None;
6510        }
6511
6512        let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
6513        let mut args = vec![
6514            Self::date_bucket_datepart(datepart),
6515            number,
6516            *date_bin.expression,
6517        ];
6518        if let Some(origin) = date_bin.origin {
6519            args.push(*origin);
6520        }
6521
6522        Some(Expression::Function(Box::new(Function::new(
6523            "DATE_BUCKET".to_string(),
6524            args,
6525        ))))
6526    }
6527
6528    fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
6529        if !(2..=3).contains(&function.args.len()) {
6530            return None;
6531        }
6532
6533        let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
6534        let mut args = vec![
6535            Self::date_bucket_datepart(datepart),
6536            number,
6537            function.args[1].clone(),
6538        ];
6539        if let Some(origin) = function.args.get(2) {
6540            args.push(origin.clone());
6541        }
6542
6543        Some(Expression::Function(Box::new(Function::new(
6544            "DATE_BUCKET".to_string(),
6545            args,
6546        ))))
6547    }
6548
6549    fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
6550        match stride {
6551            Expression::Literal(lit) => match lit.as_ref() {
6552                Literal::String(value) => Self::date_bucket_parts_from_string(value),
6553                _ => None,
6554            },
6555            Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
6556            _ => None,
6557        }
6558    }
6559
6560    fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
6561        match &interval.unit {
6562            Some(IntervalUnitSpec::Simple { unit, .. }) => {
6563                let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
6564                let amount = interval
6565                    .this
6566                    .as_ref()
6567                    .and_then(Self::date_bucket_amount_expr)?;
6568                Some((datepart, amount))
6569            }
6570            None => interval.this.as_ref().and_then(|expr| match expr {
6571                Expression::Literal(lit) => match lit.as_ref() {
6572                    Literal::String(value) => Self::date_bucket_parts_from_string(value),
6573                    _ => None,
6574                },
6575                _ => None,
6576            }),
6577            _ => None,
6578        }
6579    }
6580
6581    fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
6582        let mut parts = value.split_whitespace();
6583        let amount = parts.next()?;
6584        let unit = parts.next()?;
6585        if parts.next().is_some() {
6586            return None;
6587        }
6588
6589        Some((
6590            Self::date_bucket_datepart_from_name(unit)?,
6591            Self::positive_integer_expr(amount)?,
6592        ))
6593    }
6594
6595    fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
6596        match expr {
6597            Expression::Literal(lit) => match lit.as_ref() {
6598                Literal::Number(value) => Self::positive_integer_expr(value),
6599                Literal::String(value) => Self::positive_integer_expr(value),
6600                _ => None,
6601            },
6602            _ => Some(expr.clone()),
6603        }
6604    }
6605
6606    fn positive_integer_expr(value: &str) -> Option<Expression> {
6607        let parsed = value.trim().parse::<i64>().ok()?;
6608        (parsed > 0).then(|| Expression::number(parsed))
6609    }
6610
6611    fn date_bucket_datepart(datepart: &str) -> Expression {
6612        Expression::Var(Box::new(Var {
6613            this: datepart.to_string(),
6614        }))
6615    }
6616
6617    fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
6618        match unit {
6619            IntervalUnit::Week => Some("WEEK"),
6620            IntervalUnit::Day => Some("DAY"),
6621            IntervalUnit::Hour => Some("HOUR"),
6622            IntervalUnit::Minute => Some("MINUTE"),
6623            IntervalUnit::Second => Some("SECOND"),
6624            IntervalUnit::Millisecond => Some("MILLISECOND"),
6625            _ => None,
6626        }
6627    }
6628
6629    fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
6630        match unit.trim().to_ascii_uppercase().as_str() {
6631            "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
6632            "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
6633            "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
6634            "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
6635            "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
6636            "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
6637                Some("MILLISECOND")
6638            }
6639            _ => None,
6640        }
6641    }
6642
6643    fn node_has_fetch_with_ties(expr: &Expression) -> bool {
6644        matches!(
6645            expr,
6646            Expression::Select(select)
6647                if select
6648                    .fetch
6649                    .as_ref()
6650                    .is_some_and(|fetch| fetch.with_ties)
6651        )
6652    }
6653
6654    fn node_is_overlaps(expr: &Expression) -> bool {
6655        matches!(expr, Expression::Overlaps(_))
6656    }
6657
6658    fn node_is_date_bin(expr: &Expression) -> bool {
6659        matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
6660    }
6661
6662    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
6663        match expr {
6664            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
6665            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
6666            _ => false,
6667        }
6668    }
6669
6670    fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
6671        match expr {
6672            Expression::Function(function) => {
6673                function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6674                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
6675            }
6676            _ => false,
6677        }
6678    }
6679
6680    fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
6681        matches!(
6682            expr,
6683            Expression::Function(function)
6684                if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6685                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
6686                    && !function.distinct
6687                    && function.args.len() % 2 == 0
6688        )
6689    }
6690
6691    fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
6692        matches!(
6693            expr,
6694            Expression::Function(function)
6695                if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
6696                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
6697                    || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
6698                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
6699        )
6700    }
6701
6702    fn postgres_tsql_unsupported_function_name(
6703        expr: &Expression,
6704        target: DialectType,
6705    ) -> Option<&'static str> {
6706        match expr {
6707            Expression::Lpad(_) => Some("LPAD"),
6708            Expression::Rpad(_) => Some("RPAD"),
6709            Expression::SplitPart(_) => Some("SPLIT_PART"),
6710            Expression::Initcap(_) => Some("INITCAP"),
6711            Expression::RegexpReplace(_) => Some("REGEXP_REPLACE"),
6712            Expression::RegexpInstr(_) => Some("REGEXP_INSTR"),
6713            Expression::RegexpCount(_) => Some("REGEXP_COUNT"),
6714            Expression::RegexpSplit(_) => Some("REGEXP_SPLIT"),
6715            Expression::DecodeCase(_) => Some("DECODE"),
6716            Expression::ToJson(_) => Some("TO_JSON"),
6717            Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
6718            Expression::ToNumber(_) => Some("TO_NUMBER"),
6719            Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
6720            Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
6721            Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
6722            Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
6723            Expression::Corr(_) => Some("CORR"),
6724            Expression::CovarPop(_) => Some("COVAR_POP"),
6725            Expression::CovarSamp(_) => Some("COVAR_SAMP"),
6726            Expression::RegrAvgx(_) => Some("REGR_AVGX"),
6727            Expression::RegrAvgy(_) => Some("REGR_AVGY"),
6728            Expression::RegrCount(_) => Some("REGR_COUNT"),
6729            Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
6730            Expression::RegrR2(_) => Some("REGR_R2"),
6731            Expression::RegrSlope(_) => Some("REGR_SLOPE"),
6732            Expression::RegrSxx(_) => Some("REGR_SXX"),
6733            Expression::RegrSxy(_) => Some("REGR_SXY"),
6734            Expression::RegrSyy(_) => Some("REGR_SYY"),
6735            Expression::Function(function) => {
6736                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6737            }
6738            Expression::AggregateFunction(function) => {
6739                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6740            }
6741            _ => None,
6742        }
6743    }
6744
6745    fn postgres_tsql_unsupported_function_name_str(
6746        name: &str,
6747        target: DialectType,
6748    ) -> Option<&'static str> {
6749        if name.eq_ignore_ascii_case("LPAD") {
6750            Some("LPAD")
6751        } else if name.eq_ignore_ascii_case("RPAD") {
6752            Some("RPAD")
6753        } else if name.eq_ignore_ascii_case("SPLIT_PART") {
6754            Some("SPLIT_PART")
6755        } else if name.eq_ignore_ascii_case("INITCAP") {
6756            Some("INITCAP")
6757        } else if name.eq_ignore_ascii_case("TO_JSON") {
6758            Some("TO_JSON")
6759        } else if name.eq_ignore_ascii_case("TO_JSONB") {
6760            Some("TO_JSONB")
6761        } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
6762            Some("JSONB_OBJECT_AGG")
6763        } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
6764            Some("ROW_TO_JSON")
6765        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
6766            Some("JSON_ARRAY_ELEMENTS")
6767        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
6768            Some("JSONB_ARRAY_ELEMENTS")
6769        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
6770            Some("JSON_ARRAY_ELEMENTS_TEXT")
6771        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
6772            Some("JSONB_ARRAY_ELEMENTS_TEXT")
6773        } else if name.eq_ignore_ascii_case("ENCODE") {
6774            Some("ENCODE")
6775        } else if name.eq_ignore_ascii_case("DECODE") {
6776            Some("DECODE")
6777        } else if name.eq_ignore_ascii_case("REGEXP_REPLACE") {
6778            Some("REGEXP_REPLACE")
6779        } else if name.eq_ignore_ascii_case("REGEXP_COUNT") {
6780            Some("REGEXP_COUNT")
6781        } else if name.eq_ignore_ascii_case("REGEXP_INSTR") {
6782            Some("REGEXP_INSTR")
6783        } else if name.eq_ignore_ascii_case("REGEXP_SUBSTR") {
6784            Some("REGEXP_SUBSTR")
6785        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT") {
6786            Some("REGEXP_SPLIT")
6787        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_ARRAY") {
6788            Some("REGEXP_SPLIT_TO_ARRAY")
6789        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_TABLE") {
6790            Some("REGEXP_SPLIT_TO_TABLE")
6791        } else if name.eq_ignore_ascii_case("SHA224") {
6792            Some("SHA224")
6793        } else if name.eq_ignore_ascii_case("SHA384") {
6794            Some("SHA384")
6795        } else if name.eq_ignore_ascii_case("TO_BIN") {
6796            Some("TO_BIN")
6797        } else if name.eq_ignore_ascii_case("TO_OCT") {
6798            Some("TO_OCT")
6799        } else if target == DialectType::TSQL && name.eq_ignore_ascii_case("UNISTR") {
6800            Some("UNISTR")
6801        } else if name.eq_ignore_ascii_case("AGE") {
6802            Some("AGE")
6803        } else if name.eq_ignore_ascii_case("ERF") {
6804            Some("ERF")
6805        } else if name.eq_ignore_ascii_case("SINH") {
6806            Some("SINH")
6807        } else if name.eq_ignore_ascii_case("COSH") {
6808            Some("COSH")
6809        } else if name.eq_ignore_ascii_case("TANH") {
6810            Some("TANH")
6811        } else if name.eq_ignore_ascii_case("ASINH") {
6812            Some("ASINH")
6813        } else if name.eq_ignore_ascii_case("ACOSH") {
6814            Some("ACOSH")
6815        } else if name.eq_ignore_ascii_case("ATANH") {
6816            Some("ATANH")
6817        } else if name.eq_ignore_ascii_case("GCD") {
6818            Some("GCD")
6819        } else if name.eq_ignore_ascii_case("LCM") {
6820            Some("LCM")
6821        } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
6822            Some("QUOTE_LITERAL")
6823        } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
6824            Some("WIDTH_BUCKET")
6825        } else if name.eq_ignore_ascii_case("SCALE") {
6826            Some("SCALE")
6827        } else if name.eq_ignore_ascii_case("TRIM_SCALE") {
6828            Some("TRIM_SCALE")
6829        } else if name.eq_ignore_ascii_case("MIN_SCALE") {
6830            Some("MIN_SCALE")
6831        } else if name.eq_ignore_ascii_case("FACTORIAL") {
6832            Some("FACTORIAL")
6833        } else if name.eq_ignore_ascii_case("PG_LSN") {
6834            Some("PG_LSN")
6835        } else if name.eq_ignore_ascii_case("TO_CHAR") {
6836            Some("TO_CHAR")
6837        } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
6838            Some("PG_TYPEOF")
6839        } else if name.eq_ignore_ascii_case("BIT_AND") {
6840            Some("BIT_AND")
6841        } else if name.eq_ignore_ascii_case("BIT_OR") {
6842            Some("BIT_OR")
6843        } else if name.eq_ignore_ascii_case("BIT_XOR") {
6844            Some("BIT_XOR")
6845        } else if name.eq_ignore_ascii_case("CORR") {
6846            Some("CORR")
6847        } else if name.eq_ignore_ascii_case("COVAR_POP") {
6848            Some("COVAR_POP")
6849        } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
6850            Some("COVAR_SAMP")
6851        } else if name.eq_ignore_ascii_case("REGR_AVGX") {
6852            Some("REGR_AVGX")
6853        } else if name.eq_ignore_ascii_case("REGR_AVGY") {
6854            Some("REGR_AVGY")
6855        } else if name.eq_ignore_ascii_case("REGR_COUNT") {
6856            Some("REGR_COUNT")
6857        } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
6858            Some("REGR_INTERCEPT")
6859        } else if name.eq_ignore_ascii_case("REGR_R2") {
6860            Some("REGR_R2")
6861        } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
6862            Some("REGR_SLOPE")
6863        } else if name.eq_ignore_ascii_case("REGR_SXX") {
6864            Some("REGR_SXX")
6865        } else if name.eq_ignore_ascii_case("REGR_SXY") {
6866            Some("REGR_SXY")
6867        } else if name.eq_ignore_ascii_case("REGR_SYY") {
6868            Some("REGR_SYY")
6869        } else if name.eq_ignore_ascii_case("FLOAT8_ACCUM") {
6870            Some("FLOAT8_ACCUM")
6871        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_ACCUM") {
6872            Some("FLOAT8_REGR_ACCUM")
6873        } else if name.eq_ignore_ascii_case("FLOAT8_COMBINE") {
6874            Some("FLOAT8_COMBINE")
6875        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_COMBINE") {
6876            Some("FLOAT8_REGR_COMBINE")
6877        } else if name.eq_ignore_ascii_case("BOOLAND_STATEFUNC") {
6878            Some("BOOLAND_STATEFUNC")
6879        } else if name.eq_ignore_ascii_case("BOOLOR_STATEFUNC") {
6880            Some("BOOLOR_STATEFUNC")
6881        } else {
6882            None
6883        }
6884    }
6885
6886    fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
6887        transform_recursive(expr, &|e| match e {
6888            Expression::Trim(trim) => {
6889                let mut trim = *trim;
6890                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6891                match trim.position {
6892                    crate::expressions::TrimPosition::Both
6893                        if trim.position_explicit && trim.characters.is_some() =>
6894                    {
6895                        trim.position_explicit = false;
6896                        trim.sql_standard_syntax = true;
6897                        Ok(Expression::Trim(Box::new(trim)))
6898                    }
6899                    crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6900                        let characters = trim.characters.take().expect("checked above");
6901                        Ok(Expression::Function(Box::new(Function::new(
6902                            "LTRIM",
6903                            vec![trim.this, characters],
6904                        ))))
6905                    }
6906                    crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6907                        let characters = trim.characters.take().expect("checked above");
6908                        Ok(Expression::Function(Box::new(Function::new(
6909                            "RTRIM",
6910                            vec![trim.this, characters],
6911                        ))))
6912                    }
6913                    _ => Ok(Expression::Trim(Box::new(trim))),
6914                }
6915            }
6916            other => Ok(other),
6917        })
6918    }
6919
6920    fn normalize_postgres_string_semantics_for_tsql(expr: Expression) -> Result<Expression> {
6921        transform_recursive(expr, &|e| match e {
6922            Expression::Like(mut op) => {
6923                Self::recover_postgres_like_escape(&mut op);
6924                Ok(Expression::Like(op))
6925            }
6926            Expression::ILike(mut op) => {
6927                Self::recover_postgres_like_escape(&mut op);
6928                Ok(Expression::ILike(op))
6929            }
6930            Expression::Substring(mut substring)
6931                if substring.length.is_none()
6932                    && Self::is_explicitly_numeric_expression(&substring.start) =>
6933            {
6934                substring.length = Some(Expression::number(i32::MAX as i64));
6935                Ok(Expression::Substring(substring))
6936            }
6937            Expression::Trim(mut trim) => {
6938                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6939                Ok(Expression::Trim(trim))
6940            }
6941            Expression::Function(mut function)
6942                if !function.quoted
6943                    && matches!(
6944                        function.name.to_ascii_uppercase().as_str(),
6945                        "BTRIM" | "LTRIM" | "RTRIM"
6946                    )
6947                    && function.args.len() == 2 =>
6948            {
6949                function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
6950                Ok(Expression::Function(function))
6951            }
6952            Expression::Translate(translate) => {
6953                Ok(Self::normalize_postgres_translate_for_tsql(*translate))
6954            }
6955            Expression::Function(function)
6956                if !function.quoted
6957                    && function.name.eq_ignore_ascii_case("TRANSLATE")
6958                    && function.args.len() == 3 =>
6959            {
6960                Ok(Self::normalize_postgres_translate_function_for_tsql(
6961                    *function,
6962                ))
6963            }
6964            other => Ok(other),
6965        })
6966    }
6967
6968    fn normalize_postgres_bytea_literals_for_tsql(expr: Expression) -> Result<Expression> {
6969        transform_recursive(expr, &|e| match e {
6970            Expression::Cast(cast) if Self::is_postgres_bytea_data_type(&cast.to) => {
6971                let Some(value) = Self::postgres_plain_string_literal_value(&cast.this) else {
6972                    return Ok(Expression::Cast(cast));
6973                };
6974                let Some(hex) = Self::postgres_bytea_hex_payload(value) else {
6975                    return Ok(Expression::Cast(cast));
6976                };
6977
6978                // Replace the complete BYTEA cast. Keeping a bare T-SQL
6979                // CAST(... AS VARBINARY) would apply SQL Server's default length
6980                // and could truncate payloads longer than 30 bytes.
6981                Ok(Expression::Literal(Box::new(Literal::HexString(hex))))
6982            }
6983            other => Ok(other),
6984        })
6985    }
6986
6987    fn postgres_plain_string_literal_value(expr: &Expression) -> Option<&str> {
6988        match expr {
6989            Expression::Literal(literal) => match literal.as_ref() {
6990                Literal::String(value) => Some(value),
6991                _ => None,
6992            },
6993            Expression::Paren(paren) => Self::postgres_plain_string_literal_value(&paren.this),
6994            _ => None,
6995        }
6996    }
6997
6998    fn postgres_bytea_hex_payload(value: &str) -> Option<String> {
6999        let payload = value.strip_prefix("\\x")?;
7000        if payload.is_empty() {
7001            return Some(String::new());
7002        }
7003
7004        let mut chars = payload.chars().peekable();
7005        let mut hex = String::with_capacity(payload.len());
7006        loop {
7007            let high = chars.next()?;
7008            let low = chars.next()?;
7009            if !high.is_ascii_hexdigit() || !low.is_ascii_hexdigit() {
7010                return None;
7011            }
7012            hex.push(high);
7013            hex.push(low);
7014
7015            let Some(next) = chars.peek().copied() else {
7016                return Some(hex);
7017            };
7018            if next.is_ascii_whitespace() {
7019                while chars
7020                    .peek()
7021                    .is_some_and(|character| character.is_ascii_whitespace())
7022                {
7023                    chars.next();
7024                }
7025                // PostgreSQL permits whitespace between byte pairs, not after
7026                // the prefix or after the final pair.
7027                chars.peek()?;
7028            }
7029        }
7030    }
7031
7032    fn is_postgres_bytea_data_type(data_type: &DataType) -> bool {
7033        match data_type {
7034            DataType::VarBinary { length: None } => true,
7035            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("BYTEA"),
7036            _ => false,
7037        }
7038    }
7039
7040    fn postgres_tsql_unsupported_binary_semantics(expr: &Expression) -> Option<&'static str> {
7041        let cast = match expr {
7042            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7043                if Self::is_postgres_bytea_data_type(&cast.to) =>
7044            {
7045                cast
7046            }
7047            _ => return None,
7048        };
7049
7050        let literal = match &cast.this {
7051            Expression::Literal(literal) => literal.as_ref(),
7052            Expression::Paren(paren) => match &paren.this {
7053                Expression::Literal(literal) => literal.as_ref(),
7054                _ => return None,
7055            },
7056            _ => return None,
7057        };
7058        let value = match literal {
7059            Literal::String(value) | Literal::EscapeString(value) => value,
7060            _ => return None,
7061        };
7062
7063        if value.starts_with("\\x") {
7064            Some("bytea hex literals with invalid or unsupported formatting")
7065        } else if value.contains('\\') {
7066            Some("bytea escape-format literals")
7067        } else {
7068            None
7069        }
7070    }
7071
7072    fn recover_postgres_like_escape(op: &mut crate::expressions::LikeOp) {
7073        if op.escape.is_some() {
7074            return;
7075        }
7076
7077        let Expression::Function(function) = &op.right else {
7078            return;
7079        };
7080        if function.quoted
7081            || function.distinct
7082            || !function.name.eq_ignore_ascii_case("LIKE_ESCAPE")
7083            || function.args.len() != 2
7084        {
7085            return;
7086        }
7087
7088        let pattern = function.args[0].clone();
7089        let escape = function.args[1].clone();
7090        op.right = Self::strip_postgres_text_literal_cast(pattern);
7091        op.escape = Some(Self::strip_postgres_text_literal_cast(escape));
7092    }
7093
7094    fn normalize_postgres_translate_for_tsql(
7095        mut translate: crate::expressions::Translate,
7096    ) -> Expression {
7097        let (Some(from), Some(to)) = (&translate.from_, &translate.to) else {
7098            return Expression::Translate(Box::new(translate));
7099        };
7100
7101        let (Some(from_value), Some(to_value)) = (
7102            Self::postgres_text_literal_value(from),
7103            Self::postgres_text_literal_value(to),
7104        ) else {
7105            return Expression::Translate(Box::new(translate));
7106        };
7107        let from_value = from_value.to_string();
7108        let to_value = to_value.to_string();
7109
7110        if from_value.chars().count() > to_value.chars().count() {
7111            if let Some(input) = Self::postgres_text_literal_value(&translate.this) {
7112                return Expression::string(Self::translate_postgres_literal(
7113                    input,
7114                    &from_value,
7115                    &to_value,
7116                ));
7117            }
7118            return Expression::Translate(Box::new(translate));
7119        }
7120
7121        translate.from_ = Some(Box::new(Self::strip_postgres_text_literal_cast(
7122            *translate.from_.expect("checked above"),
7123        )));
7124        let normalized_to = if from_value.chars().count() < to_value.chars().count() {
7125            Expression::string(
7126                to_value
7127                    .chars()
7128                    .take(from_value.chars().count())
7129                    .collect::<String>(),
7130            )
7131        } else {
7132            Self::strip_postgres_text_literal_cast(*translate.to.expect("checked above"))
7133        };
7134        translate.to = Some(Box::new(normalized_to));
7135        Expression::Translate(Box::new(translate))
7136    }
7137
7138    fn normalize_postgres_translate_function_for_tsql(mut function: Function) -> Expression {
7139        let from = Self::postgres_text_literal_value(&function.args[1]);
7140        let to = Self::postgres_text_literal_value(&function.args[2]);
7141        let (Some(from), Some(to)) = (from, to) else {
7142            return Expression::Function(Box::new(function));
7143        };
7144        let from = from.to_string();
7145        let to = to.to_string();
7146
7147        if from.chars().count() > to.chars().count() {
7148            if let Some(input) = Self::postgres_text_literal_value(&function.args[0]) {
7149                return Expression::string(Self::translate_postgres_literal(input, &from, &to));
7150            }
7151            return Expression::Function(Box::new(function));
7152        }
7153
7154        function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
7155        function.args[2] = if from.chars().count() < to.chars().count() {
7156            Expression::string(to.chars().take(from.chars().count()).collect::<String>())
7157        } else {
7158            Self::strip_postgres_text_literal_cast(function.args[2].clone())
7159        };
7160        Expression::Function(Box::new(function))
7161    }
7162
7163    fn translate_postgres_literal(input: &str, from: &str, to: &str) -> String {
7164        let from = from.chars().collect::<Vec<_>>();
7165        let to = to.chars().collect::<Vec<_>>();
7166        let mut output = String::with_capacity(input.len());
7167
7168        for ch in input.chars() {
7169            match from.iter().position(|candidate| *candidate == ch) {
7170                Some(index) if index < to.len() => output.push(to[index]),
7171                Some(_) => {}
7172                None => output.push(ch),
7173            }
7174        }
7175
7176        output
7177    }
7178
7179    fn postgres_tsql_unsupported_string_semantics(expr: &Expression) -> Option<&'static str> {
7180        match expr {
7181            Expression::Substring(substring) if substring.length.is_none() => {
7182                if Self::postgres_text_literal_value(&substring.start).is_some() {
7183                    Some("regular-expression SUBSTRING")
7184                } else {
7185                    Some("SUBSTRING without a statically numeric start position")
7186                }
7187            }
7188            Expression::Translate(translate) => {
7189                let from = translate
7190                    .from_
7191                    .as_deref()
7192                    .and_then(Self::postgres_text_literal_value);
7193                let to = translate
7194                    .to
7195                    .as_deref()
7196                    .and_then(Self::postgres_text_literal_value);
7197                match (from, to) {
7198                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7199                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7200                }
7201            }
7202            Expression::Function(function)
7203                if !function.quoted && function.name.eq_ignore_ascii_case("LIKE_ESCAPE") =>
7204            {
7205                Some("LIKE_ESCAPE helper outside a LIKE predicate")
7206            }
7207            Expression::Function(function)
7208                if !function.quoted
7209                    && function.name.eq_ignore_ascii_case("TRANSLATE")
7210                    && function.args.len() == 3 =>
7211            {
7212                let from = Self::postgres_text_literal_value(&function.args[1]);
7213                let to = Self::postgres_text_literal_value(&function.args[2]);
7214                match (from, to) {
7215                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7216                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7217                }
7218            }
7219            Expression::Trim(trim)
7220                if trim
7221                    .characters
7222                    .as_ref()
7223                    .is_some_and(Self::is_unbounded_text_cast) =>
7224            {
7225                Some("TRIM character set cast to an unbounded text type")
7226            }
7227            Expression::Function(function)
7228                if !function.quoted
7229                    && matches!(
7230                        function.name.to_ascii_uppercase().as_str(),
7231                        "LTRIM" | "RTRIM"
7232                    )
7233                    && function.args.len() == 2
7234                    && Self::is_unbounded_text_cast(&function.args[1]) =>
7235            {
7236                Some("TRIM character set cast to an unbounded text type")
7237            }
7238            _ => None,
7239        }
7240    }
7241
7242    fn strip_postgres_text_literal_cast(expr: Expression) -> Expression {
7243        match expr {
7244            Expression::Cast(cast)
7245                if Self::is_text_data_type(&cast.to)
7246                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7247            {
7248                Self::strip_postgres_text_literal_cast(cast.this)
7249            }
7250            Expression::TryCast(cast)
7251                if Self::is_text_data_type(&cast.to)
7252                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7253            {
7254                Self::strip_postgres_text_literal_cast(cast.this)
7255            }
7256            Expression::SafeCast(cast)
7257                if Self::is_text_data_type(&cast.to)
7258                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7259            {
7260                Self::strip_postgres_text_literal_cast(cast.this)
7261            }
7262            Expression::Paren(mut paren)
7263                if Self::postgres_text_literal_value(&paren.this).is_some() =>
7264            {
7265                paren.this = Self::strip_postgres_text_literal_cast(paren.this);
7266                Expression::Paren(paren)
7267            }
7268            other => other,
7269        }
7270    }
7271
7272    fn postgres_text_literal_value(expr: &Expression) -> Option<&str> {
7273        match expr {
7274            Expression::Literal(literal) if literal.is_string() => Some(literal.value_str()),
7275            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7276                if Self::is_text_data_type(&cast.to) =>
7277            {
7278                Self::postgres_text_literal_value(&cast.this)
7279            }
7280            Expression::Alias(alias) => Self::postgres_text_literal_value(&alias.this),
7281            Expression::Paren(paren) => Self::postgres_text_literal_value(&paren.this),
7282            _ => None,
7283        }
7284    }
7285
7286    fn is_text_data_type(data_type: &DataType) -> bool {
7287        match data_type {
7288            DataType::Char { .. }
7289            | DataType::VarChar { .. }
7290            | DataType::String { .. }
7291            | DataType::Text
7292            | DataType::TextWithLength { .. } => true,
7293            DataType::Custom { name } => {
7294                let base = name
7295                    .split_once('(')
7296                    .map_or(name.as_str(), |(base, _)| base)
7297                    .trim();
7298                matches!(
7299                    base.to_ascii_uppercase().as_str(),
7300                    "CHAR"
7301                        | "NCHAR"
7302                        | "VARCHAR"
7303                        | "NVARCHAR"
7304                        | "TEXT"
7305                        | "NTEXT"
7306                        | "STRING"
7307                        | "CHARACTER VARYING"
7308                )
7309            }
7310            _ => false,
7311        }
7312    }
7313
7314    fn is_unbounded_text_cast(expr: &Expression) -> bool {
7315        let data_type = match expr {
7316            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7317                &cast.to
7318            }
7319            Expression::Paren(paren) => return Self::is_unbounded_text_cast(&paren.this),
7320            _ => return false,
7321        };
7322
7323        match data_type {
7324            DataType::Text => true,
7325            DataType::VarChar { length: None, .. } | DataType::String { length: None } => true,
7326            DataType::Custom { name } => name.to_ascii_uppercase().contains("(MAX)"),
7327            _ => false,
7328        }
7329    }
7330
7331    fn is_explicitly_numeric_expression(expr: &Expression) -> bool {
7332        if expr.inferred_type().is_some_and(Self::is_numeric_data_type) {
7333            return true;
7334        }
7335
7336        match expr {
7337            Expression::Literal(literal) => literal.is_number(),
7338            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7339                Self::is_numeric_data_type(&cast.to)
7340            }
7341            Expression::Alias(alias) => Self::is_explicitly_numeric_expression(&alias.this),
7342            Expression::Paren(paren) => Self::is_explicitly_numeric_expression(&paren.this),
7343            Expression::Neg(unary) => Self::is_explicitly_numeric_expression(&unary.this),
7344            _ => false,
7345        }
7346    }
7347
7348    fn is_numeric_data_type(data_type: &DataType) -> bool {
7349        match data_type {
7350            DataType::TinyInt { .. }
7351            | DataType::SmallInt { .. }
7352            | DataType::Int { .. }
7353            | DataType::BigInt { .. }
7354            | DataType::Float { .. }
7355            | DataType::Double { .. }
7356            | DataType::Decimal { .. } => true,
7357            DataType::Custom { name } => {
7358                let base = name
7359                    .split_once('(')
7360                    .map_or(name.as_str(), |(base, _)| base)
7361                    .trim();
7362                matches!(
7363                    base.to_ascii_uppercase().as_str(),
7364                    "TINYINT"
7365                        | "SMALLINT"
7366                        | "INT"
7367                        | "INTEGER"
7368                        | "BIGINT"
7369                        | "DECIMAL"
7370                        | "NUMERIC"
7371                        | "REAL"
7372                        | "FLOAT"
7373                        | "MONEY"
7374                        | "SMALLMONEY"
7375                )
7376            }
7377            _ => false,
7378        }
7379    }
7380
7381    fn normalize_postgres_only_for_tsql(expr: Expression) -> Result<Expression> {
7382        transform_recursive(expr, &|e| match e {
7383            Expression::Table(mut table) if table.only => {
7384                table.only = false;
7385                Ok(Expression::Table(table))
7386            }
7387            other => Ok(other),
7388        })
7389    }
7390
7391    fn rewrite_postgres_json_array_elements_select_for_tsql(
7392        expr: Expression,
7393    ) -> Result<Expression> {
7394        let Expression::Select(select) = expr else {
7395            return Ok(expr);
7396        };
7397        let mut select = *select;
7398        if !Self::is_plain_single_projection_select(&select) {
7399            return Ok(Expression::Select(Box::new(select)));
7400        }
7401
7402        let Some(json_arg) =
7403            Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
7404        else {
7405            return Ok(Expression::Select(Box::new(select)));
7406        };
7407
7408        select.expressions = vec![Expression::column("value")];
7409        select.from = Some(From {
7410            expressions: vec![Expression::OpenJSON(Box::new(
7411                crate::expressions::OpenJSON {
7412                    this: Box::new(json_arg),
7413                    path: None,
7414                    expressions: Vec::new(),
7415                },
7416            ))],
7417        });
7418
7419        Ok(Expression::Select(Box::new(select)))
7420    }
7421
7422    fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
7423        select.expressions.len() == 1
7424            && select.from.is_none()
7425            && select.joins.is_empty()
7426            && select.lateral_views.is_empty()
7427            && select.prewhere.is_none()
7428            && select.where_clause.is_none()
7429            && select.group_by.is_none()
7430            && select.having.is_none()
7431            && select.qualify.is_none()
7432            && select.order_by.is_none()
7433            && select.distribute_by.is_none()
7434            && select.cluster_by.is_none()
7435            && select.sort_by.is_none()
7436            && select.limit.is_none()
7437            && select.offset.is_none()
7438            && select.limit_by.is_none()
7439            && select.fetch.is_none()
7440            && !select.distinct
7441            && select.distinct_on.is_none()
7442            && select.top.is_none()
7443            && select.with.is_none()
7444            && select.sample.is_none()
7445            && select.into.is_none()
7446            && select.locks.is_empty()
7447            && select.for_xml.is_empty()
7448            && select.for_json.is_empty()
7449            && select.exclude.is_none()
7450    }
7451
7452    fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
7453        match expr {
7454            Expression::Function(function)
7455                if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
7456            {
7457                Some(function.args[0].clone())
7458            }
7459            Expression::Alias(alias) => {
7460                Self::postgres_json_array_elements_projection_arg(&alias.this)
7461            }
7462            _ => None,
7463        }
7464    }
7465
7466    fn normalize_postgres_type_function_casts(
7467        expr: Expression,
7468        target: DialectType,
7469    ) -> Result<Expression> {
7470        transform_recursive(expr, &|e| match e {
7471            Expression::Function(function) => {
7472                let mut function = *function;
7473                if function.args.len() == 1
7474                    && !function.distinct
7475                    && !function.quoted
7476                    && !function.use_bracket_syntax
7477                    && !function.name.contains('.')
7478                {
7479                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
7480                        let this = function.args.remove(0);
7481                        let cast = Cast {
7482                            this,
7483                            to,
7484                            trailing_comments: function.trailing_comments,
7485                            double_colon_syntax: false,
7486                            format: None,
7487                            default: None,
7488                            inferred_type: function.inferred_type,
7489                        };
7490                        return Ok(
7491                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
7492                                normalization::rewrite_postgres_float_to_integer_cast(cast)
7493                            } else {
7494                                Expression::Cast(Box::new(cast))
7495                            },
7496                        );
7497                    }
7498                }
7499                Ok(Expression::Function(Box::new(function)))
7500            }
7501            _ => Ok(e),
7502        })
7503    }
7504
7505    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
7506        matches!(
7507            expr,
7508            Expression::Function(function)
7509                if !function.quoted
7510                    && !function.use_bracket_syntax
7511                    && !function.name.contains('.')
7512                    && Self::postgres_type_function_data_type(&function.name).is_some()
7513        )
7514    }
7515
7516    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
7517        match name.to_ascii_uppercase().as_str() {
7518            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
7519                precision: None,
7520                scale: None,
7521            }),
7522            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
7523            "INT4" | "INT" => Some(DataType::Int {
7524                length: None,
7525                integer_spelling: false,
7526            }),
7527            "INTEGER" => Some(DataType::Int {
7528                length: None,
7529                integer_spelling: true,
7530            }),
7531            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
7532            "FLOAT4" | "REAL" => Some(DataType::Float {
7533                precision: None,
7534                scale: None,
7535                real_spelling: true,
7536            }),
7537            "FLOAT8" => Some(DataType::Double {
7538                precision: None,
7539                scale: None,
7540            }),
7541            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
7542            "TEXT" => Some(DataType::Text),
7543            "VARCHAR" => Some(DataType::VarChar {
7544                length: None,
7545                parenthesized_length: false,
7546            }),
7547            "UUID" => Some(DataType::Uuid),
7548            _ => None,
7549        }
7550    }
7551
7552    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
7553        match expr {
7554            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7555            Expression::Subquery(mut subquery) => {
7556                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7557                Ok(Expression::Subquery(subquery))
7558            }
7559            Expression::Union(mut union) => {
7560                let left = std::mem::replace(&mut union.left, Expression::null());
7561                let right = std::mem::replace(&mut union.right, Expression::null());
7562                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
7563                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
7564                if let Some(mut with) = union.with.take() {
7565                    with.ctes = with
7566                        .ctes
7567                        .into_iter()
7568                        .map(|mut cte| {
7569                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7570                            Ok(cte)
7571                        })
7572                        .collect::<Result<Vec<_>>>()?;
7573                    union.with = Some(with);
7574                }
7575                Ok(Expression::Union(union))
7576            }
7577            Expression::Intersect(mut intersect) => {
7578                let left = std::mem::replace(&mut intersect.left, Expression::null());
7579                let right = std::mem::replace(&mut intersect.right, Expression::null());
7580                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
7581                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
7582                Ok(Expression::Intersect(intersect))
7583            }
7584            Expression::Except(mut except) => {
7585                let left = std::mem::replace(&mut except.left, Expression::null());
7586                let right = std::mem::replace(&mut except.right, Expression::null());
7587                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
7588                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
7589                Ok(Expression::Except(except))
7590            }
7591            other => Self::rewrite_tsql_boolean_nested_contexts(other),
7592        }
7593    }
7594
7595    fn rewrite_postgres_row_value_equality_for_tsql(expr: Expression) -> Result<Expression> {
7596        transform_recursive(expr, &|e| match e {
7597            Expression::Eq(op) => {
7598                let op = *op;
7599                Ok(Self::postgres_row_value_equality_to_tsql_scalar(&op)
7600                    .unwrap_or_else(|| Expression::Eq(Box::new(op))))
7601            }
7602            other => Ok(other),
7603        })
7604    }
7605
7606    fn postgres_row_value_equality_to_tsql_scalar(op: &BinaryOp) -> Option<Expression> {
7607        let (row, query) =
7608            if Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right) {
7609                (&op.left, &op.right)
7610            } else if Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left) {
7611                (&op.right, &op.left)
7612            } else {
7613                return None;
7614            };
7615
7616        let row_values = Self::row_value_expressions(row)?;
7617        let projection_count = Self::subquery_projection_count(query)?;
7618        if row_values.is_empty() || row_values.len() != projection_count {
7619            return None;
7620        }
7621
7622        // Keep the complete original query behind a derived table. The outer scalar
7623        // SELECT therefore returns the same number of rows as the PostgreSQL
7624        // single-row subquery: zero rows stay NULL and multiple rows still raise a
7625        // scalar-subquery cardinality error in T-SQL/Fabric.
7626        let mut taken_names = HashSet::new();
7627        Self::collect_generated_alias_conflicts(row, &mut taken_names);
7628        Self::collect_generated_alias_conflicts(query, &mut taken_names);
7629
7630        let source_alias = find_new_name(&taken_names, "_polyglot_row");
7631        taken_names.insert(source_alias.to_ascii_lowercase());
7632        let column_aliases = (1..=row_values.len())
7633            .map(|index| {
7634                let name = find_new_name(&taken_names, &format!("_polyglot_row_value_{index}"));
7635                taken_names.insert(name.to_ascii_lowercase());
7636                Identifier::new(name)
7637            })
7638            .collect::<Vec<_>>();
7639        let source = Self::subquery_as_derived_table(
7640            query,
7641            Identifier::new(&source_alias),
7642            column_aliases.clone(),
7643        )?;
7644
7645        let mut equal_components = Vec::with_capacity(row_values.len());
7646        let mut unequal_components = Vec::with_capacity(row_values.len());
7647        for (column, row_value) in column_aliases.into_iter().zip(row_values) {
7648            let projected = Expression::qualified_column(source_alias.clone(), column.name);
7649            equal_components.push(Expression::Eq(Box::new(BinaryOp::new(
7650                projected.clone(),
7651                row_value.clone(),
7652            ))));
7653            unequal_components.push(Expression::Neq(Box::new(BinaryOp::new(
7654                projected, row_value,
7655            ))));
7656        }
7657
7658        let all_equal = equal_components
7659            .into_iter()
7660            .reduce(|left, right| Expression::And(Box::new(BinaryOp::new(left, right))))?;
7661        let any_unequal = unequal_components
7662            .into_iter()
7663            .reduce(|left, right| Expression::Or(Box::new(BinaryOp::new(left, right))))?;
7664        let comparison = Expression::Case(Box::new(Case {
7665            operand: None,
7666            whens: vec![
7667                (all_equal, Expression::number(1)),
7668                (any_unequal, Expression::number(0)),
7669            ],
7670            else_: Some(Expression::null()),
7671            comments: Vec::new(),
7672            inferred_type: None,
7673        }));
7674
7675        let scalar_select = Select::new().column(comparison).from(source);
7676        let scalar_subquery = Expression::Subquery(Box::new(Subquery {
7677            this: Expression::Select(Box::new(scalar_select)),
7678            alias: None,
7679            column_aliases: Vec::new(),
7680            alias_explicit_as: false,
7681            alias_keyword: None,
7682            order_by: None,
7683            limit: None,
7684            offset: None,
7685            distribute_by: None,
7686            sort_by: None,
7687            cluster_by: None,
7688            lateral: false,
7689            modifiers_inside: false,
7690            trailing_comments: Vec::new(),
7691            inferred_type: Some(DataType::Boolean),
7692        }));
7693
7694        Some(Expression::Cast(Box::new(Cast {
7695            this: scalar_subquery,
7696            to: DataType::Boolean,
7697            trailing_comments: Vec::new(),
7698            double_colon_syntax: false,
7699            format: None,
7700            default: None,
7701            inferred_type: Some(DataType::Boolean),
7702        })))
7703    }
7704
7705    fn row_value_expressions(expr: &Expression) -> Option<Vec<Expression>> {
7706        match expr {
7707            Expression::Tuple(tuple) => Some(tuple.expressions.clone()),
7708            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
7709                Some(function.args.clone())
7710            }
7711            Expression::Paren(paren) => Self::row_value_expressions(&paren.this),
7712            _ => None,
7713        }
7714    }
7715
7716    fn subquery_projection_count(expr: &Expression) -> Option<usize> {
7717        match expr {
7718            Expression::Select(select) => Some(select.expressions.len()),
7719            Expression::Subquery(subquery) => Self::subquery_projection_count(&subquery.this),
7720            Expression::Paren(paren) => Self::subquery_projection_count(&paren.this),
7721            _ => None,
7722        }
7723    }
7724
7725    fn subquery_as_derived_table(
7726        expr: &Expression,
7727        alias: Identifier,
7728        column_aliases: Vec<Identifier>,
7729    ) -> Option<Expression> {
7730        match expr.clone() {
7731            Expression::Subquery(mut subquery) => {
7732                subquery.alias = Some(alias);
7733                subquery.column_aliases = column_aliases;
7734                subquery.alias_explicit_as = true;
7735                subquery.alias_keyword = None;
7736                Some(Expression::Subquery(subquery))
7737            }
7738            Expression::Select(_) | Expression::Paren(_) => {
7739                Some(Expression::Subquery(Box::new(Subquery {
7740                    this: expr.clone(),
7741                    alias: Some(alias),
7742                    column_aliases,
7743                    alias_explicit_as: true,
7744                    alias_keyword: None,
7745                    order_by: None,
7746                    limit: None,
7747                    offset: None,
7748                    distribute_by: None,
7749                    sort_by: None,
7750                    cluster_by: None,
7751                    lateral: false,
7752                    modifiers_inside: false,
7753                    trailing_comments: Vec::new(),
7754                    inferred_type: None,
7755                })))
7756            }
7757            _ => None,
7758        }
7759    }
7760
7761    fn collect_generated_alias_conflicts(expr: &Expression, names: &mut HashSet<String>) {
7762        fn insert(names: &mut HashSet<String>, identifier: &Identifier) {
7763            if !identifier.name.is_empty() {
7764                names.insert(identifier.name.to_ascii_lowercase());
7765            }
7766        }
7767
7768        for node in expr.dfs() {
7769            match node {
7770                Expression::Identifier(identifier) => insert(names, identifier),
7771                Expression::Column(column) => {
7772                    insert(names, &column.name);
7773                    if let Some(table) = &column.table {
7774                        insert(names, table);
7775                    }
7776                }
7777                Expression::Table(table) => {
7778                    insert(names, &table.name);
7779                    if let Some(schema) = &table.schema {
7780                        insert(names, schema);
7781                    }
7782                    if let Some(catalog) = &table.catalog {
7783                        insert(names, catalog);
7784                    }
7785                    if let Some(alias) = &table.alias {
7786                        insert(names, alias);
7787                    }
7788                    for alias in &table.column_aliases {
7789                        insert(names, alias);
7790                    }
7791                }
7792                Expression::Alias(alias) => {
7793                    insert(names, &alias.alias);
7794                    for column_alias in &alias.column_aliases {
7795                        insert(names, column_alias);
7796                    }
7797                }
7798                Expression::Subquery(subquery) => {
7799                    if let Some(alias) = &subquery.alias {
7800                        insert(names, alias);
7801                    }
7802                    for column_alias in &subquery.column_aliases {
7803                        insert(names, column_alias);
7804                    }
7805                }
7806                Expression::Cte(cte) => {
7807                    insert(names, &cte.alias);
7808                    for column in &cte.columns {
7809                        insert(names, column);
7810                    }
7811                    for key in &cte.key_expressions {
7812                        insert(names, key);
7813                    }
7814                }
7815                Expression::Values(values) => {
7816                    if let Some(alias) = &values.alias {
7817                        insert(names, alias);
7818                    }
7819                    for column_alias in &values.column_aliases {
7820                        insert(names, column_alias);
7821                    }
7822                }
7823                Expression::Unnest(unnest) => {
7824                    if let Some(alias) = &unnest.alias {
7825                        insert(names, alias);
7826                    }
7827                    if let Some(offset_alias) = &unnest.offset_alias {
7828                        insert(names, offset_alias);
7829                    }
7830                }
7831                _ => {}
7832            }
7833        }
7834    }
7835
7836    fn rewrite_postgres_format_for_tsql(
7837        expr: Expression,
7838        target: DialectType,
7839    ) -> Result<Expression> {
7840        transform_recursive(expr, &|e| match e {
7841            Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
7842                Self::postgres_format_function_to_tsql(*f, target)
7843            }
7844            other => Ok(other),
7845        })
7846    }
7847
7848    fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
7849        let Some(format_expr) = f.args.first() else {
7850            return Err(Self::unsupported_postgres_format_for_tsql(
7851                target,
7852                "missing format string",
7853            ));
7854        };
7855
7856        let format = match format_expr {
7857            Expression::Literal(lit) if lit.is_string() => lit.value_str(),
7858            _ => {
7859                return Err(Self::unsupported_postgres_format_for_tsql(
7860                    target,
7861                    "dynamic format strings",
7862                ))
7863            }
7864        };
7865
7866        let value_args = &f.args[1..];
7867        let mut arg_index = 0usize;
7868        let mut literal = String::new();
7869        let mut segments = Vec::new();
7870        let mut chars = format.chars();
7871
7872        while let Some(ch) = chars.next() {
7873            if ch != '%' {
7874                literal.push(ch);
7875                continue;
7876            }
7877
7878            let Some(specifier) = chars.next() else {
7879                return Err(Self::unsupported_postgres_format_for_tsql(
7880                    target,
7881                    "unterminated format specifier",
7882                ));
7883            };
7884
7885            match specifier {
7886                '%' => literal.push('%'),
7887                's' => {
7888                    if !literal.is_empty() {
7889                        segments.push(Expression::string(std::mem::take(&mut literal)));
7890                    }
7891                    let Some(arg) = value_args.get(arg_index) else {
7892                        return Err(Self::unsupported_postgres_format_for_tsql(
7893                            target,
7894                            "not enough arguments",
7895                        ));
7896                    };
7897                    segments.push(arg.clone());
7898                    arg_index += 1;
7899                }
7900                other => {
7901                    return Err(Self::unsupported_postgres_format_for_tsql(
7902                        target,
7903                        format!("unsupported format specifier %{other}"),
7904                    ))
7905                }
7906            }
7907        }
7908
7909        if !literal.is_empty() {
7910            segments.push(Expression::string(literal));
7911        }
7912
7913        if arg_index != value_args.len() {
7914            return Err(Self::unsupported_postgres_format_for_tsql(
7915                target,
7916                "unused format arguments",
7917            ));
7918        }
7919
7920        Ok(Self::postgres_format_segments_to_tsql_concat(segments))
7921    }
7922
7923    fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
7924        if segments.is_empty() {
7925            return Expression::string("");
7926        }
7927
7928        if segments.len() == 1 {
7929            let only = segments.pop().expect("one segment");
7930            if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
7931                return only;
7932            }
7933
7934            return Expression::Function(Box::new(Function::new(
7935                "CONCAT".to_string(),
7936                vec![only, Expression::string("")],
7937            )));
7938        }
7939
7940        Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
7941    }
7942
7943    fn unsupported_postgres_format_for_tsql(
7944        target: DialectType,
7945        reason: impl Into<String>,
7946    ) -> crate::error::Error {
7947        crate::error::Error::unsupported(
7948            format!("PostgreSQL format() ({})", reason.into()),
7949            target.to_string(),
7950        )
7951    }
7952
7953    fn rewrite_boolean_values_in_tsql_select(
7954        mut select: Box<crate::expressions::Select>,
7955    ) -> Result<Expression> {
7956        if let Some(mut with) = select.with.take() {
7957            with.ctes = with
7958                .ctes
7959                .into_iter()
7960                .map(|mut cte| {
7961                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7962                    Ok(cte)
7963                })
7964                .collect::<Result<Vec<_>>>()?;
7965            select.with = Some(with);
7966        }
7967
7968        select.expressions = select
7969            .expressions
7970            .into_iter()
7971            .map(Self::rewrite_tsql_boolean_scalar_value)
7972            .collect::<Result<Vec<_>>>()?;
7973
7974        if let Some(mut from) = select.from.take() {
7975            from.expressions = from
7976                .expressions
7977                .into_iter()
7978                .map(Self::rewrite_tsql_boolean_nested_contexts)
7979                .collect::<Result<Vec<_>>>()?;
7980            select.from = Some(from);
7981        }
7982
7983        select.joins = select
7984            .joins
7985            .into_iter()
7986            .map(|mut join| {
7987                join.this = Self::rewrite_tsql_boolean_nested_contexts(join.this)?;
7988                if let Some(on) = join.on.take() {
7989                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
7990                }
7991                if let Some(match_condition) = join.match_condition.take() {
7992                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
7993                        match_condition,
7994                    )?);
7995                }
7996                join.pivots = join
7997                    .pivots
7998                    .into_iter()
7999                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8000                    .collect::<Result<Vec<_>>>()?;
8001                Ok(join)
8002            })
8003            .collect::<Result<Vec<_>>>()?;
8004
8005        select.lateral_views = select
8006            .lateral_views
8007            .into_iter()
8008            .map(|mut lateral_view| {
8009                lateral_view.this = Self::rewrite_tsql_boolean_nested_contexts(lateral_view.this)?;
8010                Ok(lateral_view)
8011            })
8012            .collect::<Result<Vec<_>>>()?;
8013
8014        if let Some(prewhere) = select.prewhere.take() {
8015            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
8016        }
8017
8018        if let Some(mut where_clause) = select.where_clause.take() {
8019            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
8020            select.where_clause = Some(where_clause);
8021        }
8022
8023        if let Some(mut group_by) = select.group_by.take() {
8024            group_by.expressions = group_by
8025                .expressions
8026                .into_iter()
8027                .map(Self::rewrite_tsql_boolean_scalar_value)
8028                .collect::<Result<Vec<_>>>()?;
8029            select.group_by = Some(group_by);
8030        }
8031
8032        if let Some(mut having) = select.having.take() {
8033            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
8034            select.having = Some(having);
8035        }
8036
8037        if let Some(mut qualify) = select.qualify.take() {
8038            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
8039            select.qualify = Some(qualify);
8040        }
8041
8042        if let Some(mut order_by) = select.order_by.take() {
8043            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
8044            select.order_by = Some(order_by);
8045        }
8046
8047        if let Some(mut distribute_by) = select.distribute_by.take() {
8048            distribute_by.expressions = distribute_by
8049                .expressions
8050                .into_iter()
8051                .map(Self::rewrite_tsql_boolean_scalar_value)
8052                .collect::<Result<Vec<_>>>()?;
8053            select.distribute_by = Some(distribute_by);
8054        }
8055
8056        if let Some(mut cluster_by) = select.cluster_by.take() {
8057            cluster_by.expressions =
8058                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
8059            select.cluster_by = Some(cluster_by);
8060        }
8061
8062        if let Some(mut sort_by) = select.sort_by.take() {
8063            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
8064            select.sort_by = Some(sort_by);
8065        }
8066
8067        if let Some(limit_by) = select.limit_by.take() {
8068            select.limit_by = Some(
8069                limit_by
8070                    .into_iter()
8071                    .map(Self::rewrite_tsql_boolean_scalar_value)
8072                    .collect::<Result<Vec<_>>>()?,
8073            );
8074        }
8075
8076        if let Some(distinct_on) = select.distinct_on.take() {
8077            select.distinct_on = Some(
8078                distinct_on
8079                    .into_iter()
8080                    .map(Self::rewrite_tsql_boolean_scalar_value)
8081                    .collect::<Result<Vec<_>>>()?,
8082            );
8083        }
8084
8085        if let Some(mut sample) = select.sample.take() {
8086            sample.size = Self::rewrite_tsql_boolean_nested_contexts(sample.size)?;
8087            if let Some(offset) = sample.offset.take() {
8088                sample.offset = Some(Self::rewrite_tsql_boolean_nested_contexts(offset)?);
8089            }
8090            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
8091                sample.bucket_numerator = Some(Box::new(
8092                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_numerator)?,
8093                ));
8094            }
8095            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
8096                sample.bucket_denominator = Some(Box::new(
8097                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_denominator)?,
8098                ));
8099            }
8100            if let Some(bucket_field) = sample.bucket_field.take() {
8101                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8102                    *bucket_field,
8103                )?));
8104            }
8105            select.sample = Some(sample);
8106        }
8107
8108        if let Some(settings) = select.settings.take() {
8109            select.settings = Some(
8110                settings
8111                    .into_iter()
8112                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8113                    .collect::<Result<Vec<_>>>()?,
8114            );
8115        }
8116
8117        if let Some(format) = select.format.take() {
8118            select.format = Some(Self::rewrite_tsql_boolean_nested_contexts(format)?);
8119        }
8120
8121        if let Some(mut windows) = select.windows.take() {
8122            for window in windows.iter_mut() {
8123                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
8124            }
8125            select.windows = Some(windows);
8126        }
8127
8128        Ok(Expression::Select(select))
8129    }
8130
8131    fn normalize_postgres_boolean_semantics_for_tsql(expr: Expression) -> Result<Expression> {
8132        transform_recursive(expr, &|e| match e {
8133            Expression::Function(function)
8134                if function.args.len() == 2
8135                    && (function.name.eq_ignore_ascii_case("BOOLEQ")
8136                        || function.name.eq_ignore_ascii_case("BOOLNE")) =>
8137            {
8138                let is_equal = function.name.eq_ignore_ascii_case("BOOLEQ");
8139                let mut args = function.args.into_iter();
8140                let op = BinaryOp {
8141                    left: args.next().expect("checked boolean operator arity"),
8142                    right: args.next().expect("checked boolean operator arity"),
8143                    left_comments: Vec::new(),
8144                    operator_comments: Vec::new(),
8145                    trailing_comments: function.trailing_comments,
8146                    inferred_type: None,
8147                };
8148                if is_equal {
8149                    Ok(Expression::Eq(Box::new(op)))
8150                } else {
8151                    Ok(Expression::Neq(Box::new(op)))
8152                }
8153            }
8154            Expression::Cast(cast)
8155                if matches!(cast.to, DataType::Text)
8156                    && Self::is_known_postgres_boolean_expression(&cast.this) =>
8157            {
8158                Ok(Self::postgres_boolean_text_value(cast.this))
8159            }
8160            other => Ok(other),
8161        })
8162    }
8163
8164    fn is_known_postgres_boolean_expression(expr: &Expression) -> bool {
8165        match expr {
8166            Expression::Boolean(_) => true,
8167            Expression::Cast(cast) => matches!(cast.to, DataType::Boolean),
8168            Expression::Paren(paren) => Self::is_known_postgres_boolean_expression(&paren.this),
8169            other => Self::is_tsql_boolean_value_expression(other),
8170        }
8171    }
8172
8173    fn postgres_boolean_text_value(predicate: Expression) -> Expression {
8174        if let Expression::Boolean(boolean) = predicate {
8175            return Expression::string(if boolean.value { "true" } else { "false" });
8176        }
8177
8178        Self::three_valued_boolean_case(
8179            predicate,
8180            Expression::string("true"),
8181            Expression::string("false"),
8182        )
8183    }
8184
8185    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
8186        if let Expression::Boolean(boolean) = expr {
8187            return Ok(Expression::Cast(Box::new(Cast {
8188                this: Expression::Boolean(boolean),
8189                to: DataType::Boolean,
8190                trailing_comments: Vec::new(),
8191                double_colon_syntax: false,
8192                format: None,
8193                default: None,
8194                inferred_type: None,
8195            })));
8196        }
8197
8198        if Self::is_tsql_boolean_value_expression(&expr) {
8199            // Tuple/subquery equality currently lowers only its positive branch to EXISTS.
8200            // Keep its established two-way scalar fallback until that rewrite models UNKNOWN.
8201            let can_be_unknown = Self::tsql_boolean_expression_can_be_unknown(&expr)
8202                && !Self::node_is_row_value_subquery_comparison(&expr);
8203            let predicate = Self::rewrite_tsql_boolean_predicate_context(expr)?;
8204            return Ok(Self::tsql_boolean_value_case(predicate, can_be_unknown));
8205        }
8206
8207        match expr {
8208            Expression::Alias(mut alias) => {
8209                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
8210                Ok(Expression::Alias(alias))
8211            }
8212            Expression::Paren(mut paren) => {
8213                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
8214                Ok(Expression::Paren(paren))
8215            }
8216            Expression::Cast(mut cast) => {
8217                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8218                if let Some(format) = cast.format.take() {
8219                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8220                        *format,
8221                    )?));
8222                }
8223                if let Some(default) = cast.default.take() {
8224                    cast.default =
8225                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8226                }
8227                Ok(Expression::Cast(cast))
8228            }
8229            Expression::TryCast(mut cast) => {
8230                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8231                if let Some(format) = cast.format.take() {
8232                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8233                        *format,
8234                    )?));
8235                }
8236                if let Some(default) = cast.default.take() {
8237                    cast.default =
8238                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8239                }
8240                Ok(Expression::TryCast(cast))
8241            }
8242            Expression::SafeCast(mut cast) => {
8243                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8244                if let Some(format) = cast.format.take() {
8245                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8246                        *format,
8247                    )?));
8248                }
8249                if let Some(default) = cast.default.take() {
8250                    cast.default =
8251                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8252                }
8253                Ok(Expression::SafeCast(cast))
8254            }
8255            Expression::Case(mut case) => {
8256                let is_simple_case = case.operand.is_some();
8257                if let Some(operand) = case.operand.take() {
8258                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
8259                }
8260                case.whens = case
8261                    .whens
8262                    .into_iter()
8263                    .map(|(condition, result)| {
8264                        let condition = if is_simple_case {
8265                            Self::rewrite_tsql_boolean_scalar_value(condition)?
8266                        } else {
8267                            Self::rewrite_tsql_boolean_predicate_context(condition)?
8268                        };
8269                        Ok((condition, Self::rewrite_tsql_boolean_scalar_value(result)?))
8270                    })
8271                    .collect::<Result<Vec<_>>>()?;
8272                if let Some(else_) = case.else_.take() {
8273                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
8274                }
8275                Ok(Expression::Case(case))
8276            }
8277            Expression::IfFunc(mut if_func) => {
8278                if_func.condition =
8279                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
8280                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
8281                if let Some(false_value) = if_func.false_value.take() {
8282                    if_func.false_value =
8283                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
8284                }
8285                Ok(Expression::IfFunc(if_func))
8286            }
8287            Expression::WindowFunction(mut window_function) => {
8288                window_function.this =
8289                    Self::rewrite_tsql_boolean_nested_contexts(window_function.this)?;
8290                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
8291                if let Some(mut keep) = window_function.keep.take() {
8292                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
8293                    window_function.keep = Some(keep);
8294                }
8295                Ok(Expression::WindowFunction(window_function))
8296            }
8297            Expression::WithinGroup(mut within_group) => {
8298                within_group.this = Self::rewrite_tsql_boolean_nested_contexts(within_group.this)?;
8299                within_group.order_by =
8300                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
8301                Ok(Expression::WithinGroup(within_group))
8302            }
8303            Expression::Subquery(mut subquery) => {
8304                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8305                Ok(Expression::Subquery(subquery))
8306            }
8307            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8308            other => Self::rewrite_tsql_boolean_nested_contexts(other),
8309        }
8310    }
8311
8312    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
8313        let expr = Self::rewrite_tsql_boolean_nested_contexts(expr)?;
8314        Ok(crate::transforms::ensure_bool_condition(expr))
8315    }
8316
8317    fn rewrite_tsql_boolean_nested_contexts(expr: Expression) -> Result<Expression> {
8318        transform_recursive(expr, &|e| match e {
8319            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8320            Expression::Subquery(mut subquery) => {
8321                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8322                Ok(Expression::Subquery(subquery))
8323            }
8324            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
8325                Self::rewrite_boolean_values_for_tsql(e)
8326            }
8327            other => Self::rewrite_tsql_boolean_cast_operand(other),
8328        })
8329    }
8330
8331    fn rewrite_tsql_boolean_cast_operand(expr: Expression) -> Result<Expression> {
8332        macro_rules! rewrite_cast_operand {
8333            ($variant:ident, $cast:expr) => {{
8334                let mut cast = $cast;
8335                if Self::is_tsql_boolean_value_expression(&cast.this) {
8336                    cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8337                }
8338                Ok(Expression::$variant(cast))
8339            }};
8340        }
8341
8342        match expr {
8343            Expression::Cast(cast) => rewrite_cast_operand!(Cast, cast),
8344            Expression::TryCast(cast) => rewrite_cast_operand!(TryCast, cast),
8345            Expression::SafeCast(cast) => rewrite_cast_operand!(SafeCast, cast),
8346            other => Ok(other),
8347        }
8348    }
8349
8350    fn rewrite_tsql_boolean_ordered_values(
8351        ordered: Vec<crate::expressions::Ordered>,
8352    ) -> Result<Vec<crate::expressions::Ordered>> {
8353        ordered
8354            .into_iter()
8355            .map(|mut ordered| {
8356                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
8357                if let Some(with_fill) = ordered.with_fill.take() {
8358                    ordered.with_fill = Some(Box::new(
8359                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
8360                    ));
8361                }
8362                Ok(ordered)
8363            })
8364            .collect()
8365    }
8366
8367    fn rewrite_tsql_boolean_with_fill_values(
8368        mut with_fill: crate::expressions::WithFill,
8369    ) -> Result<crate::expressions::WithFill> {
8370        if let Some(from) = with_fill.from_.take() {
8371            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
8372        }
8373        if let Some(to) = with_fill.to.take() {
8374            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
8375        }
8376        if let Some(step) = with_fill.step.take() {
8377            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
8378        }
8379        if let Some(staleness) = with_fill.staleness.take() {
8380            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8381                *staleness,
8382            )?));
8383        }
8384        if let Some(interpolate) = with_fill.interpolate.take() {
8385            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8386                *interpolate,
8387            )?));
8388        }
8389        Ok(with_fill)
8390    }
8391
8392    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
8393        over.partition_by = std::mem::take(&mut over.partition_by)
8394            .into_iter()
8395            .map(Self::rewrite_tsql_boolean_scalar_value)
8396            .collect::<Result<Vec<_>>>()?;
8397        over.order_by =
8398            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
8399        Ok(())
8400    }
8401
8402    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
8403        match expr {
8404            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
8405            Expression::Eq(_)
8406            | Expression::Neq(_)
8407            | Expression::Lt(_)
8408            | Expression::Lte(_)
8409            | Expression::Gt(_)
8410            | Expression::Gte(_)
8411            | Expression::Is(_)
8412            | Expression::IsNull(_)
8413            | Expression::IsTrue(_)
8414            | Expression::IsFalse(_)
8415            | Expression::Like(_)
8416            | Expression::ILike(_)
8417            | Expression::StartsWith(_)
8418            | Expression::SimilarTo(_)
8419            | Expression::Glob(_)
8420            | Expression::RegexpLike(_)
8421            | Expression::In(_)
8422            | Expression::Between(_)
8423            | Expression::Exists(_)
8424            | Expression::And(_)
8425            | Expression::Or(_)
8426            | Expression::Not(_)
8427            | Expression::Any(_)
8428            | Expression::All(_)
8429            | Expression::NullSafeEq(_)
8430            | Expression::NullSafeNeq(_)
8431            | Expression::EqualNull(_) => true,
8432            _ => false,
8433        }
8434    }
8435
8436    fn tsql_boolean_expression_can_be_unknown(expr: &Expression) -> bool {
8437        match expr {
8438            Expression::Boolean(_)
8439            | Expression::IsNull(_)
8440            | Expression::IsTrue(_)
8441            | Expression::IsFalse(_)
8442            | Expression::Exists(_)
8443            | Expression::NullSafeEq(_)
8444            | Expression::NullSafeNeq(_)
8445            | Expression::EqualNull(_) => false,
8446            Expression::Paren(paren) => Self::tsql_boolean_expression_can_be_unknown(&paren.this),
8447            Expression::Not(op) => Self::tsql_boolean_expression_can_be_unknown(&op.this),
8448            Expression::And(op) | Expression::Or(op) => {
8449                Self::tsql_boolean_expression_can_be_unknown(&op.left)
8450                    || Self::tsql_boolean_expression_can_be_unknown(&op.right)
8451            }
8452            _ => true,
8453        }
8454    }
8455
8456    fn tsql_boolean_value_case(predicate: Expression, can_be_unknown: bool) -> Expression {
8457        let case = if can_be_unknown {
8458            Self::three_valued_boolean_case(predicate, Expression::number(1), Expression::number(0))
8459        } else {
8460            Expression::Case(Box::new(crate::expressions::Case {
8461                operand: None,
8462                whens: vec![(predicate, Expression::number(1))],
8463                else_: Some(Expression::number(0)),
8464                comments: Vec::new(),
8465                inferred_type: None,
8466            }))
8467        };
8468
8469        Expression::Cast(Box::new(Cast {
8470            this: case,
8471            to: DataType::Boolean,
8472            trailing_comments: Vec::new(),
8473            double_colon_syntax: false,
8474            format: None,
8475            default: None,
8476            inferred_type: None,
8477        }))
8478    }
8479
8480    fn three_valued_boolean_case(
8481        predicate: Expression,
8482        true_value: Expression,
8483        false_value: Expression,
8484    ) -> Expression {
8485        let false_operand = if matches!(predicate, Expression::And(_) | Expression::Or(_)) {
8486            Expression::Paren(Box::new(crate::expressions::Paren {
8487                this: predicate.clone(),
8488                trailing_comments: Vec::new(),
8489            }))
8490        } else {
8491            predicate.clone()
8492        };
8493        let false_predicate = Expression::Not(Box::new(crate::expressions::UnaryOp {
8494            this: false_operand,
8495            inferred_type: None,
8496        }));
8497
8498        Expression::Case(Box::new(crate::expressions::Case {
8499            operand: None,
8500            whens: vec![(predicate, true_value), (false_predicate, false_value)],
8501            else_: Some(Expression::null()),
8502            comments: Vec::new(),
8503            inferred_type: None,
8504        }))
8505    }
8506
8507    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
8508        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
8509    }
8510
8511    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
8512        macro_rules! rewrite_agg_filter {
8513            ($variant:ident, $agg:expr) => {{
8514                let mut agg = $agg;
8515                if let Some(filter) = agg.filter.take() {
8516                    let this = std::mem::replace(&mut agg.this, Expression::null());
8517                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8518                }
8519                Ok(Expression::$variant(agg))
8520            }};
8521        }
8522
8523        match expr {
8524            Expression::Filter(filter) => {
8525                let condition = match *filter.expression {
8526                    Expression::Where(where_) => where_.this,
8527                    other => other,
8528                };
8529                Ok(Self::push_filter_into_tsql_aggregate(
8530                    *filter.this,
8531                    condition,
8532                ))
8533            }
8534            Expression::AggregateFunction(mut agg) => {
8535                if let Some(filter) = agg.filter.take() {
8536                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8537                }
8538                Ok(Expression::AggregateFunction(agg))
8539            }
8540            Expression::Count(mut count) => {
8541                if let Some(filter) = count.filter.take() {
8542                    let value = if count.star {
8543                        Expression::number(1)
8544                    } else {
8545                        count.this.take().unwrap_or_else(|| Expression::number(1))
8546                    };
8547                    count.star = false;
8548                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8549                }
8550                Ok(Expression::Count(count))
8551            }
8552            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
8553            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
8554            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
8555            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
8556            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
8557            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
8558            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
8559            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
8560            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
8561            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
8562            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
8563            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
8564            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
8565            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
8566            Expression::First(agg) => rewrite_agg_filter!(First, agg),
8567            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
8568            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
8569            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
8570            Expression::ApproxCountDistinct(agg) => {
8571                rewrite_agg_filter!(ApproxCountDistinct, agg)
8572            }
8573            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
8574            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
8575            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
8576            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
8577            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
8578            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
8579            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
8580            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
8581            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
8582            Expression::StringAgg(mut agg) => {
8583                if let Some(filter) = agg.filter.take() {
8584                    let this = std::mem::replace(&mut agg.this, Expression::null());
8585                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8586                }
8587                Ok(Expression::StringAgg(agg))
8588            }
8589            Expression::GroupConcat(mut agg) => {
8590                if let Some(filter) = agg.filter.take() {
8591                    let this = std::mem::replace(&mut agg.this, Expression::null());
8592                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8593                }
8594                Ok(Expression::GroupConcat(agg))
8595            }
8596            Expression::ListAgg(mut agg) => {
8597                if let Some(filter) = agg.filter.take() {
8598                    let this = std::mem::replace(&mut agg.this, Expression::null());
8599                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8600                }
8601                Ok(Expression::ListAgg(agg))
8602            }
8603            Expression::WithinGroup(mut within_group) => {
8604                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
8605                Ok(Expression::WithinGroup(within_group))
8606            }
8607            other => Ok(other),
8608        }
8609    }
8610
8611    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
8612        macro_rules! push_agg_filter {
8613            ($variant:ident, $agg:expr) => {{
8614                let mut agg = $agg;
8615                let this = std::mem::replace(&mut agg.this, Expression::null());
8616                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8617                agg.filter = None;
8618                Expression::$variant(agg)
8619            }};
8620        }
8621
8622        match expr {
8623            Expression::AggregateFunction(mut agg) => {
8624                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8625                Expression::AggregateFunction(agg)
8626            }
8627            Expression::Count(mut count) => {
8628                let value = if count.star {
8629                    Expression::number(1)
8630                } else {
8631                    count.this.take().unwrap_or_else(|| Expression::number(1))
8632                };
8633                count.star = false;
8634                count.filter = None;
8635                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8636                Expression::Count(count)
8637            }
8638            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
8639            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
8640            Expression::Min(agg) => push_agg_filter!(Min, agg),
8641            Expression::Max(agg) => push_agg_filter!(Max, agg),
8642            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
8643            Expression::CountIf(mut agg) => {
8644                agg.filter = Some(filter);
8645                Expression::CountIf(agg)
8646            }
8647            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
8648            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
8649            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
8650            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
8651            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
8652            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
8653            Expression::Median(agg) => push_agg_filter!(Median, agg),
8654            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
8655            Expression::First(agg) => push_agg_filter!(First, agg),
8656            Expression::Last(agg) => push_agg_filter!(Last, agg),
8657            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
8658            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
8659            Expression::ApproxCountDistinct(agg) => {
8660                push_agg_filter!(ApproxCountDistinct, agg)
8661            }
8662            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
8663            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
8664            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
8665            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
8666            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
8667            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
8668            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
8669            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
8670            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
8671            Expression::StringAgg(mut agg) => {
8672                let this = std::mem::replace(&mut agg.this, Expression::null());
8673                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8674                agg.filter = None;
8675                Expression::StringAgg(agg)
8676            }
8677            Expression::GroupConcat(mut agg) => {
8678                let this = std::mem::replace(&mut agg.this, Expression::null());
8679                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8680                agg.filter = None;
8681                Expression::GroupConcat(agg)
8682            }
8683            Expression::ListAgg(mut agg) => {
8684                let this = std::mem::replace(&mut agg.this, Expression::null());
8685                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8686                agg.filter = None;
8687                Expression::ListAgg(agg)
8688            }
8689            Expression::WithinGroup(mut within_group) => {
8690                within_group.this =
8691                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
8692                Expression::WithinGroup(within_group)
8693            }
8694            other => Expression::Filter(Box::new(crate::expressions::Filter {
8695                this: Box::new(other),
8696                expression: Box::new(filter),
8697            })),
8698        }
8699    }
8700
8701    fn rewrite_generic_aggregate_filter_for_tsql(
8702        agg: &mut crate::expressions::AggregateFunction,
8703        filter: Expression,
8704    ) {
8705        let is_count =
8706            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
8707        let is_count_star = is_count
8708            && (agg.args.is_empty()
8709                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
8710
8711        if is_count_star {
8712            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
8713                filter,
8714                Expression::number(1),
8715            )];
8716        } else if !agg.args.is_empty() {
8717            agg.args = agg
8718                .args
8719                .drain(..)
8720                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
8721                .collect();
8722        } else {
8723            agg.filter = Some(filter);
8724        }
8725    }
8726
8727    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
8728        let filter = crate::transforms::ensure_bool_condition(filter);
8729        Expression::Case(Box::new(crate::expressions::Case {
8730            operand: None,
8731            whens: vec![(filter, value)],
8732            else_: None,
8733            comments: Vec::new(),
8734            inferred_type: None,
8735        }))
8736    }
8737
8738    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
8739        let tokens = self.tokenize(sql)?;
8740        for (i, token) in tokens.iter().enumerate() {
8741            if token.token_type == TokenType::NullsafeEq {
8742                return Err(crate::error::Error::unsupported(
8743                    "PostgreSQL pgvector cosine distance operator <=>",
8744                    "SQLite",
8745                ));
8746            }
8747            if token.token_type == TokenType::Lt
8748                && tokens
8749                    .get(i + 1)
8750                    .is_some_and(|token| token.token_type == TokenType::Tilde)
8751                && tokens
8752                    .get(i + 2)
8753                    .is_some_and(|token| token.token_type == TokenType::Gt)
8754            {
8755                return Err(crate::error::Error::unsupported(
8756                    "PostgreSQL pgvector Hamming distance operator <~>",
8757                    "SQLite",
8758                ));
8759            }
8760        }
8761        Ok(())
8762    }
8763
8764    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
8765        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
8766            transform_recursive(expr, &|e| match e {
8767                Expression::Column(col)
8768                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
8769                {
8770                    Ok(Expression::Literal(Box::new(Literal::String(
8771                        col.name.name,
8772                    ))))
8773                }
8774                Expression::Identifier(id) if id.quoted => {
8775                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
8776                }
8777                _ => Ok(e),
8778            })
8779        }
8780
8781        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
8782            if let Some(default) = col.default.take() {
8783                col.default = Some(normalize_default_expr(default)?);
8784            }
8785
8786            for constraint in &mut col.constraints {
8787                if let ColumnConstraint::Default(default) = constraint {
8788                    *default = normalize_default_expr(default.clone())?;
8789                }
8790            }
8791
8792            Ok(())
8793        }
8794
8795        transform_recursive(expr, &|e| match e {
8796            Expression::CreateTable(mut ct) => {
8797                for column in &mut ct.columns {
8798                    normalize_column_default(column)?;
8799                }
8800                Ok(Expression::CreateTable(ct))
8801            }
8802            Expression::ColumnDef(mut col) => {
8803                normalize_column_default(&mut col)?;
8804                Ok(Expression::ColumnDef(col))
8805            }
8806            _ => Ok(e),
8807        })
8808    }
8809
8810    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
8811        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8812            use crate::expressions::DataType;
8813
8814            match dt {
8815                DataType::Bit { .. } => DataType::Int {
8816                    length: None,
8817                    integer_spelling: true,
8818                },
8819                DataType::TextWithLength { .. } => DataType::Text,
8820                DataType::VarChar { .. } => DataType::Text,
8821                DataType::Char { .. } => DataType::Text,
8822                DataType::Timestamp { timezone: true, .. } => DataType::Text,
8823                DataType::Custom { name } => {
8824                    let base = name
8825                        .split_once('(')
8826                        .map_or(name.as_str(), |(base, _)| base)
8827                        .trim();
8828                    if base.eq_ignore_ascii_case("TSVECTOR")
8829                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
8830                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
8831                        || base.eq_ignore_ascii_case("NVARCHAR")
8832                        || base.eq_ignore_ascii_case("NCHAR")
8833                    {
8834                        DataType::Text
8835                    } else {
8836                        DataType::Custom { name }
8837                    }
8838                }
8839                _ => dt,
8840            }
8841        }
8842
8843        transform_recursive(expr, &|e| match e {
8844            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
8845            Expression::CreateTable(mut ct) => {
8846                for column in &mut ct.columns {
8847                    column.data_type = sqlite_type(column.data_type.clone());
8848                }
8849                Ok(Expression::CreateTable(ct))
8850            }
8851            _ => Ok(e),
8852        })
8853    }
8854
8855    fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
8856        fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8857            use crate::expressions::DataType;
8858
8859            match dt {
8860                DataType::Decimal {
8861                    precision: None,
8862                    scale: None,
8863                } => DataType::Decimal {
8864                    precision: Some(38),
8865                    scale: Some(10),
8866                },
8867                DataType::Json | DataType::JsonB => DataType::Custom {
8868                    name: "VARCHAR(MAX)".to_string(),
8869                },
8870                _ => dt,
8871            }
8872        }
8873
8874        transform_recursive(expr, &|e| match e {
8875            Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
8876            Expression::CreateTable(mut ct) => {
8877                for column in &mut ct.columns {
8878                    column.data_type = fabric_type(column.data_type.clone());
8879                }
8880                Ok(Expression::CreateTable(ct))
8881            }
8882            Expression::ColumnDef(mut col) => {
8883                col.data_type = fabric_type(col.data_type);
8884                Ok(Expression::ColumnDef(col))
8885            }
8886            _ => Ok(e),
8887        })
8888    }
8889
8890    /// For DuckDB target: when FROM clause contains RANGE(n), replace
8891    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
8892    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
8893    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
8894        if let Expression::Select(mut select) = expr {
8895            // Check if FROM contains a RANGE function
8896            let has_range_from = if let Some(ref from) = select.from {
8897                from.expressions.iter().any(|e| {
8898                    // Check for direct RANGE(...) or aliased RANGE(...)
8899                    match e {
8900                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
8901                        Expression::Alias(a) => {
8902                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
8903                        }
8904                        _ => false,
8905                    }
8906                })
8907            } else {
8908                false
8909            };
8910
8911            if has_range_from {
8912                // Replace the ROW_NUMBER pattern in select expressions
8913                select.expressions = select
8914                    .expressions
8915                    .into_iter()
8916                    .map(|e| Self::replace_rownum_with_range(e))
8917                    .collect();
8918            }
8919
8920            Ok(Expression::Select(select))
8921        } else {
8922            Ok(expr)
8923        }
8924    }
8925
8926    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
8927    fn replace_rownum_with_range(expr: Expression) -> Expression {
8928        match expr {
8929            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
8930            Expression::Mod(op) => {
8931                let new_left = Self::try_replace_rownum_paren(&op.left);
8932                Expression::Mod(Box::new(crate::expressions::BinaryOp {
8933                    left: new_left,
8934                    right: op.right,
8935                    left_comments: op.left_comments,
8936                    operator_comments: op.operator_comments,
8937                    trailing_comments: op.trailing_comments,
8938                    inferred_type: op.inferred_type,
8939                }))
8940            }
8941            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
8942            Expression::Paren(p) => {
8943                let inner = Self::replace_rownum_with_range(p.this);
8944                Expression::Paren(Box::new(crate::expressions::Paren {
8945                    this: inner,
8946                    trailing_comments: p.trailing_comments,
8947                }))
8948            }
8949            Expression::Case(mut c) => {
8950                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
8951                c.whens = c
8952                    .whens
8953                    .into_iter()
8954                    .map(|(cond, then)| {
8955                        (
8956                            Self::replace_rownum_with_range(cond),
8957                            Self::replace_rownum_with_range(then),
8958                        )
8959                    })
8960                    .collect();
8961                if let Some(else_) = c.else_ {
8962                    c.else_ = Some(Self::replace_rownum_with_range(else_));
8963                }
8964                Expression::Case(c)
8965            }
8966            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
8967                left: Self::replace_rownum_with_range(op.left),
8968                right: op.right,
8969                left_comments: op.left_comments,
8970                operator_comments: op.operator_comments,
8971                trailing_comments: op.trailing_comments,
8972                inferred_type: op.inferred_type,
8973            })),
8974            Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
8975                left: Self::replace_rownum_with_range(op.left),
8976                right: op.right,
8977                left_comments: op.left_comments,
8978                operator_comments: op.operator_comments,
8979                trailing_comments: op.trailing_comments,
8980                inferred_type: op.inferred_type,
8981            })),
8982            Expression::Alias(mut a) => {
8983                a.this = Self::replace_rownum_with_range(a.this);
8984                Expression::Alias(a)
8985            }
8986            other => other,
8987        }
8988    }
8989
8990    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
8991    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
8992        if let Expression::Paren(ref p) = expr {
8993            if let Expression::Sub(ref sub) = p.this {
8994                if let Expression::WindowFunction(ref wf) = sub.left {
8995                    if let Expression::Function(ref f) = wf.this {
8996                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
8997                            if let Expression::Literal(ref lit) = sub.right {
8998                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
8999                                    if n == "1" {
9000                                        return Expression::column("range");
9001                                    }
9002                                }
9003                            }
9004                        }
9005                    }
9006                }
9007            }
9008        }
9009        expr.clone()
9010    }
9011
9012    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
9013    /// Converts:
9014    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
9015    /// To:
9016    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
9017    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
9018    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
9019        use crate::expressions::*;
9020        transform_recursive(expr, &|e| {
9021            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
9022            if let Expression::ArraySize(ref af) = e {
9023                if let Expression::Function(ref f) = af.this {
9024                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9025                        let result = Self::convert_array_size_gda_snowflake(f)?;
9026                        return Ok(result);
9027                    }
9028                }
9029            }
9030
9031            let Expression::Select(mut sel) = e else {
9032                return Ok(e);
9033            };
9034
9035            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
9036            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
9037            let mut gda_join_idx: Option<usize> = None;
9038
9039            for (idx, join) in sel.joins.iter().enumerate() {
9040                // The join.this may be:
9041                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
9042                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
9043                let (unnest_ref, alias_name) = match &join.this {
9044                    Expression::Unnest(ref unnest) => {
9045                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
9046                        (Some(unnest.as_ref()), alias)
9047                    }
9048                    Expression::Alias(ref a) => {
9049                        if let Expression::Unnest(ref unnest) = a.this {
9050                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
9051                        } else {
9052                            (None, None)
9053                        }
9054                    }
9055                    _ => (None, None),
9056                };
9057
9058                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
9059                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
9060                    if let Expression::Function(ref f) = unnest.this {
9061                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9062                            let start_expr = f.args[0].clone();
9063                            let end_expr = f.args[1].clone();
9064                            let step = f.args.get(2).cloned();
9065
9066                            // Extract unit from step interval
9067                            let unit = if let Some(Expression::Interval(ref iv)) = step {
9068                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9069                                    Some(format!("{:?}", unit).to_ascii_uppercase())
9070                                } else if let Some(ref this) = iv.this {
9071                                    // The interval may be stored as a string like "1 MONTH"
9072                                    if let Expression::Literal(lit) = this {
9073                                        if let Literal::String(ref s) = lit.as_ref() {
9074                                            let parts: Vec<&str> = s.split_whitespace().collect();
9075                                            if parts.len() == 2 {
9076                                                Some(parts[1].to_ascii_uppercase())
9077                                            } else if parts.len() == 1 {
9078                                                // Single word like "MONTH" or just "1"
9079                                                let upper = parts[0].to_ascii_uppercase();
9080                                                if matches!(
9081                                                    upper.as_str(),
9082                                                    "YEAR"
9083                                                        | "QUARTER"
9084                                                        | "MONTH"
9085                                                        | "WEEK"
9086                                                        | "DAY"
9087                                                        | "HOUR"
9088                                                        | "MINUTE"
9089                                                        | "SECOND"
9090                                                ) {
9091                                                    Some(upper)
9092                                                } else {
9093                                                    None
9094                                                }
9095                                            } else {
9096                                                None
9097                                            }
9098                                        } else {
9099                                            None
9100                                        }
9101                                    } else {
9102                                        None
9103                                    }
9104                                } else {
9105                                    None
9106                                }
9107                            } else {
9108                                None
9109                            };
9110
9111                            if let Some(unit_str) = unit {
9112                                gda_info = Some((alias, start_expr, end_expr, unit_str));
9113                                gda_join_idx = Some(idx);
9114                            }
9115                        }
9116                    }
9117                }
9118                if gda_info.is_some() {
9119                    break;
9120                }
9121            }
9122
9123            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
9124                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
9125                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
9126                let result = Self::try_transform_from_gda_snowflake(sel);
9127                return result;
9128            };
9129            let join_idx = gda_join_idx.unwrap();
9130
9131            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
9132            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
9133            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
9134            let datediff = Expression::Function(Box::new(Function::new(
9135                "DATEDIFF".to_string(),
9136                vec![
9137                    Expression::boxed_column(Column {
9138                        name: Identifier::new(&unit_str),
9139                        table: None,
9140                        join_mark: false,
9141                        trailing_comments: vec![],
9142                        span: None,
9143                        inferred_type: None,
9144                    }),
9145                    start_expr.clone(),
9146                    end_expr.clone(),
9147                ],
9148            )));
9149            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9150                left: datediff,
9151                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9152                left_comments: vec![],
9153                operator_comments: vec![],
9154                trailing_comments: vec![],
9155                inferred_type: None,
9156            }));
9157
9158            let array_gen_range = Expression::Function(Box::new(Function::new(
9159                "ARRAY_GENERATE_RANGE".to_string(),
9160                vec![
9161                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9162                    datediff_plus_one,
9163                ],
9164            )));
9165
9166            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
9167            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9168                name: Identifier::new("INPUT"),
9169                value: array_gen_range,
9170                separator: crate::expressions::NamedArgSeparator::DArrow,
9171            }));
9172            let flatten = Expression::Function(Box::new(Function::new(
9173                "FLATTEN".to_string(),
9174                vec![flatten_input],
9175            )));
9176
9177            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
9178            let alias_table = Alias {
9179                this: flatten,
9180                alias: Identifier::new("_t0"),
9181                column_aliases: vec![
9182                    Identifier::new("seq"),
9183                    Identifier::new("key"),
9184                    Identifier::new("path"),
9185                    Identifier::new("index"),
9186                    Identifier::new(&alias_name),
9187                    Identifier::new("this"),
9188                ],
9189                alias_explicit_as: false,
9190                alias_keyword: None,
9191                pre_alias_comments: vec![],
9192                trailing_comments: vec![],
9193                inferred_type: None,
9194            };
9195            let lateral_expr = Expression::Lateral(Box::new(Lateral {
9196                this: Box::new(Expression::Alias(Box::new(alias_table))),
9197                view: None,
9198                outer: None,
9199                alias: None,
9200                alias_quoted: false,
9201                cross_apply: None,
9202                ordinality: None,
9203                column_aliases: vec![],
9204            }));
9205
9206            // Remove the original join and add to FROM expressions
9207            sel.joins.remove(join_idx);
9208            if let Some(ref mut from) = sel.from {
9209                from.expressions.push(lateral_expr);
9210            }
9211
9212            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
9213            let dateadd_expr = Expression::Function(Box::new(Function::new(
9214                "DATEADD".to_string(),
9215                vec![
9216                    Expression::boxed_column(Column {
9217                        name: Identifier::new(&unit_str),
9218                        table: None,
9219                        join_mark: false,
9220                        trailing_comments: vec![],
9221                        span: None,
9222                        inferred_type: None,
9223                    }),
9224                    Expression::Cast(Box::new(Cast {
9225                        this: Expression::boxed_column(Column {
9226                            name: Identifier::new(&alias_name),
9227                            table: None,
9228                            join_mark: false,
9229                            trailing_comments: vec![],
9230                            span: None,
9231                            inferred_type: None,
9232                        }),
9233                        to: DataType::Int {
9234                            length: None,
9235                            integer_spelling: false,
9236                        },
9237                        trailing_comments: vec![],
9238                        double_colon_syntax: false,
9239                        format: None,
9240                        default: None,
9241                        inferred_type: None,
9242                    })),
9243                    Expression::Cast(Box::new(Cast {
9244                        this: start_expr.clone(),
9245                        to: DataType::Date,
9246                        trailing_comments: vec![],
9247                        double_colon_syntax: false,
9248                        format: None,
9249                        default: None,
9250                        inferred_type: None,
9251                    })),
9252                ],
9253            )));
9254
9255            // Replace references to the alias in the SELECT list
9256            let new_exprs: Vec<Expression> = sel
9257                .expressions
9258                .iter()
9259                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
9260                .collect();
9261            sel.expressions = new_exprs;
9262
9263            Ok(Expression::Select(sel))
9264        })
9265    }
9266
9267    /// Helper: replace column references to `alias_name` with dateadd expression
9268    fn replace_column_ref_with_dateadd(
9269        expr: &Expression,
9270        alias_name: &str,
9271        dateadd: &Expression,
9272    ) -> Expression {
9273        use crate::expressions::*;
9274        match expr {
9275            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9276                // Plain column reference -> DATEADD(...) AS alias_name
9277                Expression::Alias(Box::new(Alias {
9278                    this: dateadd.clone(),
9279                    alias: Identifier::new(alias_name),
9280                    column_aliases: vec![],
9281                    alias_explicit_as: false,
9282                    alias_keyword: None,
9283                    pre_alias_comments: vec![],
9284                    trailing_comments: vec![],
9285                    inferred_type: None,
9286                }))
9287            }
9288            Expression::Alias(a) => {
9289                // Check if the inner expression references the alias
9290                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
9291                Expression::Alias(Box::new(Alias {
9292                    this: new_this,
9293                    alias: a.alias.clone(),
9294                    column_aliases: a.column_aliases.clone(),
9295                    alias_explicit_as: false,
9296                    alias_keyword: None,
9297                    pre_alias_comments: a.pre_alias_comments.clone(),
9298                    trailing_comments: a.trailing_comments.clone(),
9299                    inferred_type: None,
9300                }))
9301            }
9302            _ => expr.clone(),
9303        }
9304    }
9305
9306    /// Helper: replace column references in inner expression (not top-level)
9307    fn replace_column_ref_inner(
9308        expr: &Expression,
9309        alias_name: &str,
9310        dateadd: &Expression,
9311    ) -> Expression {
9312        use crate::expressions::*;
9313        match expr {
9314            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9315                dateadd.clone()
9316            }
9317            Expression::Add(op) => {
9318                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9319                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9320                Expression::Add(Box::new(BinaryOp {
9321                    left,
9322                    right,
9323                    left_comments: op.left_comments.clone(),
9324                    operator_comments: op.operator_comments.clone(),
9325                    trailing_comments: op.trailing_comments.clone(),
9326                    inferred_type: None,
9327                }))
9328            }
9329            Expression::Sub(op) => {
9330                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9331                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9332                Expression::Sub(Box::new(BinaryOp {
9333                    left,
9334                    right,
9335                    left_comments: op.left_comments.clone(),
9336                    operator_comments: op.operator_comments.clone(),
9337                    trailing_comments: op.trailing_comments.clone(),
9338                    inferred_type: None,
9339                }))
9340            }
9341            Expression::Mul(op) => {
9342                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9343                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9344                Expression::Mul(Box::new(BinaryOp {
9345                    left,
9346                    right,
9347                    left_comments: op.left_comments.clone(),
9348                    operator_comments: op.operator_comments.clone(),
9349                    trailing_comments: op.trailing_comments.clone(),
9350                    inferred_type: None,
9351                }))
9352            }
9353            _ => expr.clone(),
9354        }
9355    }
9356
9357    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
9358    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
9359    fn try_transform_from_gda_snowflake(
9360        mut sel: Box<crate::expressions::Select>,
9361    ) -> Result<Expression> {
9362        use crate::expressions::*;
9363
9364        // Extract GDA info from FROM clause
9365        let mut gda_info: Option<(
9366            usize,
9367            String,
9368            Expression,
9369            Expression,
9370            String,
9371            Option<(String, Vec<Identifier>)>,
9372        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
9373
9374        if let Some(ref from) = sel.from {
9375            for (idx, table_expr) in from.expressions.iter().enumerate() {
9376                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
9377                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
9378                let (unnest_opt, outer_alias_info) = match table_expr {
9379                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
9380                    Expression::Alias(ref a) => {
9381                        if let Expression::Unnest(ref unnest) = a.this {
9382                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
9383                            (Some(unnest.as_ref()), Some(alias_info))
9384                        } else {
9385                            (None, None)
9386                        }
9387                    }
9388                    _ => (None, None),
9389                };
9390
9391                if let Some(unnest) = unnest_opt {
9392                    // Check for GENERATE_DATE_ARRAY function
9393                    let func_opt = match &unnest.this {
9394                        Expression::Function(ref f)
9395                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
9396                                && f.args.len() >= 2 =>
9397                        {
9398                            Some(f)
9399                        }
9400                        // Also check for GenerateSeries (from earlier normalization)
9401                        _ => None,
9402                    };
9403
9404                    if let Some(f) = func_opt {
9405                        let start_expr = f.args[0].clone();
9406                        let end_expr = f.args[1].clone();
9407                        let step = f.args.get(2).cloned();
9408
9409                        // Extract unit and column name
9410                        let unit = Self::extract_interval_unit_str(&step);
9411                        let col_name = outer_alias_info
9412                            .as_ref()
9413                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
9414                            .unwrap_or_else(|| "value".to_string());
9415
9416                        if let Some(unit_str) = unit {
9417                            gda_info = Some((
9418                                idx,
9419                                col_name,
9420                                start_expr,
9421                                end_expr,
9422                                unit_str,
9423                                outer_alias_info,
9424                            ));
9425                            break;
9426                        }
9427                    }
9428                }
9429            }
9430        }
9431
9432        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
9433        else {
9434            return Ok(Expression::Select(sel));
9435        };
9436
9437        // Build the Snowflake subquery:
9438        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9439        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
9440
9441        // DATEDIFF(unit, start, end)
9442        let datediff = Expression::Function(Box::new(Function::new(
9443            "DATEDIFF".to_string(),
9444            vec![
9445                Expression::boxed_column(Column {
9446                    name: Identifier::new(&unit_str),
9447                    table: None,
9448                    join_mark: false,
9449                    trailing_comments: vec![],
9450                    span: None,
9451                    inferred_type: None,
9452                }),
9453                start_expr.clone(),
9454                end_expr.clone(),
9455            ],
9456        )));
9457        // DATEDIFF(...) + 1
9458        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9459            left: datediff,
9460            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9461            left_comments: vec![],
9462            operator_comments: vec![],
9463            trailing_comments: vec![],
9464            inferred_type: None,
9465        }));
9466
9467        let array_gen_range = Expression::Function(Box::new(Function::new(
9468            "ARRAY_GENERATE_RANGE".to_string(),
9469            vec![
9470                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9471                datediff_plus_one,
9472            ],
9473        )));
9474
9475        // TABLE(FLATTEN(INPUT => ...))
9476        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9477            name: Identifier::new("INPUT"),
9478            value: array_gen_range,
9479            separator: crate::expressions::NamedArgSeparator::DArrow,
9480        }));
9481        let flatten = Expression::Function(Box::new(Function::new(
9482            "FLATTEN".to_string(),
9483            vec![flatten_input],
9484        )));
9485
9486        // Determine alias name for the table: use outer alias or _t0
9487        let table_alias_name = outer_alias_info
9488            .as_ref()
9489            .map(|(name, _)| name.clone())
9490            .unwrap_or_else(|| "_t0".to_string());
9491
9492        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
9493        let table_func =
9494            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9495        let flatten_aliased = Expression::Alias(Box::new(Alias {
9496            this: table_func,
9497            alias: Identifier::new(&table_alias_name),
9498            column_aliases: vec![
9499                Identifier::new("seq"),
9500                Identifier::new("key"),
9501                Identifier::new("path"),
9502                Identifier::new("index"),
9503                Identifier::new(&col_name),
9504                Identifier::new("this"),
9505            ],
9506            alias_explicit_as: false,
9507            alias_keyword: None,
9508            pre_alias_comments: vec![],
9509            trailing_comments: vec![],
9510            inferred_type: None,
9511        }));
9512
9513        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9514        let dateadd_expr = Expression::Function(Box::new(Function::new(
9515            "DATEADD".to_string(),
9516            vec![
9517                Expression::boxed_column(Column {
9518                    name: Identifier::new(&unit_str),
9519                    table: None,
9520                    join_mark: false,
9521                    trailing_comments: vec![],
9522                    span: None,
9523                    inferred_type: None,
9524                }),
9525                Expression::Cast(Box::new(Cast {
9526                    this: Expression::boxed_column(Column {
9527                        name: Identifier::new(&col_name),
9528                        table: None,
9529                        join_mark: false,
9530                        trailing_comments: vec![],
9531                        span: None,
9532                        inferred_type: None,
9533                    }),
9534                    to: DataType::Int {
9535                        length: None,
9536                        integer_spelling: false,
9537                    },
9538                    trailing_comments: vec![],
9539                    double_colon_syntax: false,
9540                    format: None,
9541                    default: None,
9542                    inferred_type: None,
9543                })),
9544                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
9545                start_expr.clone(),
9546            ],
9547        )));
9548        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9549            this: dateadd_expr,
9550            alias: Identifier::new(&col_name),
9551            column_aliases: vec![],
9552            alias_explicit_as: false,
9553            alias_keyword: None,
9554            pre_alias_comments: vec![],
9555            trailing_comments: vec![],
9556            inferred_type: None,
9557        }));
9558
9559        // Build inner SELECT
9560        let mut inner_select = Select::new();
9561        inner_select.expressions = vec![dateadd_aliased];
9562        inner_select.from = Some(From {
9563            expressions: vec![flatten_aliased],
9564        });
9565
9566        let inner_select_expr = Expression::Select(Box::new(inner_select));
9567        let subquery = Expression::Subquery(Box::new(Subquery {
9568            this: inner_select_expr,
9569            alias: None,
9570            column_aliases: vec![],
9571            alias_explicit_as: false,
9572            alias_keyword: None,
9573            order_by: None,
9574            limit: None,
9575            offset: None,
9576            distribute_by: None,
9577            sort_by: None,
9578            cluster_by: None,
9579            lateral: false,
9580            modifiers_inside: false,
9581            trailing_comments: vec![],
9582            inferred_type: None,
9583        }));
9584
9585        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
9586        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
9587            Expression::Alias(Box::new(Alias {
9588                this: subquery,
9589                alias: Identifier::new(&alias_name),
9590                column_aliases: col_aliases,
9591                alias_explicit_as: false,
9592                alias_keyword: None,
9593                pre_alias_comments: vec![],
9594                trailing_comments: vec![],
9595                inferred_type: None,
9596            }))
9597        } else {
9598            subquery
9599        };
9600
9601        // Replace the FROM expression
9602        if let Some(ref mut from) = sel.from {
9603            from.expressions[from_idx] = replacement;
9604        }
9605
9606        Ok(Expression::Select(sel))
9607    }
9608
9609    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
9610    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
9611    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
9612    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
9613        use crate::expressions::*;
9614
9615        let start_expr = f.args[0].clone();
9616        let end_expr = f.args[1].clone();
9617        let step = f.args.get(2).cloned();
9618        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
9619        let col_name = "value";
9620
9621        // Build the inner subquery: same as try_transform_from_gda_snowflake
9622        let datediff = Expression::Function(Box::new(Function::new(
9623            "DATEDIFF".to_string(),
9624            vec![
9625                Expression::boxed_column(Column {
9626                    name: Identifier::new(&unit_str),
9627                    table: None,
9628                    join_mark: false,
9629                    trailing_comments: vec![],
9630                    span: None,
9631                    inferred_type: None,
9632                }),
9633                start_expr.clone(),
9634                end_expr.clone(),
9635            ],
9636        )));
9637        // DATEDIFF(...) + 1
9638        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9639            left: datediff,
9640            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9641            left_comments: vec![],
9642            operator_comments: vec![],
9643            trailing_comments: vec![],
9644            inferred_type: None,
9645        }));
9646
9647        let array_gen_range = Expression::Function(Box::new(Function::new(
9648            "ARRAY_GENERATE_RANGE".to_string(),
9649            vec![
9650                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9651                datediff_plus_one,
9652            ],
9653        )));
9654
9655        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9656            name: Identifier::new("INPUT"),
9657            value: array_gen_range,
9658            separator: crate::expressions::NamedArgSeparator::DArrow,
9659        }));
9660        let flatten = Expression::Function(Box::new(Function::new(
9661            "FLATTEN".to_string(),
9662            vec![flatten_input],
9663        )));
9664
9665        let table_func =
9666            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9667        let flatten_aliased = Expression::Alias(Box::new(Alias {
9668            this: table_func,
9669            alias: Identifier::new("_t0"),
9670            column_aliases: vec![
9671                Identifier::new("seq"),
9672                Identifier::new("key"),
9673                Identifier::new("path"),
9674                Identifier::new("index"),
9675                Identifier::new(col_name),
9676                Identifier::new("this"),
9677            ],
9678            alias_explicit_as: false,
9679            alias_keyword: None,
9680            pre_alias_comments: vec![],
9681            trailing_comments: vec![],
9682            inferred_type: None,
9683        }));
9684
9685        let dateadd_expr = Expression::Function(Box::new(Function::new(
9686            "DATEADD".to_string(),
9687            vec![
9688                Expression::boxed_column(Column {
9689                    name: Identifier::new(&unit_str),
9690                    table: None,
9691                    join_mark: false,
9692                    trailing_comments: vec![],
9693                    span: None,
9694                    inferred_type: None,
9695                }),
9696                Expression::Cast(Box::new(Cast {
9697                    this: Expression::boxed_column(Column {
9698                        name: Identifier::new(col_name),
9699                        table: None,
9700                        join_mark: false,
9701                        trailing_comments: vec![],
9702                        span: None,
9703                        inferred_type: None,
9704                    }),
9705                    to: DataType::Int {
9706                        length: None,
9707                        integer_spelling: false,
9708                    },
9709                    trailing_comments: vec![],
9710                    double_colon_syntax: false,
9711                    format: None,
9712                    default: None,
9713                    inferred_type: None,
9714                })),
9715                start_expr.clone(),
9716            ],
9717        )));
9718        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9719            this: dateadd_expr,
9720            alias: Identifier::new(col_name),
9721            column_aliases: vec![],
9722            alias_explicit_as: false,
9723            alias_keyword: None,
9724            pre_alias_comments: vec![],
9725            trailing_comments: vec![],
9726            inferred_type: None,
9727        }));
9728
9729        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
9730        let mut inner_select = Select::new();
9731        inner_select.expressions = vec![dateadd_aliased];
9732        inner_select.from = Some(From {
9733            expressions: vec![flatten_aliased],
9734        });
9735
9736        // Wrap in subquery for the inner part
9737        let inner_subquery = Expression::Subquery(Box::new(Subquery {
9738            this: Expression::Select(Box::new(inner_select)),
9739            alias: None,
9740            column_aliases: vec![],
9741            alias_explicit_as: false,
9742            alias_keyword: None,
9743            order_by: None,
9744            limit: None,
9745            offset: None,
9746            distribute_by: None,
9747            sort_by: None,
9748            cluster_by: None,
9749            lateral: false,
9750            modifiers_inside: false,
9751            trailing_comments: vec![],
9752            inferred_type: None,
9753        }));
9754
9755        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
9756        let star = Expression::Star(Star {
9757            table: None,
9758            except: None,
9759            replace: None,
9760            rename: None,
9761            trailing_comments: vec![],
9762            span: None,
9763        });
9764        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
9765            this: star,
9766            distinct: false,
9767            filter: None,
9768            order_by: vec![],
9769            name: Some("ARRAY_AGG".to_string()),
9770            ignore_nulls: None,
9771            having_max: None,
9772            limit: None,
9773            inferred_type: None,
9774        }));
9775
9776        let mut outer_select = Select::new();
9777        outer_select.expressions = vec![array_agg];
9778        outer_select.from = Some(From {
9779            expressions: vec![inner_subquery],
9780        });
9781
9782        // Wrap in a subquery
9783        let outer_subquery = Expression::Subquery(Box::new(Subquery {
9784            this: Expression::Select(Box::new(outer_select)),
9785            alias: None,
9786            column_aliases: vec![],
9787            alias_explicit_as: false,
9788            alias_keyword: None,
9789            order_by: None,
9790            limit: None,
9791            offset: None,
9792            distribute_by: None,
9793            sort_by: None,
9794            cluster_by: None,
9795            lateral: false,
9796            modifiers_inside: false,
9797            trailing_comments: vec![],
9798            inferred_type: None,
9799        }));
9800
9801        // ARRAY_SIZE(subquery)
9802        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
9803            outer_subquery,
9804        ))))
9805    }
9806
9807    /// Extract interval unit string from an optional step expression.
9808    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
9809        use crate::expressions::*;
9810        if let Some(Expression::Interval(ref iv)) = step {
9811            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9812                return Some(format!("{:?}", unit).to_ascii_uppercase());
9813            }
9814            if let Some(ref this) = iv.this {
9815                if let Expression::Literal(lit) = this {
9816                    if let Literal::String(ref s) = lit.as_ref() {
9817                        let parts: Vec<&str> = s.split_whitespace().collect();
9818                        if parts.len() == 2 {
9819                            return Some(parts[1].to_ascii_uppercase());
9820                        } else if parts.len() == 1 {
9821                            let upper = parts[0].to_ascii_uppercase();
9822                            if matches!(
9823                                upper.as_str(),
9824                                "YEAR"
9825                                    | "QUARTER"
9826                                    | "MONTH"
9827                                    | "WEEK"
9828                                    | "DAY"
9829                                    | "HOUR"
9830                                    | "MINUTE"
9831                                    | "SECOND"
9832                            ) {
9833                                return Some(upper);
9834                            }
9835                        }
9836                    }
9837                }
9838            }
9839        }
9840        // Default to DAY if no step or no interval
9841        if step.is_none() {
9842            return Some("DAY".to_string());
9843        }
9844        None
9845    }
9846
9847    fn normalize_snowflake_pretty(mut sql: String) -> String {
9848        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
9849            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
9850        {
9851            sql = sql.replace(
9852                "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')",
9853                "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    )",
9854            );
9855
9856            sql = sql.replace(
9857                "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)",
9858                "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)",
9859            );
9860
9861            sql = sql.replace(
9862                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
9863                "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  )",
9864            );
9865        }
9866
9867        sql
9868    }
9869
9870    #[cfg(feature = "transpile")]
9871    fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
9872        match expr {
9873            Expression::Values(values) => Self::tsql_values_as_select(*values),
9874            Expression::Union(mut union) => {
9875                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9876                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9877                union.left = Self::wrap_tsql_values_set_operand(left);
9878                union.right = Self::wrap_tsql_values_set_operand(right);
9879                Expression::Union(union)
9880            }
9881            Expression::Intersect(mut intersect) => {
9882                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9883                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9884                intersect.left = Self::wrap_tsql_values_set_operand(left);
9885                intersect.right = Self::wrap_tsql_values_set_operand(right);
9886                Expression::Intersect(intersect)
9887            }
9888            Expression::Except(mut except) => {
9889                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9890                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9891                except.left = Self::wrap_tsql_values_set_operand(left);
9892                except.right = Self::wrap_tsql_values_set_operand(right);
9893                Expression::Except(except)
9894            }
9895            other => other,
9896        }
9897    }
9898
9899    #[cfg(feature = "transpile")]
9900    fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
9901        match expr {
9902            Expression::Values(values) => Self::tsql_values_as_select(*values),
9903            Expression::Select(mut select)
9904                if Self::is_parser_wrapped_values_set_operand(&select) =>
9905            {
9906                let mut from = select.from.take().expect("checked as present");
9907                let Expression::Values(values) = from
9908                    .expressions
9909                    .pop()
9910                    .expect("checked as a single VALUES source")
9911                else {
9912                    unreachable!("checked as a VALUES source");
9913                };
9914                Self::tsql_values_as_select(*values)
9915            }
9916            Expression::Annotated(mut annotated) => {
9917                annotated.this = Self::wrap_tsql_values_set_operand(annotated.this);
9918                Expression::Annotated(annotated)
9919            }
9920            Expression::Union(mut union) => {
9921                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9922                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9923                union.left = Self::wrap_tsql_values_set_operand(left);
9924                union.right = Self::wrap_tsql_values_set_operand(right);
9925                Expression::Union(union)
9926            }
9927            Expression::Intersect(mut intersect) => {
9928                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9929                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9930                intersect.left = Self::wrap_tsql_values_set_operand(left);
9931                intersect.right = Self::wrap_tsql_values_set_operand(right);
9932                Expression::Intersect(intersect)
9933            }
9934            Expression::Except(mut except) => {
9935                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9936                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9937                except.left = Self::wrap_tsql_values_set_operand(left);
9938                except.right = Self::wrap_tsql_values_set_operand(right);
9939                Expression::Except(except)
9940            }
9941            other => other,
9942        }
9943    }
9944
9945    #[cfg(feature = "transpile")]
9946    fn is_parser_wrapped_values_set_operand(select: &Select) -> bool {
9947        let Some(from) = &select.from else {
9948            return false;
9949        };
9950        let [Expression::Values(values)] = from.expressions.as_slice() else {
9951            return false;
9952        };
9953        if !values
9954            .alias
9955            .as_ref()
9956            .is_some_and(|alias| alias.name == "_values")
9957        {
9958            return false;
9959        }
9960
9961        let mut parser_wrapper = Select::new();
9962        parser_wrapper.expressions = vec![Expression::star()];
9963        parser_wrapper.from = Some(from.clone());
9964        select == &parser_wrapper
9965    }
9966
9967    #[cfg(feature = "transpile")]
9968    fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
9969        let column_aliases = if values.column_aliases.is_empty() {
9970            let column_count = values
9971                .expressions
9972                .first()
9973                .map(|row| row.expressions.len())
9974                .unwrap_or(0);
9975            (1..=column_count)
9976                .map(|index| Identifier::new(format!("column{index}")))
9977                .collect()
9978        } else {
9979            std::mem::take(&mut values.column_aliases)
9980        };
9981
9982        values.alias = None;
9983
9984        let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
9985            this: Expression::Values(Box::new(values)),
9986            alias: Some(Identifier::new("_v")),
9987            column_aliases,
9988            alias_explicit_as: false,
9989            alias_keyword: None,
9990            order_by: None,
9991            limit: None,
9992            offset: None,
9993            distribute_by: None,
9994            sort_by: None,
9995            cluster_by: None,
9996            lateral: false,
9997            modifiers_inside: false,
9998            trailing_comments: Vec::new(),
9999            inferred_type: None,
10000        }));
10001
10002        let mut select = crate::expressions::Select::new();
10003        select.expressions = vec![Expression::star()];
10004        select.from = Some(From {
10005            expressions: vec![values_subquery],
10006        });
10007
10008        Expression::Select(Box::new(select))
10009    }
10010
10011    fn extract_interval_parts(
10012        interval_expr: &Expression,
10013    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
10014        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
10015
10016        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
10017            match unit.trim().to_ascii_uppercase().as_str() {
10018                "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
10019                "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
10020                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
10021                "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
10022                    Some(IntervalUnit::Week)
10023                }
10024                "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
10025                "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
10026                "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
10027                "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
10028                "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
10029                | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
10030                    Some(IntervalUnit::Millisecond)
10031                }
10032                "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
10033                | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
10034                "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
10035                | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
10036                _ => None,
10037            }
10038        }
10039
10040        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
10041            let mut parts = s.split_whitespace();
10042            let value = parts.next()?;
10043            let unit = unit_from_str(parts.next()?)?;
10044            Some((
10045                Expression::Literal(Box::new(Literal::String(value.to_string()))),
10046                unit,
10047            ))
10048        }
10049
10050        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
10051            match unit {
10052                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
10053                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
10054                    Expression::Day(_) => Some(IntervalUnit::Day),
10055                    Expression::Month(_) => Some(IntervalUnit::Month),
10056                    Expression::Year(_) => Some(IntervalUnit::Year),
10057                    Expression::Identifier(id) => unit_from_str(&id.name),
10058                    Expression::Var(v) => unit_from_str(&v.this),
10059                    Expression::Column(col) => unit_from_str(&col.name.name),
10060                    _ => None,
10061                },
10062                _ => None,
10063            }
10064        }
10065
10066        match interval_expr {
10067            Expression::Interval(iv) => {
10068                let val = iv.this.clone().unwrap_or(Expression::number(0));
10069                if let Expression::Literal(lit) = &val {
10070                    if let Literal::String(s) = lit.as_ref() {
10071                        if let Some(parts) = parts_from_literal_string(s) {
10072                            return Some(parts);
10073                        }
10074                    }
10075                }
10076                let unit = iv
10077                    .unit
10078                    .as_ref()
10079                    .and_then(unit_from_spec)
10080                    .unwrap_or(IntervalUnit::Day);
10081                Some((val, unit))
10082            }
10083            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
10084                if let Expression::Literal(lit) = &cast.this {
10085                    if let Literal::String(s) = lit.as_ref() {
10086                        if let Some(parts) = parts_from_literal_string(s) {
10087                            return Some(parts);
10088                        }
10089                    }
10090                }
10091                let unit = match &cast.to {
10092                    DataType::Interval {
10093                        unit: Some(unit), ..
10094                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
10095                    _ => IntervalUnit::Day,
10096                };
10097                Some((cast.this.clone(), unit))
10098            }
10099            _ => None,
10100        }
10101    }
10102
10103    fn data_type_is_interval(dt: &DataType) -> bool {
10104        match dt {
10105            DataType::Interval { .. } => true,
10106            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
10107            _ => false,
10108        }
10109    }
10110
10111    fn node_is_interval_cast(node: &Expression) -> bool {
10112        match node {
10113            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10114                Self::data_type_is_interval(&c.to)
10115            }
10116            _ => false,
10117        }
10118    }
10119
10120    fn reject_tsql_interval_casts(
10121        expr: &Expression,
10122        target: DialectType,
10123        opts: &TranspileOptions,
10124    ) -> Result<()> {
10125        if !matches!(
10126            opts.unsupported_level,
10127            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
10128        ) {
10129            return Ok(());
10130        }
10131
10132        if expr.dfs().any(Self::node_is_interval_cast) {
10133            return Err(crate::error::Error::unsupported(
10134                "INTERVAL casts",
10135                target.to_string(),
10136            ));
10137        }
10138
10139        Ok(())
10140    }
10141
10142    fn tsql_varchar_max_type() -> DataType {
10143        DataType::Custom {
10144            name: "VARCHAR(MAX)".to_string(),
10145        }
10146    }
10147
10148    fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
10149        transform_recursive(expr, &|e| match e {
10150            Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10151                cast.to = Self::tsql_varchar_max_type();
10152                cast.double_colon_syntax = false;
10153                Ok(Expression::Cast(cast))
10154            }
10155            Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10156                cast.to = Self::tsql_varchar_max_type();
10157                cast.double_colon_syntax = false;
10158                Ok(Expression::TryCast(cast))
10159            }
10160            Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10161                cast.to = Self::tsql_varchar_max_type();
10162                cast.double_colon_syntax = false;
10163                Ok(Expression::SafeCast(cast))
10164            }
10165            _ => Ok(e),
10166        })
10167    }
10168
10169    fn rewrite_tsql_interval_arithmetic_legacy(
10170        expr: &Expression,
10171        source: DialectType,
10172    ) -> Option<Expression> {
10173        match expr {
10174            Expression::Add(op) => {
10175                if Self::extract_interval_parts(&op.right).is_some() {
10176                    return Some(Self::build_tsql_dateadd_from_interval(
10177                        op.left.clone(),
10178                        &op.right,
10179                        false,
10180                    ));
10181                }
10182
10183                if Self::is_postgres_family_source(source) {
10184                    if Self::is_explicit_date_expr(&op.left)
10185                        && Self::is_integer_day_offset_expr(&op.right)
10186                    {
10187                        return Some(Self::build_tsql_dateadd_days(
10188                            op.left.clone(),
10189                            op.right.clone(),
10190                            false,
10191                        ));
10192                    }
10193
10194                    if Self::is_integer_day_offset_expr(&op.left)
10195                        && Self::is_explicit_date_expr(&op.right)
10196                    {
10197                        return Some(Self::build_tsql_dateadd_days(
10198                            op.right.clone(),
10199                            op.left.clone(),
10200                            false,
10201                        ));
10202                    }
10203                }
10204
10205                None
10206            }
10207            Expression::Sub(op) => {
10208                if Self::extract_interval_parts(&op.right).is_some() {
10209                    return Some(Self::build_tsql_dateadd_from_interval(
10210                        op.left.clone(),
10211                        &op.right,
10212                        true,
10213                    ));
10214                }
10215
10216                if Self::is_postgres_family_source(source) {
10217                    if Self::is_explicit_date_expr(&op.left)
10218                        && Self::is_explicit_date_expr(&op.right)
10219                    {
10220                        return Some(Self::build_tsql_datediff_days(
10221                            op.right.clone(),
10222                            op.left.clone(),
10223                        ));
10224                    }
10225
10226                    if Self::is_explicit_date_expr(&op.left)
10227                        && Self::is_integer_day_offset_expr(&op.right)
10228                    {
10229                        return Some(Self::build_tsql_dateadd_days(
10230                            op.left.clone(),
10231                            op.right.clone(),
10232                            true,
10233                        ));
10234                    }
10235                }
10236
10237                None
10238            }
10239            _ => None,
10240        }
10241    }
10242
10243    fn is_postgres_family_source(source: DialectType) -> bool {
10244        matches!(
10245            source,
10246            DialectType::PostgreSQL
10247                | DialectType::Redshift
10248                | DialectType::Materialize
10249                | DialectType::RisingWave
10250                | DialectType::CockroachDB
10251        )
10252    }
10253
10254    fn is_explicit_date_expr(expr: &Expression) -> bool {
10255        use crate::expressions::Literal;
10256
10257        match expr {
10258            Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
10259            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10260                matches!(c.to, crate::expressions::DataType::Date)
10261            }
10262            Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
10263            Expression::CurrentDate(_)
10264            | Expression::Date(_)
10265            | Expression::MakeDate(_)
10266            | Expression::ToDate(_)
10267            | Expression::DateStrToDate(_) => true,
10268            _ => false,
10269        }
10270    }
10271
10272    fn is_integer_day_offset_expr(expr: &Expression) -> bool {
10273        use crate::expressions::Literal;
10274
10275        match expr {
10276            Expression::Literal(lit) => match lit.as_ref() {
10277                Literal::Number(n) => n.parse::<i64>().is_ok(),
10278                _ => false,
10279            },
10280            Expression::Parameter(_) | Expression::Placeholder(_) => true,
10281            Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
10282            Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
10283            _ => false,
10284        }
10285    }
10286
10287    fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
10288        Expression::Function(Box::new(Function::new(
10289            "DATEDIFF".to_string(),
10290            vec![Expression::Identifier(Identifier::new("DAY")), start, end],
10291        )))
10292    }
10293
10294    fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
10295        Expression::Function(Box::new(Function::new(
10296            "DATEADD".to_string(),
10297            vec![
10298                Expression::Identifier(Identifier::new("DAY")),
10299                Self::tsql_dateadd_amount(amount, subtract),
10300                date,
10301            ],
10302        )))
10303    }
10304
10305    fn build_tsql_dateadd_from_interval(
10306        date: Expression,
10307        interval: &Expression,
10308        subtract: bool,
10309    ) -> Expression {
10310        let (value, unit) = Self::extract_interval_parts(interval)
10311            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
10312        let unit = normalization::temporal::interval_unit_to_string(&unit);
10313        let amount = Self::tsql_dateadd_amount(value, subtract);
10314
10315        Expression::Function(Box::new(Function::new(
10316            "DATEADD".to_string(),
10317            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
10318        )))
10319    }
10320
10321    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
10322        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
10323
10324        fn numeric_literal_value(value: &Expression) -> Option<&str> {
10325            match value {
10326                Expression::Literal(lit) => match lit.as_ref() {
10327                    crate::expressions::Literal::Number(n)
10328                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
10329                    _ => None,
10330                },
10331                _ => None,
10332            }
10333        }
10334
10335        fn colon_parameter(value: &Expression) -> Option<Expression> {
10336            let Expression::Literal(lit) = value else {
10337                return None;
10338            };
10339            let crate::expressions::Literal::String(s) = lit.as_ref() else {
10340                return None;
10341            };
10342            let name = s.strip_prefix(':')?;
10343            if name.is_empty()
10344                || !name
10345                    .chars()
10346                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
10347            {
10348                return None;
10349            }
10350
10351            Some(Expression::Parameter(Box::new(Parameter {
10352                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
10353                    None
10354                } else {
10355                    Some(name.to_string())
10356                },
10357                index: name.parse::<u32>().ok(),
10358                style: ParameterStyle::Colon,
10359                quoted: false,
10360                string_quoted: false,
10361                expression: None,
10362            })))
10363        }
10364
10365        let value = colon_parameter(&value).unwrap_or(value);
10366
10367        if let Some(n) = numeric_literal_value(&value) {
10368            if let Ok(parsed) = n.parse::<f64>() {
10369                let normalized = if negate { -parsed } else { parsed };
10370                let rendered = if normalized.fract() == 0.0 {
10371                    format!("{}", normalized as i64)
10372                } else {
10373                    normalized.to_string()
10374                };
10375                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
10376                    rendered,
10377                )));
10378            }
10379        }
10380
10381        if !negate {
10382            return value;
10383        }
10384
10385        match value {
10386            Expression::Neg(op) => op.this,
10387            other => Expression::Neg(Box::new(UnaryOp {
10388                this: other,
10389                inferred_type: None,
10390            })),
10391        }
10392    }
10393
10394    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
10395    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
10396    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
10397    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
10398}
10399
10400#[cfg(test)]
10401mod tests {
10402    use super::*;
10403
10404    #[cfg(all(
10405        feature = "transpile",
10406        feature = "dialect-hana",
10407        feature = "dialect-duckdb",
10408        feature = "dialect-trino"
10409    ))]
10410    #[test]
10411    fn hana_validation_and_generation_agree_after_ast_serialization() {
10412        let hana = Dialect::get(DialectType::HANA);
10413        for (sql, duckdb, trino) in [
10414            ("SELECT COALESCE(IFNULL(x, 0), 1) FROM t", true, true),
10415            ("SELECT LOCATE('abcabc', 'bc', 2)", true, true),
10416            ("SELECT LOCATE('abcabc', 'bc', 1, 2)", false, true),
10417            ("SELECT SUBSTRING('abc', -1, -2)", true, true),
10418            ("SELECT TO_DECIMAL(12.345, 5, 2)", true, true),
10419            (
10420                "SELECT CAST(12.345 AS DECIMAL(5)), CAST(-12.9 AS INT)",
10421                true,
10422                true,
10423            ),
10424            ("SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD')", true, true),
10425            (
10426                "SELECT TO_TIMESTAMP('2024-01-15', 'YYYY-MM-DD')",
10427                false,
10428                true,
10429            ),
10430            (
10431                "SELECT TO_VARCHAR(DATE '2024-01-15', 'YYYY-MM-DD')",
10432                true,
10433                true,
10434            ),
10435            ("SELECT CURRENT_UTCDATE", true, true),
10436            (
10437                "SELECT CURRENT_UTCTIMESTAMP(7), CURRENT_UTCTIME",
10438                false,
10439                true,
10440            ),
10441            ("CREATE TABLE t (n INT, xs INT ARRAY)", true, true),
10442            ("CREATE TABLE t (n SMALLDECIMAL ARRAY)", false, false),
10443            ("ALTER TABLE t ADD (n SMALLDECIMAL ARRAY)", false, false),
10444            ("ALTER TABLE t ADD (n INT ARRAY)", true, true),
10445            ("SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t", false, false),
10446            ("SELECT COALESCE(CAST(x AS INT), 0) FROM t", false, false),
10447            (
10448                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10449                false,
10450                false,
10451            ),
10452            (
10453                "SELECT COALESCE(SUBSTR_REGEXPR('a' IN s), 'x') FROM t",
10454                false,
10455                false,
10456            ),
10457            (
10458                "SELECT COALESCE(STRING_AGG(x, ','), '') FROM t",
10459                false,
10460                false,
10461            ),
10462            (
10463                "CREATE TABLE t (n INT DEFAULT ADD_DAYS(d, 1))",
10464                false,
10465                false,
10466            ),
10467            ("SELECT * FROM t FOR JSON", false, false),
10468            ("CREATE COLUMN TABLE t (n INT)", false, false),
10469            ("SELECT * FROM t WITH HINT (NO_INLINE)", false, false),
10470        ] {
10471            let ast = hana.parse(sql).unwrap().remove(0);
10472            let json = serde_json::to_string(&ast).unwrap();
10473            let ast: Expression = serde_json::from_str(&json).unwrap();
10474            for (target, supported) in [(DialectType::DuckDB, duckdb), (DialectType::Trino, trino)]
10475            {
10476                assert_eq!(
10477                    Dialect::reject_hana_source_semantics(&ast, DialectType::HANA, target).is_ok(),
10478                    supported,
10479                    "validation: {sql} -> {target}"
10480                );
10481                assert_eq!(
10482                    Dialect::get(target).generate(&ast).is_ok(),
10483                    supported,
10484                    "generation: {sql} -> {target}"
10485                );
10486            }
10487            assert!(Dialect::reject_hana_source_semantics(
10488                &ast,
10489                DialectType::HANA,
10490                DialectType::HANA
10491            )
10492            .is_ok());
10493        }
10494    }
10495
10496    #[cfg(all(
10497        feature = "transpile",
10498        feature = "dialect-hana",
10499        feature = "dialect-duckdb"
10500    ))]
10501    #[test]
10502    fn hana_rejects_source_semantics_before_preprocessing_can_erase_them() {
10503        use std::sync::atomic::{AtomicBool, Ordering};
10504        let called = Arc::new(AtomicBool::new(false));
10505        let seen = Arc::clone(&called);
10506        let mut hana = Dialect::get(DialectType::HANA);
10507        hana.custom_preprocess = Some(Box::new(move |_| {
10508            seen.store(true, Ordering::Relaxed);
10509            Ok(Expression::number(1))
10510        }));
10511        for level in [
10512            UnsupportedLevel::Ignore,
10513            UnsupportedLevel::Warn,
10514            UnsupportedLevel::Raise,
10515            UnsupportedLevel::Immediate,
10516        ] {
10517            for sql in [
10518                "SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t",
10519                "SELECT COALESCE(CAST(x AS INT), 0) FROM t",
10520                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10521                "CREATE TABLE t (x SMALLDECIMAL ARRAY)",
10522                "ALTER TABLE t ADD (x SMALLDECIMAL ARRAY)",
10523                "SELECT CAST(xs AS SMALLDECIMAL ARRAY) FROM t",
10524                "CREATE FUNCTION f(x SMALLDECIMAL ARRAY) RETURNS INT AS 1",
10525                "SELECT * FROM t FOR JSON",
10526                "SELECT * FROM t WITH HINT (NO_INLINE)",
10527            ] {
10528                assert!(
10529                    hana.transpile_with(
10530                        sql,
10531                        DialectType::DuckDB,
10532                        TranspileOptions::default().with_unsupported_level(level)
10533                    )
10534                    .is_err(),
10535                    "{sql}"
10536                );
10537                assert!(
10538                    !called.load(Ordering::Relaxed),
10539                    "preprocessing must not erase unsupported input: {sql}"
10540                );
10541            }
10542        }
10543        let mut target = Dialect::get(DialectType::DuckDB);
10544        Arc::make_mut(&mut target.generator_config).dialect = Some(DialectType::Generic);
10545        assert!(hana
10546            .transpile("SELECT LOCATE('abc', 'b')", &target)
10547            .is_err());
10548        assert!(!called.load(Ordering::Relaxed));
10549        assert!(hana
10550            .transpile("SELECT COALESCE(x, 0) FROM t", DialectType::DuckDB)
10551            .is_ok());
10552        assert!(called.load(Ordering::Relaxed));
10553    }
10554
10555    #[test]
10556    fn built_in_dialect_instances_share_tokenizer_config() {
10557        let first = Dialect::get(DialectType::PostgreSQL);
10558        let second = Dialect::get(DialectType::PostgreSQL);
10559
10560        assert!(first.tokenizer.shares_config_with(&second.tokenizer));
10561    }
10562
10563    #[test]
10564    fn test_dialect_type_from_str() {
10565        assert_eq!(
10566            "postgres".parse::<DialectType>().unwrap(),
10567            DialectType::PostgreSQL
10568        );
10569        assert_eq!(
10570            "postgresql".parse::<DialectType>().unwrap(),
10571            DialectType::PostgreSQL
10572        );
10573        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
10574        assert_eq!(
10575            "bigquery".parse::<DialectType>().unwrap(),
10576            DialectType::BigQuery
10577        );
10578    }
10579
10580    #[test]
10581    fn test_basic_transpile() {
10582        let dialect = Dialect::get(DialectType::Generic);
10583        let result = dialect
10584            .transpile("SELECT 1", DialectType::PostgreSQL)
10585            .unwrap();
10586        assert_eq!(result.len(), 1);
10587        assert_eq!(result[0], "SELECT 1");
10588    }
10589
10590    #[test]
10591    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
10592        let sqlite = Dialect::get(DialectType::SQLite);
10593        let result = sqlite
10594            .transpile(
10595                r#"CREATE TABLE "_collections" (
10596                    "type" TEXT DEFAULT "base" NOT NULL,
10597                    "fields" JSON DEFAULT "[]" NOT NULL,
10598                    "options" JSON DEFAULT "{}" NOT NULL
10599                )"#,
10600                DialectType::PostgreSQL,
10601            )
10602            .unwrap();
10603
10604        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
10605        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
10606        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
10607    }
10608
10609    #[test]
10610    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
10611        let sqlite = Dialect::get(DialectType::SQLite);
10612        let result = sqlite
10613            .transpile(
10614                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
10615                DialectType::SQLite,
10616            )
10617            .unwrap();
10618
10619        assert_eq!(
10620            result[0],
10621            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
10622        );
10623    }
10624
10625    #[test]
10626    fn test_function_transformation_mysql() {
10627        // NVL should be transformed to IFNULL in MySQL
10628        let dialect = Dialect::get(DialectType::Generic);
10629        let result = dialect
10630            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
10631            .unwrap();
10632        assert_eq!(result[0], "SELECT IFNULL(a, b)");
10633    }
10634
10635    #[test]
10636    fn test_get_path_duckdb() {
10637        // Test: step by step
10638        let snowflake = Dialect::get(DialectType::Snowflake);
10639
10640        // Step 1: Parse and check what Snowflake produces as intermediate
10641        let result_sf_sf = snowflake
10642            .transpile(
10643                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10644                DialectType::Snowflake,
10645            )
10646            .unwrap();
10647        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
10648
10649        // Step 2: DuckDB target
10650        let result_sf_dk = snowflake
10651            .transpile(
10652                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10653                DialectType::DuckDB,
10654            )
10655            .unwrap();
10656        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
10657
10658        // Step 3: GET_PATH directly
10659        let result_gp = snowflake
10660            .transpile(
10661                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
10662                DialectType::DuckDB,
10663            )
10664            .unwrap();
10665        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
10666    }
10667
10668    #[test]
10669    fn test_function_transformation_postgres() {
10670        // IFNULL should be transformed to COALESCE in PostgreSQL
10671        let dialect = Dialect::get(DialectType::Generic);
10672        let result = dialect
10673            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
10674            .unwrap();
10675        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10676
10677        // NVL should also be transformed to COALESCE
10678        let result = dialect
10679            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
10680            .unwrap();
10681        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10682    }
10683
10684    #[test]
10685    fn test_hive_cast_to_trycast() {
10686        // Hive CAST should become TRY_CAST for targets that support it
10687        let hive = Dialect::get(DialectType::Hive);
10688        let result = hive
10689            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
10690            .unwrap();
10691        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
10692
10693        let result = hive
10694            .transpile("CAST(1 AS INT)", DialectType::Presto)
10695            .unwrap();
10696        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
10697    }
10698
10699    #[test]
10700    fn test_hive_array_identity() {
10701        // Hive ARRAY<DATE> should preserve angle bracket syntax
10702        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')";
10703        let hive = Dialect::get(DialectType::Hive);
10704
10705        // Test via transpile (this works)
10706        let result = hive.transpile(sql, DialectType::Hive).unwrap();
10707        eprintln!("Hive ARRAY via transpile: {}", result[0]);
10708        assert!(
10709            result[0].contains("ARRAY<DATE>"),
10710            "transpile: Expected ARRAY<DATE>, got: {}",
10711            result[0]
10712        );
10713
10714        // Test via parse -> transform -> generate (identity test path)
10715        let ast = hive.parse(sql).unwrap();
10716        let transformed = hive.transform(ast[0].clone()).unwrap();
10717        let output = hive.generate(&transformed).unwrap();
10718        eprintln!("Hive ARRAY via identity path: {}", output);
10719        assert!(
10720            output.contains("ARRAY<DATE>"),
10721            "identity path: Expected ARRAY<DATE>, got: {}",
10722            output
10723        );
10724    }
10725
10726    #[test]
10727    fn test_starrocks_delete_between_expansion() {
10728        // StarRocks doesn't support BETWEEN in DELETE statements
10729        let dialect = Dialect::get(DialectType::Generic);
10730
10731        // BETWEEN should be expanded to >= AND <= in DELETE
10732        let result = dialect
10733            .transpile(
10734                "DELETE FROM t WHERE a BETWEEN b AND c",
10735                DialectType::StarRocks,
10736            )
10737            .unwrap();
10738        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
10739
10740        // NOT BETWEEN should be expanded to < OR > in DELETE
10741        let result = dialect
10742            .transpile(
10743                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
10744                DialectType::StarRocks,
10745            )
10746            .unwrap();
10747        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
10748
10749        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
10750        let result = dialect
10751            .transpile(
10752                "SELECT * FROM t WHERE a BETWEEN b AND c",
10753                DialectType::StarRocks,
10754            )
10755            .unwrap();
10756        assert!(
10757            result[0].contains("BETWEEN"),
10758            "BETWEEN should be preserved in SELECT"
10759        );
10760    }
10761
10762    #[test]
10763    fn test_snowflake_ltrim_rtrim_parse() {
10764        let sf = Dialect::get(DialectType::Snowflake);
10765        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
10766        let result = sf.transpile(sql, DialectType::DuckDB);
10767        match &result {
10768            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
10769            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
10770        }
10771        assert!(
10772            result.is_ok(),
10773            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
10774            result.err()
10775        );
10776    }
10777
10778    #[test]
10779    fn test_duckdb_count_if_parse() {
10780        let duck = Dialect::get(DialectType::DuckDB);
10781        let sql = "COUNT_IF(x)";
10782        let result = duck.transpile(sql, DialectType::DuckDB);
10783        match &result {
10784            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
10785            Err(e) => eprintln!("COUNT_IF error: {}", e),
10786        }
10787        assert!(
10788            result.is_ok(),
10789            "Expected successful parse of COUNT_IF(x), got error: {:?}",
10790            result.err()
10791        );
10792    }
10793
10794    #[test]
10795    fn test_tsql_cast_tinyint_parse() {
10796        let tsql = Dialect::get(DialectType::TSQL);
10797        let sql = "CAST(X AS TINYINT)";
10798        let result = tsql.transpile(sql, DialectType::DuckDB);
10799        match &result {
10800            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
10801            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
10802        }
10803        assert!(
10804            result.is_ok(),
10805            "Expected successful transpile, got error: {:?}",
10806            result.err()
10807        );
10808    }
10809
10810    #[test]
10811    fn test_pg_hash_bitwise_xor() {
10812        let dialect = Dialect::get(DialectType::PostgreSQL);
10813        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
10814        assert_eq!(result[0], "x # y");
10815    }
10816
10817    #[test]
10818    fn test_pg_array_to_duckdb() {
10819        let dialect = Dialect::get(DialectType::PostgreSQL);
10820        let result = dialect
10821            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
10822            .unwrap();
10823        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
10824    }
10825
10826    #[test]
10827    fn test_array_remove_bigquery() {
10828        let dialect = Dialect::get(DialectType::Generic);
10829        let result = dialect
10830            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
10831            .unwrap();
10832        assert_eq!(
10833            result[0],
10834            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
10835        );
10836    }
10837
10838    #[test]
10839    fn test_map_clickhouse_case() {
10840        let dialect = Dialect::get(DialectType::Generic);
10841        let parsed = dialect
10842            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
10843            .unwrap();
10844        eprintln!("MAP parsed: {:?}", parsed);
10845        let result = dialect
10846            .transpile(
10847                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
10848                DialectType::ClickHouse,
10849            )
10850            .unwrap();
10851        eprintln!("MAP result: {}", result[0]);
10852    }
10853
10854    #[test]
10855    fn test_generate_date_array_presto() {
10856        let dialect = Dialect::get(DialectType::Generic);
10857        let result = dialect.transpile(
10858            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10859            DialectType::Presto,
10860        ).unwrap();
10861        eprintln!("GDA -> Presto: {}", result[0]);
10862        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
10863    }
10864
10865    #[test]
10866    fn test_generate_date_array_postgres() {
10867        let dialect = Dialect::get(DialectType::Generic);
10868        let result = dialect.transpile(
10869            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10870            DialectType::PostgreSQL,
10871        ).unwrap();
10872        eprintln!("GDA -> PostgreSQL: {}", result[0]);
10873    }
10874
10875    #[test]
10876    fn test_generate_date_array_snowflake() {
10877        let dialect = Dialect::get(DialectType::Generic);
10878        let result = dialect
10879            .transpile(
10880                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10881                DialectType::Snowflake,
10882            )
10883            .unwrap();
10884        eprintln!("GDA -> Snowflake: {}", result[0]);
10885    }
10886
10887    #[test]
10888    fn test_array_length_generate_date_array_snowflake() {
10889        let dialect = Dialect::get(DialectType::Generic);
10890        let result = dialect.transpile(
10891            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10892            DialectType::Snowflake,
10893        ).unwrap();
10894        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
10895    }
10896
10897    #[test]
10898    fn test_generate_date_array_mysql() {
10899        let dialect = Dialect::get(DialectType::Generic);
10900        let result = dialect.transpile(
10901            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10902            DialectType::MySQL,
10903        ).unwrap();
10904        eprintln!("GDA -> MySQL: {}", result[0]);
10905    }
10906
10907    #[test]
10908    fn test_generate_date_array_redshift() {
10909        let dialect = Dialect::get(DialectType::Generic);
10910        let result = dialect.transpile(
10911            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10912            DialectType::Redshift,
10913        ).unwrap();
10914        eprintln!("GDA -> Redshift: {}", result[0]);
10915    }
10916
10917    #[test]
10918    fn test_generate_date_array_tsql() {
10919        let dialect = Dialect::get(DialectType::Generic);
10920        let result = dialect.transpile(
10921            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10922            DialectType::TSQL,
10923        ).unwrap();
10924        eprintln!("GDA -> TSQL: {}", result[0]);
10925    }
10926
10927    #[test]
10928    fn test_struct_colon_syntax() {
10929        let dialect = Dialect::get(DialectType::Generic);
10930        // Test without colon first
10931        let result = dialect.transpile(
10932            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
10933            DialectType::ClickHouse,
10934        );
10935        match result {
10936            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
10937            Err(e) => eprintln!("STRUCT no colon error: {}", e),
10938        }
10939        // Now test with colon
10940        let result = dialect.transpile(
10941            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
10942            DialectType::ClickHouse,
10943        );
10944        match result {
10945            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
10946            Err(e) => eprintln!("STRUCT colon error: {}", e),
10947        }
10948    }
10949
10950    #[test]
10951    fn test_generate_date_array_cte_wrapped_mysql() {
10952        let dialect = Dialect::get(DialectType::Generic);
10953        let result = dialect.transpile(
10954            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10955            DialectType::MySQL,
10956        ).unwrap();
10957        eprintln!("GDA CTE -> MySQL: {}", result[0]);
10958    }
10959
10960    #[test]
10961    fn test_generate_date_array_cte_wrapped_tsql() {
10962        let dialect = Dialect::get(DialectType::Generic);
10963        let result = dialect.transpile(
10964            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10965            DialectType::TSQL,
10966        ).unwrap();
10967        eprintln!("GDA CTE -> TSQL: {}", result[0]);
10968    }
10969
10970    #[test]
10971    fn test_decode_literal_no_null_check() {
10972        // Oracle DECODE with all literals should produce simple equality, no IS NULL
10973        let dialect = Dialect::get(DialectType::Oracle);
10974        let result = dialect
10975            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
10976            .unwrap();
10977        assert_eq!(
10978            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
10979            "Literal DECODE should not have IS NULL checks"
10980        );
10981    }
10982
10983    #[test]
10984    fn test_decode_column_vs_literal_no_null_check() {
10985        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
10986        let dialect = Dialect::get(DialectType::Oracle);
10987        let result = dialect
10988            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
10989            .unwrap();
10990        assert_eq!(
10991            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
10992            "Column vs literal DECODE should not have IS NULL checks"
10993        );
10994    }
10995
10996    #[test]
10997    fn test_decode_column_vs_column_keeps_null_check() {
10998        // Oracle DECODE with column vs column should keep null-safe comparison
10999        let dialect = Dialect::get(DialectType::Oracle);
11000        let result = dialect
11001            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
11002            .unwrap();
11003        assert!(
11004            result[0].contains("IS NULL"),
11005            "Column vs column DECODE should have IS NULL checks, got: {}",
11006            result[0]
11007        );
11008    }
11009
11010    #[test]
11011    fn test_decode_null_search() {
11012        // Oracle DECODE with NULL search should use IS NULL
11013        let dialect = Dialect::get(DialectType::Oracle);
11014        let result = dialect
11015            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
11016            .unwrap();
11017        assert_eq!(
11018            result[0],
11019            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
11020        );
11021    }
11022
11023    // =========================================================================
11024    // REGEXP function transpilation tests
11025    // =========================================================================
11026
11027    #[test]
11028    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
11029        let dialect = Dialect::get(DialectType::Snowflake);
11030        let result = dialect
11031            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
11032            .unwrap();
11033        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11034    }
11035
11036    #[test]
11037    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
11038        let dialect = Dialect::get(DialectType::Snowflake);
11039        let result = dialect
11040            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
11041            .unwrap();
11042        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11043    }
11044
11045    #[test]
11046    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
11047        let dialect = Dialect::get(DialectType::Snowflake);
11048        let result = dialect
11049            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
11050            .unwrap();
11051        assert_eq!(
11052            result[0],
11053            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
11054        );
11055    }
11056
11057    #[test]
11058    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
11059        let dialect = Dialect::get(DialectType::Snowflake);
11060        let result = dialect
11061            .transpile(
11062                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
11063                DialectType::DuckDB,
11064            )
11065            .unwrap();
11066        assert_eq!(
11067            result[0],
11068            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
11069        );
11070    }
11071
11072    #[test]
11073    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
11074        let dialect = Dialect::get(DialectType::Snowflake);
11075        let result = dialect
11076            .transpile(
11077                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
11078                DialectType::DuckDB,
11079            )
11080            .unwrap();
11081        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11082    }
11083
11084    #[test]
11085    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
11086        let dialect = Dialect::get(DialectType::Snowflake);
11087        let result = dialect
11088            .transpile(
11089                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11090                DialectType::DuckDB,
11091            )
11092            .unwrap();
11093        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11094    }
11095
11096    #[test]
11097    fn test_regexp_substr_snowflake_identity_strip_group0() {
11098        let dialect = Dialect::get(DialectType::Snowflake);
11099        let result = dialect
11100            .transpile(
11101                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11102                DialectType::Snowflake,
11103            )
11104            .unwrap();
11105        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
11106    }
11107
11108    #[test]
11109    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
11110        let dialect = Dialect::get(DialectType::Snowflake);
11111        let result = dialect
11112            .transpile(
11113                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
11114                DialectType::DuckDB,
11115            )
11116            .unwrap();
11117        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11118    }
11119
11120    #[test]
11121    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
11122        let dialect = Dialect::get(DialectType::Snowflake);
11123        let result = dialect
11124            .transpile(
11125                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
11126                DialectType::DuckDB,
11127            )
11128            .unwrap();
11129        assert_eq!(
11130            result[0],
11131            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
11132        );
11133    }
11134
11135    #[test]
11136    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
11137        let dialect = Dialect::get(DialectType::Snowflake);
11138        let result = dialect
11139            .transpile(
11140                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
11141                DialectType::DuckDB,
11142            )
11143            .unwrap();
11144        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11145    }
11146
11147    #[test]
11148    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
11149        let dialect = Dialect::get(DialectType::Snowflake);
11150        let result = dialect
11151            .transpile(
11152                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11153                DialectType::DuckDB,
11154            )
11155            .unwrap();
11156        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11157    }
11158
11159    #[test]
11160    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
11161        let dialect = Dialect::get(DialectType::Snowflake);
11162        let result = dialect
11163            .transpile(
11164                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11165                DialectType::Snowflake,
11166            )
11167            .unwrap();
11168        assert_eq!(
11169            result[0],
11170            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
11171        );
11172    }
11173
11174    #[test]
11175    fn test_regexp_count_snowflake_to_duckdb_2arg() {
11176        let dialect = Dialect::get(DialectType::Snowflake);
11177        let result = dialect
11178            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
11179            .unwrap();
11180        assert_eq!(
11181            result[0],
11182            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
11183        );
11184    }
11185
11186    #[test]
11187    fn test_regexp_count_snowflake_to_duckdb_3arg() {
11188        let dialect = Dialect::get(DialectType::Snowflake);
11189        let result = dialect
11190            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
11191            .unwrap();
11192        assert_eq!(
11193            result[0],
11194            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
11195        );
11196    }
11197
11198    #[test]
11199    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
11200        let dialect = Dialect::get(DialectType::Snowflake);
11201        let result = dialect
11202            .transpile(
11203                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
11204                DialectType::DuckDB,
11205            )
11206            .unwrap();
11207        assert_eq!(
11208            result[0],
11209            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
11210        );
11211    }
11212
11213    #[test]
11214    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
11215        let dialect = Dialect::get(DialectType::Snowflake);
11216        let result = dialect
11217            .transpile(
11218                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
11219                DialectType::DuckDB,
11220            )
11221            .unwrap();
11222        assert_eq!(
11223            result[0],
11224            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
11225        );
11226    }
11227
11228    #[test]
11229    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
11230        let dialect = Dialect::get(DialectType::Snowflake);
11231        let result = dialect
11232            .transpile(
11233                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
11234                DialectType::DuckDB,
11235            )
11236            .unwrap();
11237        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
11238    }
11239
11240    #[test]
11241    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
11242        let dialect = Dialect::get(DialectType::Snowflake);
11243        let result = dialect
11244            .transpile(
11245                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
11246                DialectType::DuckDB,
11247            )
11248            .unwrap();
11249        assert_eq!(
11250            result[0],
11251            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
11252        );
11253    }
11254
11255    #[test]
11256    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
11257        let dialect = Dialect::get(DialectType::Snowflake);
11258        let result = dialect
11259            .transpile(
11260                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
11261                DialectType::DuckDB,
11262            )
11263            .unwrap();
11264        assert_eq!(
11265            result[0],
11266            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
11267        );
11268    }
11269
11270    #[test]
11271    fn test_rlike_snowflake_to_duckdb_2arg() {
11272        let dialect = Dialect::get(DialectType::Snowflake);
11273        let result = dialect
11274            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
11275            .unwrap();
11276        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
11277    }
11278
11279    #[test]
11280    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
11281        let dialect = Dialect::get(DialectType::Snowflake);
11282        let result = dialect
11283            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
11284            .unwrap();
11285        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
11286    }
11287
11288    #[test]
11289    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
11290        let dialect = Dialect::get(DialectType::BigQuery);
11291        let result = dialect
11292            .transpile(
11293                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
11294                DialectType::Snowflake,
11295            )
11296            .unwrap();
11297        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
11298    }
11299
11300    #[test]
11301    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
11302        let dialect = Dialect::get(DialectType::BigQuery);
11303        let result = dialect
11304            .transpile(
11305                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
11306                DialectType::Snowflake,
11307            )
11308            .unwrap();
11309        assert_eq!(
11310            result[0],
11311            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
11312        );
11313    }
11314
11315    #[test]
11316    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
11317        let dialect = Dialect::get(DialectType::Snowflake);
11318        let result = dialect
11319            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
11320            .unwrap();
11321        assert!(
11322            result[0].contains("CASE WHEN"),
11323            "Expected CASE WHEN in result: {}",
11324            result[0]
11325        );
11326        assert!(
11327            result[0].contains("LIST_SUM"),
11328            "Expected LIST_SUM in result: {}",
11329            result[0]
11330        );
11331    }
11332
11333    #[test]
11334    fn test_array_except_generic_to_duckdb() {
11335        let dialect = Dialect::get(DialectType::Generic);
11336        let result = dialect
11337            .transpile(
11338                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11339                DialectType::DuckDB,
11340            )
11341            .unwrap();
11342        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
11343        assert!(
11344            result[0].contains("CASE WHEN"),
11345            "Expected CASE WHEN: {}",
11346            result[0]
11347        );
11348        assert!(
11349            result[0].contains("LIST_FILTER"),
11350            "Expected LIST_FILTER: {}",
11351            result[0]
11352        );
11353        assert!(
11354            result[0].contains("LIST_DISTINCT"),
11355            "Expected LIST_DISTINCT: {}",
11356            result[0]
11357        );
11358        assert!(
11359            result[0].contains("IS NOT DISTINCT FROM"),
11360            "Expected IS NOT DISTINCT FROM: {}",
11361            result[0]
11362        );
11363        assert!(
11364            result[0].contains("= 0"),
11365            "Expected = 0 filter: {}",
11366            result[0]
11367        );
11368    }
11369
11370    #[test]
11371    fn test_array_except_generic_to_snowflake() {
11372        let dialect = Dialect::get(DialectType::Generic);
11373        let result = dialect
11374            .transpile(
11375                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11376                DialectType::Snowflake,
11377            )
11378            .unwrap();
11379        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
11380        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
11381    }
11382
11383    #[test]
11384    fn test_array_except_generic_to_presto() {
11385        let dialect = Dialect::get(DialectType::Generic);
11386        let result = dialect
11387            .transpile(
11388                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11389                DialectType::Presto,
11390            )
11391            .unwrap();
11392        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
11393        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
11394    }
11395
11396    #[test]
11397    fn test_array_except_snowflake_to_duckdb() {
11398        let dialect = Dialect::get(DialectType::Snowflake);
11399        let result = dialect
11400            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
11401            .unwrap();
11402        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
11403        assert!(
11404            result[0].contains("CASE WHEN"),
11405            "Expected CASE WHEN: {}",
11406            result[0]
11407        );
11408        assert!(
11409            result[0].contains("LIST_TRANSFORM"),
11410            "Expected LIST_TRANSFORM: {}",
11411            result[0]
11412        );
11413    }
11414
11415    #[test]
11416    fn test_array_contains_snowflake_to_snowflake() {
11417        let dialect = Dialect::get(DialectType::Snowflake);
11418        let result = dialect
11419            .transpile(
11420                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11421                DialectType::Snowflake,
11422            )
11423            .unwrap();
11424        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
11425        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
11426    }
11427
11428    #[test]
11429    fn test_array_contains_snowflake_to_duckdb() {
11430        let dialect = Dialect::get(DialectType::Snowflake);
11431        let result = dialect
11432            .transpile(
11433                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11434                DialectType::DuckDB,
11435            )
11436            .unwrap();
11437        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
11438        assert!(
11439            result[0].contains("CASE WHEN"),
11440            "Expected CASE WHEN: {}",
11441            result[0]
11442        );
11443        assert!(
11444            result[0].contains("NULLIF"),
11445            "Expected NULLIF: {}",
11446            result[0]
11447        );
11448        assert!(
11449            result[0].contains("ARRAY_CONTAINS"),
11450            "Expected ARRAY_CONTAINS: {}",
11451            result[0]
11452        );
11453    }
11454
11455    #[test]
11456    fn test_array_distinct_snowflake_to_duckdb() {
11457        let dialect = Dialect::get(DialectType::Snowflake);
11458        let result = dialect
11459            .transpile(
11460                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
11461                DialectType::DuckDB,
11462            )
11463            .unwrap();
11464        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
11465        assert!(
11466            result[0].contains("CASE WHEN"),
11467            "Expected CASE WHEN: {}",
11468            result[0]
11469        );
11470        assert!(
11471            result[0].contains("LIST_DISTINCT"),
11472            "Expected LIST_DISTINCT: {}",
11473            result[0]
11474        );
11475        assert!(
11476            result[0].contains("LIST_APPEND"),
11477            "Expected LIST_APPEND: {}",
11478            result[0]
11479        );
11480        assert!(
11481            result[0].contains("LIST_FILTER"),
11482            "Expected LIST_FILTER: {}",
11483            result[0]
11484        );
11485    }
11486}