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 additionally checks placement here because a known width alone does
761/// not establish that every generated ordinal can be resolved to an expression.
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            // Keep explicit placement until generation. Clearing it here would
804            // make normalization substitute the source dialect's implicit default.
805            o
806        })
807        .collect();
808    Ok(())
809}
810
811/// Expand when the target lacks ALL, or when a native target cannot use it
812/// for this query (aggregate projections in Snowflake, DISTINCT ON emulation).
813#[cfg(feature = "transpile")]
814fn expand_select_order_by_all(
815    mut sel: Box<crate::expressions::Select>,
816    source: DialectType,
817    target: DialectType,
818) -> Result<Box<crate::expressions::Select>> {
819    if !is_order_by_all(&sel.order_by) {
820        return Ok(sel);
821    }
822    let emulate_distinct = sel
823        .distinct_on
824        .as_ref()
825        .is_some_and(|cols| !cols.is_empty())
826        && !Dialect::target_supports_distinct_on(target);
827    let snowflake_aggregate = target == DialectType::Snowflake
828        && sel
829            .expressions
830            .iter()
831            .any(|e| contains_in_projection(e, crate::traversal::is_aggregate));
832    if target.supports_order_by_all() && !emulate_distinct && !snowflake_aggregate {
833        return Ok(sel);
834    }
835    let width =
836        if sel.expressions.is_empty() || sel.expressions.iter().any(has_unknown_column_count) {
837            None
838        } else {
839            Some(sel.expressions.len())
840        };
841    expand_order_by_all(&mut sel.order_by, width, source, target)?;
842    Ok(sel)
843}
844
845/// Applies a dialect transform bottom-up through selected syntax children.
846///
847/// The public entrypoint uses an explicit task stack for the recursion-heavy shapes
848/// that dominate deeply nested SQL (nested SELECT/FROM/SUBQUERY chains, set-operation
849/// trees, and common binary/unary expression chains). Less common shapes currently
850/// reuse the reference recursive implementation so semantics stay identical while
851/// the hot path avoids stack growth.
852#[cfg(any(
853    feature = "transpile",
854    feature = "ast-tools",
855    feature = "generate",
856    feature = "semantic"
857))]
858pub fn transform_recursive<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
859where
860    F: Fn(Expression) -> Result<Expression>,
861{
862    #[cfg(feature = "stacker")]
863    {
864        let red_zone = if cfg!(debug_assertions) {
865            4 * 1024 * 1024
866        } else {
867            1024 * 1024
868        };
869        stacker::maybe_grow(red_zone, 8 * 1024 * 1024, move || {
870            transform_recursive_inner(expr, transform_fn)
871        })
872    }
873    #[cfg(not(feature = "stacker"))]
874    {
875        transform_recursive_inner(expr, transform_fn)
876    }
877}
878
879#[cfg(any(
880    feature = "transpile",
881    feature = "ast-tools",
882    feature = "generate",
883    feature = "semantic"
884))]
885fn transform_recursive_inner<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
886where
887    F: Fn(Expression) -> Result<Expression>,
888{
889    // These are the shapes handled by the former explicit-stack fast path. Other
890    // nodes retain the reference transformer's selective and wrapper-aware child
891    // semantics even though all physical children are visible to traversal APIs.
892    fn uses_generated_dispatch(expression: &Expression) -> bool {
893        match expression {
894            Expression::CreateTable(_) => true,
895            Expression::Select(select) => {
896                select.joins.is_empty()
897                    && select.with.is_none()
898                    && select.order_by.is_none()
899                    && select.windows.is_none()
900                    && select.settings.is_none()
901            }
902            Expression::Union(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
903            Expression::Intersect(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
904            Expression::Except(set_op) => set_op.with.is_none() && set_op.order_by.is_none(),
905            Expression::Subquery(subquery) => subquery.order_by.is_none(),
906            Expression::Vertica(_)
907            | Expression::Literal(_)
908            | Expression::Boolean(_)
909            | Expression::Null(_)
910            | Expression::Identifier(_)
911            | Expression::Star(_)
912            | Expression::Parameter(_)
913            | Expression::Placeholder(_)
914            | Expression::SessionParameter(_)
915            | Expression::Interval(_)
916            | Expression::Alias(_)
917            | Expression::Paren(_)
918            | Expression::Not(_)
919            | Expression::Neg(_)
920            | Expression::IsNull(_)
921            | Expression::IsTrue(_)
922            | Expression::IsFalse(_)
923            | Expression::Exists(_)
924            | Expression::Any(_)
925            | Expression::All(_)
926            | Expression::TableArgument(_)
927            | Expression::And(_)
928            | Expression::Or(_)
929            | Expression::Add(_)
930            | Expression::Sub(_)
931            | Expression::Mul(_)
932            | Expression::Div(_)
933            | Expression::IntDiv(_)
934            | Expression::Eq(_)
935            | Expression::NullSafeEq(_)
936            | Expression::NullSafeNeq(_)
937            | Expression::Lt(_)
938            | Expression::Gt(_)
939            | Expression::Neq(_)
940            | Expression::Lte(_)
941            | Expression::Gte(_)
942            | Expression::Mod(_)
943            | Expression::Concat(_)
944            | Expression::BitwiseAnd(_)
945            | Expression::BitwiseOr(_)
946            | Expression::BitwiseXor(_)
947            | Expression::BitwiseLeftShift(_)
948            | Expression::BitwiseRightShift(_)
949            | Expression::Is(_)
950            | Expression::MemberOf(_)
951            | Expression::ArrayContainsAll(_)
952            | Expression::ArrayContainedBy(_)
953            | Expression::ArrayOverlaps(_)
954            | Expression::TsMatch(_)
955            | Expression::Adjacent(_)
956            | Expression::Like(_)
957            | Expression::ILike(_)
958            | Expression::Function(_)
959            | Expression::AggregateFunction(_)
960            | Expression::JSONValue(_)
961            | Expression::JSONTable(_)
962            | Expression::JSONColumnDef(_)
963            | Expression::JsonQuery(_)
964            | Expression::RegexpLike(_)
965            | Expression::RegexpReplace(_)
966            | Expression::RegexpExtract(_)
967            | Expression::RegexpInstr(_)
968            | Expression::RegexpCount(_)
969            | Expression::Cube(_)
970            | Expression::Rollup(_)
971            | Expression::GroupingSets(_)
972            | Expression::Hint(_)
973            | Expression::PartitionByProperty(_)
974            | Expression::StorageProperty(_)
975            | Expression::Hierarchy(_)
976            | Expression::ViewParameter(_)
977            | Expression::Call(_)
978            | Expression::Lead(_)
979            | Expression::Lag(_)
980            | Expression::Array(_)
981            | Expression::Tuple(_)
982            | Expression::ArrayFunc(_)
983            | Expression::Coalesce(_)
984            | Expression::Greatest(_)
985            | Expression::Least(_)
986            | Expression::ArrayConcat(_)
987            | Expression::ArrayIntersect(_)
988            | Expression::ArrayZip(_)
989            | Expression::MapConcat(_)
990            | Expression::JsonArray(_)
991            | Expression::From(_) => true,
992            _ => false,
993        }
994    }
995
996    crate::traversal::transform_with_dispatch(
997        expr,
998        transform_fn,
999        &uses_generated_dispatch,
1000        &transform_recursive_reference,
1001    )
1002}
1003
1004#[cfg(any(
1005    feature = "transpile",
1006    feature = "ast-tools",
1007    feature = "generate",
1008    feature = "semantic"
1009))]
1010fn transform_table_ref_recursive<F>(table: TableRef, transform_fn: &F) -> Result<TableRef>
1011where
1012    F: Fn(Expression) -> Result<Expression>,
1013{
1014    match transform_recursive(Expression::Table(Box::new(table)), transform_fn)? {
1015        Expression::Table(table) => Ok(*table),
1016        _ => Err(crate::error::Error::parse(
1017            "TableRef transformation returned non-table expression",
1018            0,
1019            0,
1020            0,
1021            0,
1022        )),
1023    }
1024}
1025
1026#[cfg(any(
1027    feature = "transpile",
1028    feature = "ast-tools",
1029    feature = "generate",
1030    feature = "semantic"
1031))]
1032fn transform_from_recursive<F>(from: From, transform_fn: &F) -> Result<From>
1033where
1034    F: Fn(Expression) -> Result<Expression>,
1035{
1036    match transform_recursive(Expression::From(Box::new(from)), transform_fn)? {
1037        Expression::From(from) => Ok(*from),
1038        _ => Err(crate::error::Error::parse(
1039            "FROM transformation returned non-FROM expression",
1040            0,
1041            0,
1042            0,
1043            0,
1044        )),
1045    }
1046}
1047
1048#[cfg(any(
1049    feature = "transpile",
1050    feature = "ast-tools",
1051    feature = "generate",
1052    feature = "semantic"
1053))]
1054fn transform_join_recursive<F>(mut join: Join, transform_fn: &F) -> Result<Join>
1055where
1056    F: Fn(Expression) -> Result<Expression>,
1057{
1058    join.this = transform_recursive(join.this, transform_fn)?;
1059    if let Some(on) = join.on.take() {
1060        join.on = Some(transform_recursive(on, transform_fn)?);
1061    }
1062    if let Some(match_condition) = join.match_condition.take() {
1063        join.match_condition = Some(transform_recursive(match_condition, transform_fn)?);
1064    }
1065    join.pivots = join
1066        .pivots
1067        .into_iter()
1068        .map(|pivot| transform_recursive(pivot, transform_fn))
1069        .collect::<Result<Vec<_>>>()?;
1070
1071    match transform_fn(Expression::Join(Box::new(join)))? {
1072        Expression::Join(join) => Ok(*join),
1073        _ => Err(crate::error::Error::parse(
1074            "Join transformation returned non-join expression",
1075            0,
1076            0,
1077            0,
1078            0,
1079        )),
1080    }
1081}
1082
1083#[cfg(any(
1084    feature = "transpile",
1085    feature = "ast-tools",
1086    feature = "generate",
1087    feature = "semantic"
1088))]
1089fn transform_output_clause_recursive<F>(
1090    mut output: OutputClause,
1091    transform_fn: &F,
1092) -> Result<OutputClause>
1093where
1094    F: Fn(Expression) -> Result<Expression>,
1095{
1096    output.columns = output
1097        .columns
1098        .into_iter()
1099        .map(|column| transform_recursive(column, transform_fn))
1100        .collect::<Result<Vec<_>>>()?;
1101    if let Some(into_table) = output.into_table.take() {
1102        output.into_table = Some(transform_recursive(into_table, transform_fn)?);
1103    }
1104    Ok(output)
1105}
1106
1107#[cfg(any(
1108    feature = "transpile",
1109    feature = "ast-tools",
1110    feature = "generate",
1111    feature = "semantic"
1112))]
1113fn transform_with_recursive<F>(mut with: With, transform_fn: &F) -> Result<With>
1114where
1115    F: Fn(Expression) -> Result<Expression>,
1116{
1117    with.ctes = with
1118        .ctes
1119        .into_iter()
1120        .map(|mut cte| {
1121            cte.this = transform_recursive(cte.this, transform_fn)?;
1122            Ok(cte)
1123        })
1124        .collect::<Result<Vec<_>>>()?;
1125    if let Some(search) = with.search.take() {
1126        with.search = Some(Box::new(transform_recursive(*search, transform_fn)?));
1127    }
1128    Ok(with)
1129}
1130
1131#[cfg(any(
1132    feature = "transpile",
1133    feature = "ast-tools",
1134    feature = "generate",
1135    feature = "semantic"
1136))]
1137fn transform_order_by_recursive<F>(mut order: OrderBy, transform_fn: &F) -> Result<OrderBy>
1138where
1139    F: Fn(Expression) -> Result<Expression>,
1140{
1141    order.expressions = order
1142        .expressions
1143        .into_iter()
1144        .map(|mut ordered| {
1145            let original = ordered.this.clone();
1146            ordered.this = transform_recursive(ordered.this, transform_fn).unwrap_or(original);
1147            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
1148                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
1149                Ok(_) | Err(_) => Ok(ordered),
1150            }
1151        })
1152        .collect::<Result<Vec<_>>>()?;
1153    Ok(order)
1154}
1155
1156#[cfg(any(
1157    feature = "transpile",
1158    feature = "ast-tools",
1159    feature = "generate",
1160    feature = "semantic"
1161))]
1162fn transform_recursive_reference<F>(expr: Expression, transform_fn: &F) -> Result<Expression>
1163where
1164    F: Fn(Expression) -> Result<Expression>,
1165{
1166    use crate::expressions::BinaryOp;
1167
1168    // Helper macro to recurse into AggFunc-based expressions (this, filter, order_by, having_max, limit).
1169    macro_rules! recurse_agg {
1170        ($variant:ident, $f:expr) => {{
1171            let mut f = $f;
1172            f.this = transform_recursive(f.this, transform_fn)?;
1173            if let Some(filter) = f.filter.take() {
1174                f.filter = Some(transform_recursive(filter, transform_fn)?);
1175            }
1176            for ord in &mut f.order_by {
1177                ord.this = transform_recursive(
1178                    std::mem::replace(&mut ord.this, Expression::Null(crate::expressions::Null)),
1179                    transform_fn,
1180                )?;
1181            }
1182            if let Some((ref mut expr, _)) = f.having_max {
1183                *expr = Box::new(transform_recursive(
1184                    std::mem::replace(expr.as_mut(), Expression::Null(crate::expressions::Null)),
1185                    transform_fn,
1186                )?);
1187            }
1188            if let Some(limit) = f.limit.take() {
1189                f.limit = Some(Box::new(transform_recursive(*limit, transform_fn)?));
1190            }
1191            Expression::$variant(f)
1192        }};
1193    }
1194
1195    // Helper macro to transform binary ops with Box<BinaryOp>
1196    macro_rules! transform_binary {
1197        ($variant:ident, $op:expr) => {{
1198            let left = transform_recursive($op.left, transform_fn)?;
1199            let right = transform_recursive($op.right, transform_fn)?;
1200            Expression::$variant(Box::new(BinaryOp {
1201                left,
1202                right,
1203                left_comments: $op.left_comments,
1204                operator_comments: $op.operator_comments,
1205                trailing_comments: $op.trailing_comments,
1206                inferred_type: $op.inferred_type,
1207            }))
1208        }};
1209    }
1210
1211    // Fast path: leaf nodes never need child traversal, apply transform directly
1212    if matches!(
1213        &expr,
1214        Expression::Literal(_)
1215            | Expression::Boolean(_)
1216            | Expression::Null(_)
1217            | Expression::Identifier(_)
1218            | Expression::Star(_)
1219            | Expression::Parameter(_)
1220            | Expression::Placeholder(_)
1221            | Expression::SessionParameter(_)
1222    ) {
1223        return transform_fn(expr);
1224    }
1225
1226    // First recursively transform children, then apply the transform function
1227    let expr = match expr {
1228        Expression::Select(mut select) => {
1229            select.query_hints = std::mem::take(&mut select.query_hints)
1230                .into_iter()
1231                .map(|hint| transform_recursive(hint, transform_fn))
1232                .collect::<Result<Vec<_>>>()?;
1233            if let Some(extension) = &mut select.vertica {
1234                use crate::ast_children::AstNode;
1235                let mut failure = None;
1236                extension.visit_expressions_mut(&mut |child| {
1237                    if failure.is_none() {
1238                        let old =
1239                            std::mem::replace(child, Expression::Null(crate::expressions::Null));
1240                        match transform_recursive(old, transform_fn) {
1241                            Ok(new) => *child = new,
1242                            Err(error) => failure = Some(error),
1243                        }
1244                    }
1245                });
1246                if let Some(error) = failure {
1247                    return Err(error);
1248                }
1249            }
1250            select.expressions = select
1251                .expressions
1252                .into_iter()
1253                .map(|e| transform_recursive(e, transform_fn))
1254                .collect::<Result<Vec<_>>>()?;
1255
1256            // Transform FROM clause
1257            if let Some(mut from) = select.from.take() {
1258                from.expressions = from
1259                    .expressions
1260                    .into_iter()
1261                    .map(|e| transform_recursive(e, transform_fn))
1262                    .collect::<Result<Vec<_>>>()?;
1263                select.from = Some(from);
1264            }
1265
1266            // Transform JOINs - important for CROSS APPLY / LATERAL transformations
1267            select.joins = select
1268                .joins
1269                .into_iter()
1270                .map(|mut join| {
1271                    join.this = transform_recursive(join.this, transform_fn)?;
1272                    if let Some(on) = join.on.take() {
1273                        join.on = Some(transform_recursive(on, transform_fn)?);
1274                    }
1275                    // Wrap join in Expression::Join to allow transform_fn to transform it
1276                    match transform_fn(Expression::Join(Box::new(join)))? {
1277                        Expression::Join(j) => Ok(*j),
1278                        _ => Err(crate::error::Error::parse(
1279                            "Join transformation returned non-join expression",
1280                            0,
1281                            0,
1282                            0,
1283                            0,
1284                        )),
1285                    }
1286                })
1287                .collect::<Result<Vec<_>>>()?;
1288
1289            // Transform LATERAL VIEW expressions (Hive/Spark)
1290            select.lateral_views = select
1291                .lateral_views
1292                .into_iter()
1293                .map(|mut lv| {
1294                    lv.this = transform_recursive(lv.this, transform_fn)?;
1295                    Ok(lv)
1296                })
1297                .collect::<Result<Vec<_>>>()?;
1298
1299            // Transform WHERE clause
1300            if let Some(mut where_clause) = select.where_clause.take() {
1301                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1302                select.where_clause = Some(where_clause);
1303            }
1304
1305            // Transform GROUP BY
1306            if let Some(mut group_by) = select.group_by.take() {
1307                group_by.expressions = group_by
1308                    .expressions
1309                    .into_iter()
1310                    .map(|e| transform_recursive(e, transform_fn))
1311                    .collect::<Result<Vec<_>>>()?;
1312                select.group_by = Some(group_by);
1313            }
1314
1315            // Transform HAVING
1316            if let Some(mut having) = select.having.take() {
1317                having.this = transform_recursive(having.this, transform_fn)?;
1318                select.having = Some(having);
1319            }
1320
1321            // Transform WITH (CTEs)
1322            if let Some(mut with) = select.with.take() {
1323                with.ctes = with
1324                    .ctes
1325                    .into_iter()
1326                    .map(|mut cte| {
1327                        let original = cte.this.clone();
1328                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1329                        cte
1330                    })
1331                    .collect();
1332                select.with = Some(with);
1333            }
1334
1335            // Transform ORDER BY
1336            if let Some(mut order) = select.order_by.take() {
1337                order.expressions = order
1338                    .expressions
1339                    .into_iter()
1340                    .map(|o| {
1341                        let mut o = o;
1342                        let original = o.this.clone();
1343                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1344                        // Also apply transform to the Ordered wrapper itself (for NULLS FIRST etc.)
1345                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1346                            Ok(Expression::Ordered(transformed)) => *transformed,
1347                            Ok(_) | Err(_) => o,
1348                        }
1349                    })
1350                    .collect();
1351                select.order_by = Some(order);
1352            }
1353
1354            // Transform WINDOW clause order_by
1355            if let Some(ref mut windows) = select.windows {
1356                for nw in windows.iter_mut() {
1357                    nw.spec.order_by = std::mem::take(&mut nw.spec.order_by)
1358                        .into_iter()
1359                        .map(|o| {
1360                            let mut o = o;
1361                            let original = o.this.clone();
1362                            o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1363                            match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1364                                Ok(Expression::Ordered(transformed)) => *transformed,
1365                                Ok(_) | Err(_) => o,
1366                            }
1367                        })
1368                        .collect();
1369                }
1370            }
1371
1372            // Transform QUALIFY
1373            if let Some(mut qual) = select.qualify.take() {
1374                qual.this = transform_recursive(qual.this, transform_fn)?;
1375                select.qualify = Some(qual);
1376            }
1377
1378            Expression::Select(select)
1379        }
1380        Expression::Function(mut f) => {
1381            f.args = f
1382                .args
1383                .into_iter()
1384                .map(|e| transform_recursive(e, transform_fn))
1385                .collect::<Result<Vec<_>>>()?;
1386            Expression::Function(f)
1387        }
1388        Expression::AggregateFunction(mut f) => {
1389            f.args = f
1390                .args
1391                .into_iter()
1392                .map(|e| transform_recursive(e, transform_fn))
1393                .collect::<Result<Vec<_>>>()?;
1394            if let Some(filter) = f.filter {
1395                f.filter = Some(transform_recursive(filter, transform_fn)?);
1396            }
1397            Expression::AggregateFunction(f)
1398        }
1399        Expression::WindowFunction(mut wf) => {
1400            wf.this = transform_recursive(wf.this, transform_fn)?;
1401            wf.over.partition_by = wf
1402                .over
1403                .partition_by
1404                .into_iter()
1405                .map(|e| transform_recursive(e, transform_fn))
1406                .collect::<Result<Vec<_>>>()?;
1407            // Transform order_by items through Expression::Ordered wrapper
1408            wf.over.order_by = wf
1409                .over
1410                .order_by
1411                .into_iter()
1412                .map(|o| {
1413                    let mut o = o;
1414                    o.this = transform_recursive(o.this, transform_fn)?;
1415                    match transform_fn(Expression::Ordered(Box::new(o)))? {
1416                        Expression::Ordered(transformed) => Ok(*transformed),
1417                        _ => Err(crate::error::Error::parse(
1418                            "Ordered transformation returned non-Ordered expression",
1419                            0,
1420                            0,
1421                            0,
1422                            0,
1423                        )),
1424                    }
1425                })
1426                .collect::<Result<Vec<_>>>()?;
1427            Expression::WindowFunction(wf)
1428        }
1429        Expression::Alias(mut a) => {
1430            a.this = transform_recursive(a.this, transform_fn)?;
1431            Expression::Alias(a)
1432        }
1433        Expression::Cast(mut c) => {
1434            c.this = transform_recursive(c.this, transform_fn)?;
1435            // Also transform the target data type (recursively for nested types like ARRAY<INT>, STRUCT<a INT>)
1436            c.to = transform_data_type_recursive(c.to, transform_fn)?;
1437            Expression::Cast(c)
1438        }
1439        Expression::And(op) => transform_binary!(And, *op),
1440        Expression::Or(op) => transform_binary!(Or, *op),
1441        Expression::Add(op) => transform_binary!(Add, *op),
1442        Expression::Sub(op) => transform_binary!(Sub, *op),
1443        Expression::Mul(op) => transform_binary!(Mul, *op),
1444        Expression::Div(op) => transform_binary!(Div, *op),
1445        Expression::Eq(op) => transform_binary!(Eq, *op),
1446        Expression::Lt(op) => transform_binary!(Lt, *op),
1447        Expression::Gt(op) => transform_binary!(Gt, *op),
1448        Expression::Paren(mut p) => {
1449            p.this = transform_recursive(p.this, transform_fn)?;
1450            Expression::Paren(p)
1451        }
1452        Expression::Coalesce(mut f) => {
1453            f.expressions = f
1454                .expressions
1455                .into_iter()
1456                .map(|e| transform_recursive(e, transform_fn))
1457                .collect::<Result<Vec<_>>>()?;
1458            Expression::Coalesce(f)
1459        }
1460        Expression::IfNull(mut f) => {
1461            f.this = transform_recursive(f.this, transform_fn)?;
1462            f.expression = transform_recursive(f.expression, transform_fn)?;
1463            Expression::IfNull(f)
1464        }
1465        Expression::Nvl(mut f) => {
1466            f.this = transform_recursive(f.this, transform_fn)?;
1467            f.expression = transform_recursive(f.expression, transform_fn)?;
1468            Expression::Nvl(f)
1469        }
1470        Expression::In(mut i) => {
1471            i.this = transform_recursive(i.this, transform_fn)?;
1472            i.expressions = i
1473                .expressions
1474                .into_iter()
1475                .map(|e| transform_recursive(e, transform_fn))
1476                .collect::<Result<Vec<_>>>()?;
1477            if let Some(query) = i.query {
1478                i.query = Some(transform_recursive(query, transform_fn)?);
1479            }
1480            Expression::In(i)
1481        }
1482        Expression::Not(mut n) => {
1483            n.this = transform_recursive(n.this, transform_fn)?;
1484            Expression::Not(n)
1485        }
1486        Expression::ArraySlice(mut s) => {
1487            s.this = transform_recursive(s.this, transform_fn)?;
1488            if let Some(start) = s.start {
1489                s.start = Some(transform_recursive(start, transform_fn)?);
1490            }
1491            if let Some(end) = s.end {
1492                s.end = Some(transform_recursive(end, transform_fn)?);
1493            }
1494            Expression::ArraySlice(s)
1495        }
1496        Expression::Subscript(mut s) => {
1497            s.this = transform_recursive(s.this, transform_fn)?;
1498            s.index = transform_recursive(s.index, transform_fn)?;
1499            Expression::Subscript(s)
1500        }
1501        Expression::Array(mut a) => {
1502            a.expressions = a
1503                .expressions
1504                .into_iter()
1505                .map(|e| transform_recursive(e, transform_fn))
1506                .collect::<Result<Vec<_>>>()?;
1507            Expression::Array(a)
1508        }
1509        Expression::Struct(mut s) => {
1510            let mut new_fields = Vec::new();
1511            for (name, expr) in s.fields {
1512                let transformed = transform_recursive(expr, transform_fn)?;
1513                new_fields.push((name, transformed));
1514            }
1515            s.fields = new_fields;
1516            Expression::Struct(s)
1517        }
1518        Expression::NamedArgument(mut na) => {
1519            na.value = transform_recursive(na.value, transform_fn)?;
1520            Expression::NamedArgument(na)
1521        }
1522        Expression::MapFunc(mut m) => {
1523            m.keys = m
1524                .keys
1525                .into_iter()
1526                .map(|e| transform_recursive(e, transform_fn))
1527                .collect::<Result<Vec<_>>>()?;
1528            m.values = m
1529                .values
1530                .into_iter()
1531                .map(|e| transform_recursive(e, transform_fn))
1532                .collect::<Result<Vec<_>>>()?;
1533            Expression::MapFunc(m)
1534        }
1535        Expression::ArrayFunc(mut a) => {
1536            a.expressions = a
1537                .expressions
1538                .into_iter()
1539                .map(|e| transform_recursive(e, transform_fn))
1540                .collect::<Result<Vec<_>>>()?;
1541            Expression::ArrayFunc(a)
1542        }
1543        Expression::Lambda(mut l) => {
1544            l.body = transform_recursive(l.body, transform_fn)?;
1545            Expression::Lambda(l)
1546        }
1547        Expression::JsonExtract(mut f) => {
1548            f.this = transform_recursive(f.this, transform_fn)?;
1549            f.path = transform_recursive(f.path, transform_fn)?;
1550            Expression::JsonExtract(f)
1551        }
1552        Expression::JsonExtractScalar(mut f) => {
1553            f.this = transform_recursive(f.this, transform_fn)?;
1554            f.path = transform_recursive(f.path, transform_fn)?;
1555            Expression::JsonExtractScalar(f)
1556        }
1557
1558        // ===== UnaryFunc-based expressions =====
1559        // These all have a single `this: Expression` child
1560        Expression::Length(mut f) => {
1561            f.this = transform_recursive(f.this, transform_fn)?;
1562            Expression::Length(f)
1563        }
1564        Expression::Upper(mut f) => {
1565            f.this = transform_recursive(f.this, transform_fn)?;
1566            Expression::Upper(f)
1567        }
1568        Expression::Lower(mut f) => {
1569            f.this = transform_recursive(f.this, transform_fn)?;
1570            Expression::Lower(f)
1571        }
1572        Expression::LTrim(mut f) => {
1573            f.this = transform_recursive(f.this, transform_fn)?;
1574            Expression::LTrim(f)
1575        }
1576        Expression::RTrim(mut f) => {
1577            f.this = transform_recursive(f.this, transform_fn)?;
1578            Expression::RTrim(f)
1579        }
1580        Expression::Reverse(mut f) => {
1581            f.this = transform_recursive(f.this, transform_fn)?;
1582            Expression::Reverse(f)
1583        }
1584        Expression::Abs(mut f) => {
1585            f.this = transform_recursive(f.this, transform_fn)?;
1586            Expression::Abs(f)
1587        }
1588        Expression::Ceil(mut f) => {
1589            f.this = transform_recursive(f.this, transform_fn)?;
1590            Expression::Ceil(f)
1591        }
1592        Expression::Floor(mut f) => {
1593            f.this = transform_recursive(f.this, transform_fn)?;
1594            Expression::Floor(f)
1595        }
1596        Expression::Sign(mut f) => {
1597            f.this = transform_recursive(f.this, transform_fn)?;
1598            Expression::Sign(f)
1599        }
1600        Expression::Sqrt(mut f) => {
1601            f.this = transform_recursive(f.this, transform_fn)?;
1602            Expression::Sqrt(f)
1603        }
1604        Expression::Cbrt(mut f) => {
1605            f.this = transform_recursive(f.this, transform_fn)?;
1606            Expression::Cbrt(f)
1607        }
1608        Expression::Ln(mut f) => {
1609            f.this = transform_recursive(f.this, transform_fn)?;
1610            Expression::Ln(f)
1611        }
1612        Expression::Log(mut f) => {
1613            f.this = transform_recursive(f.this, transform_fn)?;
1614            if let Some(base) = f.base {
1615                f.base = Some(transform_recursive(base, transform_fn)?);
1616            }
1617            Expression::Log(f)
1618        }
1619        Expression::Exp(mut f) => {
1620            f.this = transform_recursive(f.this, transform_fn)?;
1621            Expression::Exp(f)
1622        }
1623        Expression::Date(mut f) => {
1624            f.this = transform_recursive(f.this, transform_fn)?;
1625            Expression::Date(f)
1626        }
1627        Expression::Stddev(f) => recurse_agg!(Stddev, f),
1628        Expression::StddevSamp(f) => recurse_agg!(StddevSamp, f),
1629        Expression::Variance(f) => recurse_agg!(Variance, f),
1630
1631        // ===== BinaryFunc-based expressions =====
1632        Expression::ModFunc(mut f) => {
1633            f.this = transform_recursive(f.this, transform_fn)?;
1634            f.expression = transform_recursive(f.expression, transform_fn)?;
1635            Expression::ModFunc(f)
1636        }
1637        Expression::Power(mut f) => {
1638            f.this = transform_recursive(f.this, transform_fn)?;
1639            f.expression = transform_recursive(f.expression, transform_fn)?;
1640            Expression::Power(f)
1641        }
1642        Expression::MapFromArrays(mut f) => {
1643            f.this = transform_recursive(f.this, transform_fn)?;
1644            f.expression = transform_recursive(f.expression, transform_fn)?;
1645            Expression::MapFromArrays(f)
1646        }
1647        Expression::ElementAt(mut f) => {
1648            f.this = transform_recursive(f.this, transform_fn)?;
1649            f.expression = transform_recursive(f.expression, transform_fn)?;
1650            Expression::ElementAt(f)
1651        }
1652        Expression::MapContainsKey(mut f) => {
1653            f.this = transform_recursive(f.this, transform_fn)?;
1654            f.expression = transform_recursive(f.expression, transform_fn)?;
1655            Expression::MapContainsKey(f)
1656        }
1657        Expression::Left(mut f) => {
1658            f.this = transform_recursive(f.this, transform_fn)?;
1659            f.length = transform_recursive(f.length, transform_fn)?;
1660            Expression::Left(f)
1661        }
1662        Expression::Right(mut f) => {
1663            f.this = transform_recursive(f.this, transform_fn)?;
1664            f.length = transform_recursive(f.length, transform_fn)?;
1665            Expression::Right(f)
1666        }
1667        Expression::Repeat(mut f) => {
1668            f.this = transform_recursive(f.this, transform_fn)?;
1669            f.times = transform_recursive(f.times, transform_fn)?;
1670            Expression::Repeat(f)
1671        }
1672
1673        // ===== Complex function expressions =====
1674        Expression::Substring(mut f) => {
1675            f.this = transform_recursive(f.this, transform_fn)?;
1676            f.start = transform_recursive(f.start, transform_fn)?;
1677            if let Some(len) = f.length {
1678                f.length = Some(transform_recursive(len, transform_fn)?);
1679            }
1680            Expression::Substring(f)
1681        }
1682        Expression::Replace(mut f) => {
1683            f.this = transform_recursive(f.this, transform_fn)?;
1684            f.old = transform_recursive(f.old, transform_fn)?;
1685            f.new = transform_recursive(f.new, transform_fn)?;
1686            Expression::Replace(f)
1687        }
1688        Expression::ConcatWs(mut f) => {
1689            f.separator = transform_recursive(f.separator, transform_fn)?;
1690            f.expressions = f
1691                .expressions
1692                .into_iter()
1693                .map(|e| transform_recursive(e, transform_fn))
1694                .collect::<Result<Vec<_>>>()?;
1695            Expression::ConcatWs(f)
1696        }
1697        Expression::Trim(mut f) => {
1698            f.this = transform_recursive(f.this, transform_fn)?;
1699            if let Some(chars) = f.characters {
1700                f.characters = Some(transform_recursive(chars, transform_fn)?);
1701            }
1702            Expression::Trim(f)
1703        }
1704        Expression::Split(mut f) => {
1705            f.this = transform_recursive(f.this, transform_fn)?;
1706            f.delimiter = transform_recursive(f.delimiter, transform_fn)?;
1707            Expression::Split(f)
1708        }
1709        Expression::Lpad(mut f) => {
1710            f.this = transform_recursive(f.this, transform_fn)?;
1711            f.length = transform_recursive(f.length, transform_fn)?;
1712            if let Some(fill) = f.fill {
1713                f.fill = Some(transform_recursive(fill, transform_fn)?);
1714            }
1715            Expression::Lpad(f)
1716        }
1717        Expression::Rpad(mut f) => {
1718            f.this = transform_recursive(f.this, transform_fn)?;
1719            f.length = transform_recursive(f.length, transform_fn)?;
1720            if let Some(fill) = f.fill {
1721                f.fill = Some(transform_recursive(fill, transform_fn)?);
1722            }
1723            Expression::Rpad(f)
1724        }
1725
1726        // ===== Conditional expressions =====
1727        Expression::Case(mut c) => {
1728            if let Some(operand) = c.operand {
1729                c.operand = Some(transform_recursive(operand, transform_fn)?);
1730            }
1731            c.whens = c
1732                .whens
1733                .into_iter()
1734                .map(|(cond, then)| {
1735                    let new_cond = transform_recursive(cond.clone(), transform_fn).unwrap_or(cond);
1736                    let new_then = transform_recursive(then.clone(), transform_fn).unwrap_or(then);
1737                    (new_cond, new_then)
1738                })
1739                .collect();
1740            if let Some(else_expr) = c.else_ {
1741                c.else_ = Some(transform_recursive(else_expr, transform_fn)?);
1742            }
1743            Expression::Case(c)
1744        }
1745        Expression::IfFunc(mut f) => {
1746            f.condition = transform_recursive(f.condition, transform_fn)?;
1747            f.true_value = transform_recursive(f.true_value, transform_fn)?;
1748            if let Some(false_val) = f.false_value {
1749                f.false_value = Some(transform_recursive(false_val, transform_fn)?);
1750            }
1751            Expression::IfFunc(f)
1752        }
1753
1754        // ===== Date/Time expressions =====
1755        Expression::DateAdd(mut f) => {
1756            f.this = transform_recursive(f.this, transform_fn)?;
1757            f.interval = transform_recursive(f.interval, transform_fn)?;
1758            Expression::DateAdd(f)
1759        }
1760        Expression::DateSub(mut f) => {
1761            f.this = transform_recursive(f.this, transform_fn)?;
1762            f.interval = transform_recursive(f.interval, transform_fn)?;
1763            Expression::DateSub(f)
1764        }
1765        Expression::DateDiff(mut f) => {
1766            f.this = transform_recursive(f.this, transform_fn)?;
1767            f.expression = transform_recursive(f.expression, transform_fn)?;
1768            Expression::DateDiff(f)
1769        }
1770        Expression::DateTrunc(mut f) => {
1771            f.this = transform_recursive(f.this, transform_fn)?;
1772            Expression::DateTrunc(f)
1773        }
1774        Expression::Extract(mut f) => {
1775            f.this = transform_recursive(f.this, transform_fn)?;
1776            Expression::Extract(f)
1777        }
1778
1779        // ===== JSON expressions =====
1780        Expression::JsonObject(mut f) => {
1781            f.pairs = f
1782                .pairs
1783                .into_iter()
1784                .map(|(k, v)| {
1785                    let new_k = transform_recursive(k, transform_fn)?;
1786                    let new_v = transform_recursive(v, transform_fn)?;
1787                    Ok((new_k, new_v))
1788                })
1789                .collect::<Result<Vec<_>>>()?;
1790            Expression::JsonObject(f)
1791        }
1792
1793        // ===== Subquery expressions =====
1794        Expression::Subquery(mut s) => {
1795            s.this = transform_recursive(s.this, transform_fn)?;
1796            if let Some(order_by) = s.order_by.take() {
1797                s.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
1798            }
1799            Expression::Subquery(s)
1800        }
1801        Expression::Exists(mut e) => {
1802            e.this = transform_recursive(e.this, transform_fn)?;
1803            Expression::Exists(e)
1804        }
1805        Expression::Describe(mut d) => {
1806            d.target = transform_recursive(d.target, transform_fn)?;
1807            Expression::Describe(d)
1808        }
1809
1810        // ===== Set operations =====
1811        Expression::Union(mut u) => {
1812            let left = std::mem::replace(&mut u.left, Expression::Null(Null));
1813            u.left = transform_recursive(left, transform_fn)?;
1814            let right = std::mem::replace(&mut u.right, Expression::Null(Null));
1815            u.right = transform_recursive(right, transform_fn)?;
1816            if let Some(mut order) = u.order_by.take() {
1817                order.expressions = order
1818                    .expressions
1819                    .into_iter()
1820                    .map(|o| {
1821                        let mut o = o;
1822                        let original = o.this.clone();
1823                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1824                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1825                            Ok(Expression::Ordered(transformed)) => *transformed,
1826                            Ok(_) | Err(_) => o,
1827                        }
1828                    })
1829                    .collect();
1830                u.order_by = Some(order);
1831            }
1832            if let Some(mut with) = u.with.take() {
1833                with.ctes = with
1834                    .ctes
1835                    .into_iter()
1836                    .map(|mut cte| {
1837                        let original = cte.this.clone();
1838                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1839                        cte
1840                    })
1841                    .collect();
1842                u.with = Some(with);
1843            }
1844            Expression::Union(u)
1845        }
1846        Expression::Intersect(mut i) => {
1847            let left = std::mem::replace(&mut i.left, Expression::Null(Null));
1848            i.left = transform_recursive(left, transform_fn)?;
1849            let right = std::mem::replace(&mut i.right, Expression::Null(Null));
1850            i.right = transform_recursive(right, transform_fn)?;
1851            if let Some(mut order) = i.order_by.take() {
1852                order.expressions = order
1853                    .expressions
1854                    .into_iter()
1855                    .map(|o| {
1856                        let mut o = o;
1857                        let original = o.this.clone();
1858                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1859                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1860                            Ok(Expression::Ordered(transformed)) => *transformed,
1861                            Ok(_) | Err(_) => o,
1862                        }
1863                    })
1864                    .collect();
1865                i.order_by = Some(order);
1866            }
1867            if let Some(mut with) = i.with.take() {
1868                with.ctes = with
1869                    .ctes
1870                    .into_iter()
1871                    .map(|mut cte| {
1872                        let original = cte.this.clone();
1873                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1874                        cte
1875                    })
1876                    .collect();
1877                i.with = Some(with);
1878            }
1879            Expression::Intersect(i)
1880        }
1881        Expression::Except(mut e) => {
1882            let left = std::mem::replace(&mut e.left, Expression::Null(Null));
1883            e.left = transform_recursive(left, transform_fn)?;
1884            let right = std::mem::replace(&mut e.right, Expression::Null(Null));
1885            e.right = transform_recursive(right, transform_fn)?;
1886            if let Some(mut order) = e.order_by.take() {
1887                order.expressions = order
1888                    .expressions
1889                    .into_iter()
1890                    .map(|o| {
1891                        let mut o = o;
1892                        let original = o.this.clone();
1893                        o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
1894                        match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
1895                            Ok(Expression::Ordered(transformed)) => *transformed,
1896                            Ok(_) | Err(_) => o,
1897                        }
1898                    })
1899                    .collect();
1900                e.order_by = Some(order);
1901            }
1902            if let Some(mut with) = e.with.take() {
1903                with.ctes = with
1904                    .ctes
1905                    .into_iter()
1906                    .map(|mut cte| {
1907                        let original = cte.this.clone();
1908                        cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
1909                        cte
1910                    })
1911                    .collect();
1912                e.with = Some(with);
1913            }
1914            Expression::Except(e)
1915        }
1916
1917        // ===== DML expressions =====
1918        Expression::Insert(mut ins) => {
1919            // Transform VALUES clause expressions
1920            let mut new_values = Vec::new();
1921            for row in ins.values {
1922                let mut new_row = Vec::new();
1923                for e in row {
1924                    new_row.push(transform_recursive(e, transform_fn)?);
1925                }
1926                new_values.push(new_row);
1927            }
1928            ins.values = new_values;
1929
1930            // Transform query (for INSERT ... SELECT)
1931            if let Some(query) = ins.query {
1932                ins.query = Some(transform_recursive(query, transform_fn)?);
1933            }
1934
1935            // Transform RETURNING clause
1936            let mut new_returning = Vec::new();
1937            for e in ins.returning {
1938                new_returning.push(transform_recursive(e, transform_fn)?);
1939            }
1940            ins.returning = new_returning;
1941
1942            // Transform ON CONFLICT clause
1943            if let Some(on_conflict) = ins.on_conflict {
1944                ins.on_conflict = Some(Box::new(transform_recursive(*on_conflict, transform_fn)?));
1945            }
1946
1947            Expression::Insert(ins)
1948        }
1949        Expression::Update(mut upd) => {
1950            upd.table = transform_table_ref_recursive(upd.table, transform_fn)?;
1951            upd.extra_tables = upd
1952                .extra_tables
1953                .into_iter()
1954                .map(|table| transform_table_ref_recursive(table, transform_fn))
1955                .collect::<Result<Vec<_>>>()?;
1956            upd.table_joins = upd
1957                .table_joins
1958                .into_iter()
1959                .map(|join| transform_join_recursive(join, transform_fn))
1960                .collect::<Result<Vec<_>>>()?;
1961            upd.set = upd
1962                .set
1963                .into_iter()
1964                .map(|(id, val)| {
1965                    let new_val = transform_recursive(val.clone(), transform_fn).unwrap_or(val);
1966                    (id, new_val)
1967                })
1968                .collect();
1969            if let Some(from_clause) = upd.from_clause.take() {
1970                upd.from_clause = Some(transform_from_recursive(from_clause, transform_fn)?);
1971            }
1972            upd.from_joins = upd
1973                .from_joins
1974                .into_iter()
1975                .map(|join| transform_join_recursive(join, transform_fn))
1976                .collect::<Result<Vec<_>>>()?;
1977            if let Some(mut where_clause) = upd.where_clause.take() {
1978                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
1979                upd.where_clause = Some(where_clause);
1980            }
1981            upd.returning = upd
1982                .returning
1983                .into_iter()
1984                .map(|expr| transform_recursive(expr, transform_fn))
1985                .collect::<Result<Vec<_>>>()?;
1986            if let Some(output) = upd.output.take() {
1987                upd.output = Some(transform_output_clause_recursive(output, transform_fn)?);
1988            }
1989            if let Some(with) = upd.with.take() {
1990                upd.with = Some(transform_with_recursive(with, transform_fn)?);
1991            }
1992            if let Some(limit) = upd.limit.take() {
1993                upd.limit = Some(transform_recursive(limit, transform_fn)?);
1994            }
1995            if let Some(order_by) = upd.order_by.take() {
1996                upd.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
1997            }
1998            Expression::Update(upd)
1999        }
2000        Expression::Delete(mut del) => {
2001            del.table = transform_table_ref_recursive(del.table, transform_fn)?;
2002            del.using = del
2003                .using
2004                .into_iter()
2005                .map(|table| transform_table_ref_recursive(table, transform_fn))
2006                .collect::<Result<Vec<_>>>()?;
2007            if let Some(mut where_clause) = del.where_clause.take() {
2008                where_clause.this = transform_recursive(where_clause.this, transform_fn)?;
2009                del.where_clause = Some(where_clause);
2010            }
2011            if let Some(output) = del.output.take() {
2012                del.output = Some(transform_output_clause_recursive(output, transform_fn)?);
2013            }
2014            if let Some(with) = del.with.take() {
2015                del.with = Some(transform_with_recursive(with, transform_fn)?);
2016            }
2017            if let Some(limit) = del.limit.take() {
2018                del.limit = Some(transform_recursive(limit, transform_fn)?);
2019            }
2020            if let Some(order_by) = del.order_by.take() {
2021                del.order_by = Some(transform_order_by_recursive(order_by, transform_fn)?);
2022            }
2023            del.returning = del
2024                .returning
2025                .into_iter()
2026                .map(|expr| transform_recursive(expr, transform_fn))
2027                .collect::<Result<Vec<_>>>()?;
2028            del.tables = del
2029                .tables
2030                .into_iter()
2031                .map(|table| transform_table_ref_recursive(table, transform_fn))
2032                .collect::<Result<Vec<_>>>()?;
2033            del.joins = del
2034                .joins
2035                .into_iter()
2036                .map(|join| transform_join_recursive(join, transform_fn))
2037                .collect::<Result<Vec<_>>>()?;
2038            Expression::Delete(del)
2039        }
2040
2041        // ===== CTE expressions =====
2042        Expression::With(mut w) => {
2043            w.ctes = w
2044                .ctes
2045                .into_iter()
2046                .map(|mut cte| {
2047                    let original = cte.this.clone();
2048                    cte.this = transform_recursive(cte.this, transform_fn).unwrap_or(original);
2049                    cte
2050                })
2051                .collect();
2052            Expression::With(w)
2053        }
2054        Expression::Cte(mut c) => {
2055            c.this = transform_recursive(c.this, transform_fn)?;
2056            Expression::Cte(c)
2057        }
2058
2059        // ===== Order expressions =====
2060        Expression::Ordered(mut o) => {
2061            o.this = transform_recursive(o.this, transform_fn)?;
2062            Expression::Ordered(o)
2063        }
2064
2065        // ===== Negation =====
2066        Expression::Neg(mut n) => {
2067            n.this = transform_recursive(n.this, transform_fn)?;
2068            Expression::Neg(n)
2069        }
2070
2071        // ===== Between =====
2072        Expression::Between(mut b) => {
2073            b.this = transform_recursive(b.this, transform_fn)?;
2074            b.low = transform_recursive(b.low, transform_fn)?;
2075            b.high = transform_recursive(b.high, transform_fn)?;
2076            Expression::Between(b)
2077        }
2078        Expression::IsNull(mut i) => {
2079            i.this = transform_recursive(i.this, transform_fn)?;
2080            Expression::IsNull(i)
2081        }
2082        Expression::IsTrue(mut i) => {
2083            i.this = transform_recursive(i.this, transform_fn)?;
2084            Expression::IsTrue(i)
2085        }
2086        Expression::IsFalse(mut i) => {
2087            i.this = transform_recursive(i.this, transform_fn)?;
2088            Expression::IsFalse(i)
2089        }
2090
2091        // ===== Like expressions =====
2092        Expression::Like(mut l) => {
2093            l.left = transform_recursive(l.left, transform_fn)?;
2094            l.right = transform_recursive(l.right, transform_fn)?;
2095            Expression::Like(l)
2096        }
2097        Expression::ILike(mut l) => {
2098            l.left = transform_recursive(l.left, transform_fn)?;
2099            l.right = transform_recursive(l.right, transform_fn)?;
2100            Expression::ILike(l)
2101        }
2102
2103        // ===== Additional binary ops not covered by macro =====
2104        Expression::Neq(op) => transform_binary!(Neq, *op),
2105        Expression::Lte(op) => transform_binary!(Lte, *op),
2106        Expression::Gte(op) => transform_binary!(Gte, *op),
2107        Expression::Mod(op) => transform_binary!(Mod, *op),
2108        Expression::Concat(op) => transform_binary!(Concat, *op),
2109        Expression::BitwiseAnd(op) => transform_binary!(BitwiseAnd, *op),
2110        Expression::BitwiseOr(op) => transform_binary!(BitwiseOr, *op),
2111        Expression::BitwiseXor(op) => transform_binary!(BitwiseXor, *op),
2112        Expression::BitwiseLeftShift(op) => transform_binary!(BitwiseLeftShift, *op),
2113        Expression::BitwiseRightShift(op) => transform_binary!(BitwiseRightShift, *op),
2114        Expression::Is(op) => transform_binary!(Is, *op),
2115
2116        // ===== TryCast / SafeCast =====
2117        Expression::TryCast(mut c) => {
2118            c.this = transform_recursive(c.this, transform_fn)?;
2119            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2120            Expression::TryCast(c)
2121        }
2122        Expression::SafeCast(mut c) => {
2123            c.this = transform_recursive(c.this, transform_fn)?;
2124            c.to = transform_data_type_recursive(c.to, transform_fn)?;
2125            Expression::SafeCast(c)
2126        }
2127
2128        // ===== Misc =====
2129        Expression::Unnest(mut f) => {
2130            f.this = transform_recursive(f.this, transform_fn)?;
2131            f.expressions = f
2132                .expressions
2133                .into_iter()
2134                .map(|e| transform_recursive(e, transform_fn))
2135                .collect::<Result<Vec<_>>>()?;
2136            Expression::Unnest(f)
2137        }
2138        Expression::Explode(mut f) => {
2139            f.this = transform_recursive(f.this, transform_fn)?;
2140            Expression::Explode(f)
2141        }
2142        Expression::GroupConcat(mut f) => {
2143            f.this = transform_recursive(f.this, transform_fn)?;
2144            Expression::GroupConcat(f)
2145        }
2146        Expression::StringAgg(mut f) => {
2147            f.this = transform_recursive(f.this, transform_fn)?;
2148            if let Some(order_by) = f.order_by.take() {
2149                f.order_by = Some(
2150                    order_by
2151                        .into_iter()
2152                        .map(|mut ordered| {
2153                            let original = ordered.this.clone();
2154                            ordered.this =
2155                                transform_recursive(ordered.this, transform_fn).unwrap_or(original);
2156                            match transform_fn(Expression::Ordered(Box::new(ordered.clone()))) {
2157                                Ok(Expression::Ordered(transformed)) => Ok(*transformed),
2158                                Ok(_) | Err(_) => Ok(ordered),
2159                            }
2160                        })
2161                        .collect::<Result<Vec<_>>>()?,
2162                );
2163            }
2164            Expression::StringAgg(f)
2165        }
2166        Expression::ListAgg(mut f) => {
2167            f.this = transform_recursive(f.this, transform_fn)?;
2168            Expression::ListAgg(f)
2169        }
2170        Expression::ArrayAgg(mut f) => {
2171            f.this = transform_recursive(f.this, transform_fn)?;
2172            Expression::ArrayAgg(f)
2173        }
2174        Expression::ParseJson(mut f) => {
2175            f.this = transform_recursive(f.this, transform_fn)?;
2176            Expression::ParseJson(f)
2177        }
2178        Expression::ToJson(mut f) => {
2179            f.this = transform_recursive(f.this, transform_fn)?;
2180            Expression::ToJson(f)
2181        }
2182        Expression::JSONExtract(mut e) => {
2183            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2184            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2185            Expression::JSONExtract(e)
2186        }
2187        Expression::JSONExtractScalar(mut e) => {
2188            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2189            e.expression = Box::new(transform_recursive(*e.expression, transform_fn)?);
2190            Expression::JSONExtractScalar(e)
2191        }
2192
2193        // StrToTime: recurse into this
2194        Expression::StrToTime(mut e) => {
2195            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2196            Expression::StrToTime(e)
2197        }
2198
2199        // UnixToTime: recurse into this
2200        Expression::UnixToTime(mut e) => {
2201            e.this = Box::new(transform_recursive(*e.this, transform_fn)?);
2202            Expression::UnixToTime(e)
2203        }
2204
2205        Expression::Upsert(mut upsert) => {
2206            upsert.table = transform_table_ref_recursive(upsert.table, transform_fn)?;
2207            upsert.source = transform_recursive(upsert.source, transform_fn)?;
2208            if let Some(partition) = upsert.partition.take() {
2209                upsert.partition = Some(transform_recursive(partition, transform_fn)?);
2210            }
2211            if let Some(condition) = upsert.condition.take() {
2212                upsert.condition = Some(transform_recursive(condition, transform_fn)?);
2213            }
2214            Expression::Upsert(upsert)
2215        }
2216
2217        // CreateTable: recurse into column defaults, on_update expressions, and data types
2218        Expression::CreateTable(mut ct) => {
2219            for col in &mut ct.columns {
2220                if let Some(default_expr) = col.default.take() {
2221                    col.default = Some(transform_recursive(default_expr, transform_fn)?);
2222                }
2223                if let Some(on_update_expr) = col.on_update.take() {
2224                    col.on_update = Some(transform_recursive(on_update_expr, transform_fn)?);
2225                }
2226                // Note: Column data type transformations (INT -> INT64 for BigQuery, etc.)
2227                // are NOT applied here because per-dialect transforms are designed for CAST/expression
2228                // contexts and may not produce correct results for DDL column definitions.
2229                // The DDL type mappings would need dedicated handling per source/target pair.
2230            }
2231            if let Some(as_select) = ct.as_select.take() {
2232                ct.as_select = Some(transform_recursive(as_select, transform_fn)?);
2233            }
2234            Expression::CreateTable(ct)
2235        }
2236
2237        // CreateView: recurse into the view body query
2238        Expression::CreateView(mut cv) => {
2239            cv.query = transform_recursive(cv.query, transform_fn)?;
2240            Expression::CreateView(cv)
2241        }
2242
2243        // CreateTask: recurse into the task body
2244        Expression::CreateTask(mut ct) => {
2245            ct.body = transform_recursive(ct.body, transform_fn)?;
2246            Expression::CreateTask(ct)
2247        }
2248
2249        // Prepare: recurse into the prepared statement body
2250        Expression::Prepare(mut prepare) => {
2251            prepare.statement = transform_recursive(prepare.statement, transform_fn)?;
2252            Expression::Prepare(prepare)
2253        }
2254
2255        // Execute: recurse into procedure/prepared name and argument values
2256        Expression::Execute(mut execute) => {
2257            execute.this = transform_recursive(execute.this, transform_fn)?;
2258            execute.arguments = execute
2259                .arguments
2260                .into_iter()
2261                .map(|argument| transform_recursive(argument, transform_fn))
2262                .collect::<Result<Vec<_>>>()?;
2263            execute.parameters = execute
2264                .parameters
2265                .into_iter()
2266                .map(|mut parameter| {
2267                    parameter.value = transform_recursive(parameter.value, transform_fn)?;
2268                    Ok(parameter)
2269                })
2270                .collect::<Result<Vec<_>>>()?;
2271            Expression::Execute(execute)
2272        }
2273
2274        // CreateProcedure: recurse into body expressions
2275        Expression::CreateProcedure(mut cp) => {
2276            if let Some(body) = cp.body.take() {
2277                cp.body = Some(match body {
2278                    FunctionBody::Expression(expr) => {
2279                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2280                    }
2281                    FunctionBody::Return(expr) => {
2282                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2283                    }
2284                    FunctionBody::Statements(stmts) => {
2285                        let transformed_stmts = stmts
2286                            .into_iter()
2287                            .map(|s| transform_recursive(s, transform_fn))
2288                            .collect::<Result<Vec<_>>>()?;
2289                        FunctionBody::Statements(transformed_stmts)
2290                    }
2291                    other => other,
2292                });
2293            }
2294            Expression::CreateProcedure(cp)
2295        }
2296
2297        // CreateFunction: recurse into body expressions
2298        Expression::CreateFunction(mut cf) => {
2299            if let Some(body) = cf.body.take() {
2300                cf.body = Some(match body {
2301                    FunctionBody::Expression(expr) => {
2302                        FunctionBody::Expression(transform_recursive(expr, transform_fn)?)
2303                    }
2304                    FunctionBody::Return(expr) => {
2305                        FunctionBody::Return(transform_recursive(expr, transform_fn)?)
2306                    }
2307                    FunctionBody::Statements(stmts) => {
2308                        let transformed_stmts = stmts
2309                            .into_iter()
2310                            .map(|s| transform_recursive(s, transform_fn))
2311                            .collect::<Result<Vec<_>>>()?;
2312                        FunctionBody::Statements(transformed_stmts)
2313                    }
2314                    other => other,
2315                });
2316            }
2317            Expression::CreateFunction(cf)
2318        }
2319
2320        // MemberOf: recurse into left and right operands
2321        Expression::MemberOf(op) => transform_binary!(MemberOf, *op),
2322        // ArrayContainsAll (@>): recurse into left and right operands
2323        Expression::ArrayContainsAll(op) => transform_binary!(ArrayContainsAll, *op),
2324        // ArrayContainedBy (<@): recurse into left and right operands
2325        Expression::ArrayContainedBy(op) => transform_binary!(ArrayContainedBy, *op),
2326        // ArrayOverlaps (&&): recurse into left and right operands
2327        Expression::ArrayOverlaps(op) => transform_binary!(ArrayOverlaps, *op),
2328        // TsMatch (@@): recurse into left and right operands
2329        Expression::TsMatch(op) => transform_binary!(TsMatch, *op),
2330        // Adjacent (-|-): recurse into left and right operands
2331        Expression::Adjacent(op) => transform_binary!(Adjacent, *op),
2332
2333        // Table: recurse into when (HistoricalData) and changes fields
2334        Expression::Table(mut t) => {
2335            if let Some(when) = t.when.take() {
2336                let transformed =
2337                    transform_recursive(Expression::HistoricalData(when), transform_fn)?;
2338                if let Expression::HistoricalData(hd) = transformed {
2339                    t.when = Some(hd);
2340                }
2341            }
2342            if let Some(changes) = t.changes.take() {
2343                let transformed = transform_recursive(Expression::Changes(changes), transform_fn)?;
2344                if let Expression::Changes(c) = transformed {
2345                    t.changes = Some(c);
2346                }
2347            }
2348            Expression::Table(t)
2349        }
2350
2351        // HistoricalData (Snowflake time travel): recurse into expression
2352        Expression::HistoricalData(mut hd) => {
2353            *hd.expression = transform_recursive(*hd.expression, transform_fn)?;
2354            Expression::HistoricalData(hd)
2355        }
2356
2357        // Changes (Snowflake CHANGES clause): recurse into at_before and end
2358        Expression::Changes(mut c) => {
2359            if let Some(at_before) = c.at_before.take() {
2360                c.at_before = Some(Box::new(transform_recursive(*at_before, transform_fn)?));
2361            }
2362            if let Some(end) = c.end.take() {
2363                c.end = Some(Box::new(transform_recursive(*end, transform_fn)?));
2364            }
2365            Expression::Changes(c)
2366        }
2367
2368        // TableArgument: TABLE(expr) or MODEL(expr)
2369        Expression::TableArgument(mut ta) => {
2370            ta.this = transform_recursive(ta.this, transform_fn)?;
2371            Expression::TableArgument(ta)
2372        }
2373
2374        // JoinedTable: (tbl1 JOIN tbl2 ON ...) - recurse into left and join tables
2375        Expression::JoinedTable(mut jt) => {
2376            jt.left = transform_recursive(jt.left, transform_fn)?;
2377            jt.joins = jt
2378                .joins
2379                .into_iter()
2380                .map(|mut join| {
2381                    join.this = transform_recursive(join.this, transform_fn)?;
2382                    if let Some(on) = join.on.take() {
2383                        join.on = Some(transform_recursive(on, transform_fn)?);
2384                    }
2385                    match transform_fn(Expression::Join(Box::new(join)))? {
2386                        Expression::Join(j) => Ok(*j),
2387                        _ => Err(crate::error::Error::parse(
2388                            "Join transformation returned non-join expression",
2389                            0,
2390                            0,
2391                            0,
2392                            0,
2393                        )),
2394                    }
2395                })
2396                .collect::<Result<Vec<_>>>()?;
2397            jt.lateral_views = jt
2398                .lateral_views
2399                .into_iter()
2400                .map(|mut lv| {
2401                    lv.this = transform_recursive(lv.this, transform_fn)?;
2402                    Ok(lv)
2403                })
2404                .collect::<Result<Vec<_>>>()?;
2405            Expression::JoinedTable(jt)
2406        }
2407
2408        // Lateral: LATERAL func() - recurse into the function expression
2409        Expression::Lateral(mut lat) => {
2410            *lat.this = transform_recursive(*lat.this, transform_fn)?;
2411            Expression::Lateral(lat)
2412        }
2413
2414        // WithinGroup: recurse into order_by items (for NULLS FIRST/LAST etc.)
2415        // but NOT into wg.this - the inner function is handled by StringAggConvert/GroupConcatConvert
2416        // as a unit together with the WithinGroup wrapper
2417        Expression::WithinGroup(mut wg) => {
2418            wg.order_by = wg
2419                .order_by
2420                .into_iter()
2421                .map(|mut o| {
2422                    let original = o.this.clone();
2423                    o.this = transform_recursive(o.this, transform_fn).unwrap_or(original);
2424                    match transform_fn(Expression::Ordered(Box::new(o.clone()))) {
2425                        Ok(Expression::Ordered(transformed)) => *transformed,
2426                        Ok(_) | Err(_) => o,
2427                    }
2428                })
2429                .collect();
2430            Expression::WithinGroup(wg)
2431        }
2432
2433        // Filter: recurse into both the aggregate and the filter condition
2434        Expression::Filter(mut f) => {
2435            f.this = Box::new(transform_recursive(*f.this, transform_fn)?);
2436            f.expression = Box::new(transform_recursive(*f.expression, transform_fn)?);
2437            Expression::Filter(f)
2438        }
2439
2440        // Aggregate functions (AggFunc-based): recurse into the aggregate argument,
2441        // filter, order_by, having_max, and limit.
2442        // Stddev, StddevSamp, Variance, and ArrayAgg are handled earlier in this match.
2443        Expression::Sum(f) => recurse_agg!(Sum, f),
2444        Expression::Avg(f) => recurse_agg!(Avg, f),
2445        Expression::Min(f) => recurse_agg!(Min, f),
2446        Expression::Max(f) => recurse_agg!(Max, f),
2447        Expression::CountIf(f) => recurse_agg!(CountIf, f),
2448        Expression::StddevPop(f) => recurse_agg!(StddevPop, f),
2449        Expression::VarPop(f) => recurse_agg!(VarPop, f),
2450        Expression::VarSamp(f) => recurse_agg!(VarSamp, f),
2451        Expression::Median(f) => recurse_agg!(Median, f),
2452        Expression::Mode(f) => recurse_agg!(Mode, f),
2453        Expression::First(f) => recurse_agg!(First, f),
2454        Expression::Last(f) => recurse_agg!(Last, f),
2455        Expression::AnyValue(f) => recurse_agg!(AnyValue, f),
2456        Expression::ApproxDistinct(f) => recurse_agg!(ApproxDistinct, f),
2457        Expression::ApproxCountDistinct(f) => recurse_agg!(ApproxCountDistinct, f),
2458        Expression::LogicalAnd(f) => recurse_agg!(LogicalAnd, f),
2459        Expression::LogicalOr(f) => recurse_agg!(LogicalOr, f),
2460        Expression::Skewness(f) => recurse_agg!(Skewness, f),
2461        Expression::ArrayConcatAgg(f) => recurse_agg!(ArrayConcatAgg, f),
2462        Expression::ArrayUniqueAgg(f) => recurse_agg!(ArrayUniqueAgg, f),
2463        Expression::BoolXorAgg(f) => recurse_agg!(BoolXorAgg, f),
2464        Expression::BitwiseOrAgg(f) => recurse_agg!(BitwiseOrAgg, f),
2465        Expression::BitwiseAndAgg(f) => recurse_agg!(BitwiseAndAgg, f),
2466        Expression::BitwiseXorAgg(f) => recurse_agg!(BitwiseXorAgg, f),
2467
2468        // Count has its own struct with an Option<Expression> `this` field
2469        Expression::Count(mut c) => {
2470            if let Some(this) = c.this.take() {
2471                c.this = Some(transform_recursive(this, transform_fn)?);
2472            }
2473            if let Some(filter) = c.filter.take() {
2474                c.filter = Some(transform_recursive(filter, transform_fn)?);
2475            }
2476            Expression::Count(c)
2477        }
2478
2479        Expression::PipeOperator(mut pipe) => {
2480            pipe.this = transform_recursive(pipe.this, transform_fn)?;
2481            pipe.expression = transform_recursive(pipe.expression, transform_fn)?;
2482            Expression::PipeOperator(pipe)
2483        }
2484
2485        // ArrayExcept/ArrayContains/ArrayDistinct: recurse into children
2486        Expression::ArrayExcept(mut f) => {
2487            f.this = transform_recursive(f.this, transform_fn)?;
2488            f.expression = transform_recursive(f.expression, transform_fn)?;
2489            Expression::ArrayExcept(f)
2490        }
2491        Expression::ArrayContains(mut f) => {
2492            f.this = transform_recursive(f.this, transform_fn)?;
2493            f.expression = transform_recursive(f.expression, transform_fn)?;
2494            Expression::ArrayContains(f)
2495        }
2496        Expression::ArrayDistinct(mut f) => {
2497            f.this = transform_recursive(f.this, transform_fn)?;
2498            Expression::ArrayDistinct(f)
2499        }
2500        Expression::ArrayPosition(mut f) => {
2501            f.this = transform_recursive(f.this, transform_fn)?;
2502            f.expression = transform_recursive(f.expression, transform_fn)?;
2503            Expression::ArrayPosition(f)
2504        }
2505
2506        // Pass through leaf nodes unchanged
2507        other => other,
2508    };
2509
2510    // Then apply the transform function
2511    transform_fn(expr)
2512}
2513
2514/// Returns the tokenizer config, generator config, and expression transform closure
2515/// for a built-in dialect type. This is the shared implementation used by both
2516/// `Dialect::get()` and custom dialect construction.
2517// ---------------------------------------------------------------------------
2518// Cached dialect configurations
2519// ---------------------------------------------------------------------------
2520
2521/// Pre-computed tokenizer + generator configs for a dialect, cached via `LazyLock`.
2522/// Transform closures are cheap (unit-struct method calls) and created fresh each time.
2523struct CachedDialectConfig {
2524    tokenizer_config: Arc<TokenizerConfig>,
2525    #[cfg(feature = "generate")]
2526    generator_config: Arc<GeneratorConfig>,
2527}
2528
2529struct DialectConfigs {
2530    tokenizer_config: Arc<TokenizerConfig>,
2531    #[cfg(feature = "generate")]
2532    generator_config: Arc<GeneratorConfig>,
2533    #[cfg(feature = "transpile")]
2534    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2535}
2536
2537/// Declare a per-dialect `LazyLock<CachedDialectConfig>` static.
2538macro_rules! cached_dialect {
2539    ($static_name:ident, $dialect_struct:expr, $feature:literal) => {
2540        #[cfg(feature = $feature)]
2541        static $static_name: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2542            let d = $dialect_struct;
2543            CachedDialectConfig {
2544                tokenizer_config: Arc::new(d.tokenizer_config()),
2545                #[cfg(feature = "generate")]
2546                generator_config: Arc::new(d.generator_config()),
2547            }
2548        });
2549    };
2550}
2551
2552static CACHED_GENERIC: LazyLock<CachedDialectConfig> = LazyLock::new(|| {
2553    let d = GenericDialect;
2554    CachedDialectConfig {
2555        tokenizer_config: Arc::new(d.tokenizer_config()),
2556        #[cfg(feature = "generate")]
2557        generator_config: Arc::new(d.generator_config()),
2558    }
2559});
2560
2561cached_dialect!(CACHED_POSTGRESQL, PostgresDialect, "dialect-postgresql");
2562cached_dialect!(CACHED_MYSQL, MySQLDialect, "dialect-mysql");
2563cached_dialect!(CACHED_BIGQUERY, BigQueryDialect, "dialect-bigquery");
2564cached_dialect!(CACHED_SNOWFLAKE, SnowflakeDialect, "dialect-snowflake");
2565cached_dialect!(CACHED_DUCKDB, DuckDBDialect, "dialect-duckdb");
2566cached_dialect!(CACHED_TSQL, TSQLDialect, "dialect-tsql");
2567cached_dialect!(CACHED_ORACLE, OracleDialect, "dialect-oracle");
2568cached_dialect!(CACHED_HIVE, HiveDialect, "dialect-hive");
2569cached_dialect!(CACHED_SPARK, SparkDialect, "dialect-spark");
2570cached_dialect!(CACHED_SQLITE, SQLiteDialect, "dialect-sqlite");
2571cached_dialect!(CACHED_PRESTO, PrestoDialect, "dialect-presto");
2572cached_dialect!(CACHED_TRINO, TrinoDialect, "dialect-trino");
2573cached_dialect!(CACHED_REDSHIFT, RedshiftDialect, "dialect-redshift");
2574cached_dialect!(CACHED_CLICKHOUSE, ClickHouseDialect, "dialect-clickhouse");
2575cached_dialect!(CACHED_DATABRICKS, DatabricksDialect, "dialect-databricks");
2576cached_dialect!(CACHED_ATHENA, AthenaDialect, "dialect-athena");
2577cached_dialect!(CACHED_TERADATA, TeradataDialect, "dialect-teradata");
2578cached_dialect!(CACHED_DORIS, DorisDialect, "dialect-doris");
2579cached_dialect!(CACHED_STARROCKS, StarRocksDialect, "dialect-starrocks");
2580cached_dialect!(
2581    CACHED_MATERIALIZE,
2582    MaterializeDialect,
2583    "dialect-materialize"
2584);
2585cached_dialect!(CACHED_RISINGWAVE, RisingWaveDialect, "dialect-risingwave");
2586cached_dialect!(
2587    CACHED_SINGLESTORE,
2588    SingleStoreDialect,
2589    "dialect-singlestore"
2590);
2591cached_dialect!(
2592    CACHED_COCKROACHDB,
2593    CockroachDBDialect,
2594    "dialect-cockroachdb"
2595);
2596cached_dialect!(CACHED_TIDB, TiDBDialect, "dialect-tidb");
2597cached_dialect!(CACHED_DRUID, DruidDialect, "dialect-druid");
2598cached_dialect!(CACHED_SOLR, SolrDialect, "dialect-solr");
2599cached_dialect!(CACHED_TABLEAU, TableauDialect, "dialect-tableau");
2600cached_dialect!(CACHED_DUNE, DuneDialect, "dialect-dune");
2601cached_dialect!(CACHED_FABRIC, FabricDialect, "dialect-fabric");
2602cached_dialect!(CACHED_DRILL, DrillDialect, "dialect-drill");
2603cached_dialect!(CACHED_DREMIO, DremioDialect, "dialect-dremio");
2604cached_dialect!(CACHED_EXASOL, ExasolDialect, "dialect-exasol");
2605cached_dialect!(CACHED_DATAFUSION, DataFusionDialect, "dialect-datafusion");
2606cached_dialect!(CACHED_HANA, HanaDialect, "dialect-hana");
2607cached_dialect!(CACHED_VERTICA, VerticaDialect, "dialect-vertica");
2608
2609fn configs_for_dialect_type(dt: DialectType) -> DialectConfigs {
2610    /// Clone configs from a cached static and pair with a fresh transform closure.
2611    macro_rules! from_cache {
2612        ($cache:expr, $dialect_struct:expr) => {{
2613            let c = &*$cache;
2614            DialectConfigs {
2615                tokenizer_config: c.tokenizer_config.clone(),
2616                #[cfg(feature = "generate")]
2617                generator_config: c.generator_config.clone(),
2618                #[cfg(feature = "transpile")]
2619                transformer: Box::new(move |e| $dialect_struct.transform_expr(e)),
2620            }
2621        }};
2622    }
2623    match dt {
2624        #[cfg(feature = "dialect-postgresql")]
2625        DialectType::PostgreSQL => from_cache!(CACHED_POSTGRESQL, PostgresDialect),
2626        #[cfg(feature = "dialect-mysql")]
2627        DialectType::MySQL => from_cache!(CACHED_MYSQL, MySQLDialect),
2628        #[cfg(feature = "dialect-bigquery")]
2629        DialectType::BigQuery => from_cache!(CACHED_BIGQUERY, BigQueryDialect),
2630        #[cfg(feature = "dialect-snowflake")]
2631        DialectType::Snowflake => from_cache!(CACHED_SNOWFLAKE, SnowflakeDialect),
2632        #[cfg(feature = "dialect-duckdb")]
2633        DialectType::DuckDB => from_cache!(CACHED_DUCKDB, DuckDBDialect),
2634        #[cfg(feature = "dialect-tsql")]
2635        DialectType::TSQL => from_cache!(CACHED_TSQL, TSQLDialect),
2636        #[cfg(feature = "dialect-oracle")]
2637        DialectType::Oracle => from_cache!(CACHED_ORACLE, OracleDialect),
2638        #[cfg(feature = "dialect-hive")]
2639        DialectType::Hive => from_cache!(CACHED_HIVE, HiveDialect),
2640        #[cfg(feature = "dialect-spark")]
2641        DialectType::Spark => from_cache!(CACHED_SPARK, SparkDialect),
2642        #[cfg(feature = "dialect-sqlite")]
2643        DialectType::SQLite => from_cache!(CACHED_SQLITE, SQLiteDialect),
2644        #[cfg(feature = "dialect-presto")]
2645        DialectType::Presto => from_cache!(CACHED_PRESTO, PrestoDialect),
2646        #[cfg(feature = "dialect-trino")]
2647        DialectType::Trino => from_cache!(CACHED_TRINO, TrinoDialect),
2648        #[cfg(feature = "dialect-redshift")]
2649        DialectType::Redshift => from_cache!(CACHED_REDSHIFT, RedshiftDialect),
2650        #[cfg(feature = "dialect-clickhouse")]
2651        DialectType::ClickHouse => from_cache!(CACHED_CLICKHOUSE, ClickHouseDialect),
2652        #[cfg(feature = "dialect-databricks")]
2653        DialectType::Databricks => from_cache!(CACHED_DATABRICKS, DatabricksDialect),
2654        #[cfg(feature = "dialect-athena")]
2655        DialectType::Athena => from_cache!(CACHED_ATHENA, AthenaDialect),
2656        #[cfg(feature = "dialect-teradata")]
2657        DialectType::Teradata => from_cache!(CACHED_TERADATA, TeradataDialect),
2658        #[cfg(feature = "dialect-doris")]
2659        DialectType::Doris => from_cache!(CACHED_DORIS, DorisDialect),
2660        #[cfg(feature = "dialect-starrocks")]
2661        DialectType::StarRocks => from_cache!(CACHED_STARROCKS, StarRocksDialect),
2662        #[cfg(feature = "dialect-materialize")]
2663        DialectType::Materialize => from_cache!(CACHED_MATERIALIZE, MaterializeDialect),
2664        #[cfg(feature = "dialect-risingwave")]
2665        DialectType::RisingWave => from_cache!(CACHED_RISINGWAVE, RisingWaveDialect),
2666        #[cfg(feature = "dialect-singlestore")]
2667        DialectType::SingleStore => from_cache!(CACHED_SINGLESTORE, SingleStoreDialect),
2668        #[cfg(feature = "dialect-cockroachdb")]
2669        DialectType::CockroachDB => from_cache!(CACHED_COCKROACHDB, CockroachDBDialect),
2670        #[cfg(feature = "dialect-tidb")]
2671        DialectType::TiDB => from_cache!(CACHED_TIDB, TiDBDialect),
2672        #[cfg(feature = "dialect-druid")]
2673        DialectType::Druid => from_cache!(CACHED_DRUID, DruidDialect),
2674        #[cfg(feature = "dialect-solr")]
2675        DialectType::Solr => from_cache!(CACHED_SOLR, SolrDialect),
2676        #[cfg(feature = "dialect-tableau")]
2677        DialectType::Tableau => from_cache!(CACHED_TABLEAU, TableauDialect),
2678        #[cfg(feature = "dialect-dune")]
2679        DialectType::Dune => from_cache!(CACHED_DUNE, DuneDialect),
2680        #[cfg(feature = "dialect-fabric")]
2681        DialectType::Fabric => from_cache!(CACHED_FABRIC, FabricDialect),
2682        #[cfg(feature = "dialect-drill")]
2683        DialectType::Drill => from_cache!(CACHED_DRILL, DrillDialect),
2684        #[cfg(feature = "dialect-dremio")]
2685        DialectType::Dremio => from_cache!(CACHED_DREMIO, DremioDialect),
2686        #[cfg(feature = "dialect-exasol")]
2687        DialectType::Exasol => from_cache!(CACHED_EXASOL, ExasolDialect),
2688        #[cfg(feature = "dialect-datafusion")]
2689        DialectType::DataFusion => from_cache!(CACHED_DATAFUSION, DataFusionDialect),
2690        #[cfg(feature = "dialect-hana")]
2691        DialectType::HANA => from_cache!(CACHED_HANA, HanaDialect),
2692        #[cfg(feature = "dialect-vertica")]
2693        DialectType::Vertica => from_cache!(CACHED_VERTICA, VerticaDialect),
2694        _ => from_cache!(CACHED_GENERIC, GenericDialect),
2695    }
2696}
2697
2698// ---------------------------------------------------------------------------
2699// Custom dialect registry
2700// ---------------------------------------------------------------------------
2701
2702static CUSTOM_DIALECT_REGISTRY: LazyLock<RwLock<HashMap<String, Arc<CustomDialectConfig>>>> =
2703    LazyLock::new(|| RwLock::new(HashMap::new()));
2704
2705struct CustomDialectConfig {
2706    name: String,
2707    base_dialect: DialectType,
2708    tokenizer_config: Arc<TokenizerConfig>,
2709    #[cfg(feature = "generate")]
2710    generator_config: GeneratorConfig,
2711    #[cfg(feature = "transpile")]
2712    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2713    #[cfg(feature = "transpile")]
2714    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2715}
2716
2717/// Fluent builder for creating and registering custom SQL dialects.
2718///
2719/// A custom dialect is based on an existing built-in dialect and allows selective
2720/// overrides of tokenizer configuration, generator configuration, and expression
2721/// transforms.
2722///
2723/// # Example
2724///
2725/// ```rust,ignore
2726/// use polyglot_sql::dialects::{CustomDialectBuilder, DialectType, Dialect};
2727/// use polyglot_sql::generator::NormalizeFunctions;
2728///
2729/// CustomDialectBuilder::new("my_postgres")
2730///     .based_on(DialectType::PostgreSQL)
2731///     .generator_config_modifier(|gc| {
2732///         gc.normalize_functions = NormalizeFunctions::Lower;
2733///     })
2734///     .register()
2735///     .unwrap();
2736///
2737/// let d = Dialect::get_by_name("my_postgres").unwrap();
2738/// let exprs = d.parse("SELECT COUNT(*)").unwrap();
2739/// let sql = d.generate(&exprs[0]).unwrap();
2740/// assert_eq!(sql, "select count(*)");
2741///
2742/// polyglot_sql::unregister_custom_dialect("my_postgres");
2743/// ```
2744pub struct CustomDialectBuilder {
2745    name: String,
2746    base_dialect: DialectType,
2747    tokenizer_modifier: Option<Box<dyn FnOnce(&mut TokenizerConfig)>>,
2748    #[cfg(feature = "generate")]
2749    generator_modifier: Option<Box<dyn FnOnce(&mut GeneratorConfig)>>,
2750    #[cfg(feature = "transpile")]
2751    transform: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2752    #[cfg(feature = "transpile")]
2753    preprocess: Option<Arc<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2754}
2755
2756impl CustomDialectBuilder {
2757    /// Create a new builder with the given name. Defaults to `Generic` as the base dialect.
2758    pub fn new(name: impl Into<String>) -> Self {
2759        Self {
2760            name: name.into(),
2761            base_dialect: DialectType::Generic,
2762            tokenizer_modifier: None,
2763            #[cfg(feature = "generate")]
2764            generator_modifier: None,
2765            #[cfg(feature = "transpile")]
2766            transform: None,
2767            #[cfg(feature = "transpile")]
2768            preprocess: None,
2769        }
2770    }
2771
2772    /// Set the base built-in dialect to inherit configuration from.
2773    pub fn based_on(mut self, dialect: DialectType) -> Self {
2774        self.base_dialect = dialect;
2775        self
2776    }
2777
2778    /// Provide a closure that modifies the tokenizer configuration inherited from the base dialect.
2779    pub fn tokenizer_config_modifier<F>(mut self, f: F) -> Self
2780    where
2781        F: FnOnce(&mut TokenizerConfig) + 'static,
2782    {
2783        self.tokenizer_modifier = Some(Box::new(f));
2784        self
2785    }
2786
2787    /// Provide a closure that modifies the generator configuration inherited from the base dialect.
2788    #[cfg(feature = "generate")]
2789    pub fn generator_config_modifier<F>(mut self, f: F) -> Self
2790    where
2791        F: FnOnce(&mut GeneratorConfig) + 'static,
2792    {
2793        self.generator_modifier = Some(Box::new(f));
2794        self
2795    }
2796
2797    /// Set a custom per-node expression transform function.
2798    ///
2799    /// This replaces the base dialect's transform. It is called on every expression
2800    /// node during the recursive transform pass.
2801    #[cfg(feature = "transpile")]
2802    pub fn transform_fn<F>(mut self, f: F) -> Self
2803    where
2804        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2805    {
2806        self.transform = Some(Arc::new(f));
2807        self
2808    }
2809
2810    /// Set a custom whole-tree preprocessing function.
2811    ///
2812    /// This replaces the base dialect's built-in preprocessing. It is called once
2813    /// on the entire expression tree before the recursive per-node transform.
2814    #[cfg(feature = "transpile")]
2815    pub fn preprocess_fn<F>(mut self, f: F) -> Self
2816    where
2817        F: Fn(Expression) -> Result<Expression> + Send + Sync + 'static,
2818    {
2819        self.preprocess = Some(Arc::new(f));
2820        self
2821    }
2822
2823    /// Build the custom dialect configuration and register it in the global registry.
2824    ///
2825    /// Returns an error if:
2826    /// - The name collides with a built-in dialect name
2827    /// - A custom dialect with the same name is already registered
2828    pub fn register(self) -> Result<()> {
2829        // Reject names that collide with built-in dialects
2830        if DialectType::from_str(&self.name).is_ok() {
2831            return Err(crate::error::Error::parse(
2832                format!(
2833                    "Cannot register custom dialect '{}': name collides with built-in dialect",
2834                    self.name
2835                ),
2836                0,
2837                0,
2838                0,
2839                0,
2840            ));
2841        }
2842
2843        // Get base configs
2844        let base_configs = configs_for_dialect_type(self.base_dialect);
2845        let mut tok_config = (*base_configs.tokenizer_config).clone();
2846        #[cfg(feature = "generate")]
2847        let mut gen_config = (*base_configs.generator_config).clone();
2848
2849        // Apply modifiers
2850        if let Some(tok_mod) = self.tokenizer_modifier {
2851            tok_mod(&mut tok_config);
2852        }
2853        #[cfg(feature = "generate")]
2854        if let Some(gen_mod) = self.generator_modifier {
2855            gen_mod(&mut gen_config);
2856        }
2857
2858        let config = CustomDialectConfig {
2859            name: self.name.clone(),
2860            base_dialect: self.base_dialect,
2861            tokenizer_config: Arc::new(tok_config),
2862            #[cfg(feature = "generate")]
2863            generator_config: gen_config,
2864            #[cfg(feature = "transpile")]
2865            transform: self.transform,
2866            #[cfg(feature = "transpile")]
2867            preprocess: self.preprocess,
2868        };
2869
2870        register_custom_dialect(config)
2871    }
2872}
2873
2874use std::str::FromStr;
2875
2876fn register_custom_dialect(config: CustomDialectConfig) -> Result<()> {
2877    let mut registry = CUSTOM_DIALECT_REGISTRY.write().map_err(|e| {
2878        crate::error::Error::parse(format!("Registry lock poisoned: {}", e), 0, 0, 0, 0)
2879    })?;
2880
2881    if registry.contains_key(&config.name) {
2882        return Err(crate::error::Error::parse(
2883            format!("Custom dialect '{}' is already registered", config.name),
2884            0,
2885            0,
2886            0,
2887            0,
2888        ));
2889    }
2890
2891    registry.insert(config.name.clone(), Arc::new(config));
2892    Ok(())
2893}
2894
2895/// Remove a custom dialect from the global registry.
2896///
2897/// Returns `true` if a dialect with that name was found and removed,
2898/// `false` if no such custom dialect existed.
2899pub fn unregister_custom_dialect(name: &str) -> bool {
2900    if let Ok(mut registry) = CUSTOM_DIALECT_REGISTRY.write() {
2901        registry.remove(name).is_some()
2902    } else {
2903        false
2904    }
2905}
2906
2907fn get_custom_dialect_config(name: &str) -> Option<Arc<CustomDialectConfig>> {
2908    CUSTOM_DIALECT_REGISTRY
2909        .read()
2910        .ok()
2911        .and_then(|registry| registry.get(name).cloned())
2912}
2913
2914/// Main entry point for dialect-specific SQL operations.
2915///
2916/// A `Dialect` bundles together a tokenizer, generator configuration, and expression
2917/// transformer for a specific SQL database engine. It is the high-level API through
2918/// which callers parse, generate, transform, and transpile SQL.
2919///
2920/// # Usage
2921///
2922/// ```rust,ignore
2923/// use polyglot_sql::dialects::{Dialect, DialectType};
2924///
2925/// // Parse PostgreSQL SQL into an AST
2926/// let pg = Dialect::get(DialectType::PostgreSQL);
2927/// let exprs = pg.parse("SELECT id, name FROM users WHERE active")?;
2928///
2929/// // Transpile from PostgreSQL to BigQuery
2930/// let results = pg.transpile("SELECT NOW()", DialectType::BigQuery)?;
2931/// assert_eq!(results[0], "SELECT CURRENT_TIMESTAMP()");
2932/// ```
2933///
2934/// Obtain an instance via [`Dialect::get`] or [`Dialect::get_by_name`].
2935/// The struct is `Send + Sync` safe so it can be shared across threads.
2936pub struct Dialect {
2937    dialect_type: DialectType,
2938    tokenizer: Tokenizer,
2939    #[cfg(feature = "generate")]
2940    generator_config: Arc<GeneratorConfig>,
2941    #[cfg(feature = "transpile")]
2942    transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
2943    /// Optional function to get expression-specific generator config (for hybrid dialects like Athena).
2944    #[cfg(feature = "generate")]
2945    generator_config_for_expr: Option<Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>>,
2946    /// Optional custom preprocessing function (overrides built-in preprocess for custom dialects).
2947    #[cfg(feature = "transpile")]
2948    custom_preprocess: Option<Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>>,
2949}
2950
2951/// Options for [`Dialect::transpile_with`].
2952///
2953/// Use [`TranspileOptions::default`] for defaults, then tweak the fields you need.
2954/// The struct is marked `#[non_exhaustive]` so new fields can be added without
2955/// breaking the API.
2956///
2957/// The struct derives `Serialize`/`Deserialize` using camelCase field names so
2958/// it can be round-tripped over JSON bridges (C FFI, WASM) without mapping.
2959#[cfg(feature = "transpile")]
2960#[derive(Debug, Clone, Serialize, Deserialize)]
2961#[serde(rename_all = "camelCase", default)]
2962#[non_exhaustive]
2963pub struct TranspileOptions {
2964    /// Whether to pretty-print the output SQL.
2965    pub pretty: bool,
2966    /// How unsupported target-dialect constructs should be handled.
2967    ///
2968    /// The default is [`UnsupportedLevel::Warn`], which preserves the current
2969    /// compatibility behavior and continues transpilation.
2970    pub unsupported_level: UnsupportedLevel,
2971    /// Maximum number of unsupported diagnostics to include in raised errors.
2972    pub max_unsupported: usize,
2973    /// Complexity guard limits used while parsing, transforming, and generating.
2974    pub complexity_guard: ComplexityGuardOptions,
2975}
2976
2977#[cfg(feature = "transpile")]
2978impl Default for TranspileOptions {
2979    fn default() -> Self {
2980        Self {
2981            pretty: false,
2982            unsupported_level: UnsupportedLevel::Warn,
2983            max_unsupported: 3,
2984            complexity_guard: ComplexityGuardOptions::default(),
2985        }
2986    }
2987}
2988
2989#[cfg(feature = "transpile")]
2990impl TranspileOptions {
2991    /// Construct options with pretty-printing enabled.
2992    pub fn pretty() -> Self {
2993        Self {
2994            pretty: true,
2995            ..Default::default()
2996        }
2997    }
2998
2999    /// Construct options that raise when known unsupported constructs remain.
3000    pub fn strict() -> Self {
3001        Self {
3002            unsupported_level: UnsupportedLevel::Raise,
3003            ..Default::default()
3004        }
3005    }
3006
3007    /// Set how unsupported target-dialect constructs should be handled.
3008    pub fn with_unsupported_level(mut self, level: UnsupportedLevel) -> Self {
3009        self.unsupported_level = level;
3010        self
3011    }
3012
3013    /// Set the maximum number of unsupported diagnostics to include in raised errors.
3014    pub fn with_max_unsupported(mut self, max: usize) -> Self {
3015        self.max_unsupported = max;
3016        self
3017    }
3018
3019    /// Set complexity guard limits for parse/transpile/generate recursion-heavy paths.
3020    pub fn with_complexity_guard(mut self, guard: ComplexityGuardOptions) -> Self {
3021        self.complexity_guard = guard;
3022        self
3023    }
3024}
3025
3026/// A value that can be used as the target dialect in [`Dialect::transpile`] /
3027/// [`Dialect::transpile_with`].
3028///
3029/// Implemented for [`DialectType`] (built-in dialect enum) and `&Dialect` (any
3030/// dialect handle, including custom ones). End users do not normally need to
3031/// implement this trait themselves.
3032#[cfg(feature = "transpile")]
3033pub trait TranspileTarget {
3034    /// Invoke `f` with a reference to the resolved target dialect.
3035    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R;
3036}
3037
3038#[cfg(feature = "transpile")]
3039impl TranspileTarget for DialectType {
3040    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3041        f(&Dialect::get(self))
3042    }
3043}
3044
3045#[cfg(feature = "transpile")]
3046impl TranspileTarget for &Dialect {
3047    fn with_dialect<R>(self, f: impl FnOnce(&Dialect) -> R) -> R {
3048        f(self)
3049    }
3050}
3051
3052impl Dialect {
3053    /// Creates a fully configured [`Dialect`] instance for the given [`DialectType`].
3054    ///
3055    /// This is the primary constructor. It initializes the tokenizer, generator config,
3056    /// and expression transformer based on the dialect's [`DialectImpl`] implementation.
3057    /// For hybrid dialects like Athena, it also sets up expression-specific generator
3058    /// config routing.
3059    pub fn get(dialect_type: DialectType) -> Self {
3060        let configs = configs_for_dialect_type(dialect_type);
3061        let tokenizer_config = configs.tokenizer_config;
3062        #[cfg(feature = "generate")]
3063        let generator_config = configs.generator_config;
3064        #[cfg(feature = "transpile")]
3065        let transformer = configs.transformer;
3066
3067        // Set up expression-specific generator config for hybrid dialects
3068        #[cfg(feature = "generate")]
3069        let generator_config_for_expr: Option<
3070            Box<dyn Fn(&Expression) -> GeneratorConfig + Send + Sync>,
3071        > = match dialect_type {
3072            #[cfg(feature = "dialect-athena")]
3073            DialectType::Athena => Some(Box::new(|expr| {
3074                AthenaDialect.generator_config_for_expr(expr)
3075            })),
3076            _ => None,
3077        };
3078
3079        Self {
3080            dialect_type,
3081            tokenizer: Tokenizer::from_shared_config(tokenizer_config),
3082            #[cfg(feature = "generate")]
3083            generator_config,
3084            #[cfg(feature = "transpile")]
3085            transformer,
3086            #[cfg(feature = "generate")]
3087            generator_config_for_expr,
3088            #[cfg(feature = "transpile")]
3089            custom_preprocess: None,
3090        }
3091    }
3092
3093    /// Look up a dialect by string name.
3094    ///
3095    /// Checks built-in dialect names first (via [`DialectType::from_str`]), then
3096    /// falls back to the custom dialect registry. Returns `None` if no dialect
3097    /// with the given name exists.
3098    pub fn get_by_name(name: &str) -> Option<Self> {
3099        // Try built-in first
3100        if let Ok(dt) = DialectType::from_str(name) {
3101            return Some(Self::get(dt));
3102        }
3103
3104        // Try custom registry
3105        let config = get_custom_dialect_config(name)?;
3106        Some(Self::from_custom_config(&config))
3107    }
3108
3109    /// Construct a `Dialect` from a custom dialect configuration.
3110    fn from_custom_config(config: &CustomDialectConfig) -> Self {
3111        // Build the transformer: use custom if provided, else use base dialect's
3112        #[cfg(feature = "transpile")]
3113        let transformer: Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync> =
3114            if let Some(ref custom_transform) = config.transform {
3115                let t = Arc::clone(custom_transform);
3116                Box::new(move |e| t(e))
3117            } else {
3118                configs_for_dialect_type(config.base_dialect).transformer
3119            };
3120
3121        // Build the custom preprocess: use custom if provided
3122        #[cfg(feature = "transpile")]
3123        let custom_preprocess: Option<
3124            Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>,
3125        > = config.preprocess.as_ref().map(|p| {
3126            let p = Arc::clone(p);
3127            Box::new(move |e: Expression| p(e))
3128                as Box<dyn Fn(Expression) -> Result<Expression> + Send + Sync>
3129        });
3130
3131        Self {
3132            dialect_type: config.base_dialect,
3133            tokenizer: Tokenizer::from_shared_config(config.tokenizer_config.clone()),
3134            #[cfg(feature = "generate")]
3135            generator_config: Arc::new(config.generator_config.clone()),
3136            #[cfg(feature = "transpile")]
3137            transformer,
3138            #[cfg(feature = "generate")]
3139            generator_config_for_expr: None,
3140            #[cfg(feature = "transpile")]
3141            custom_preprocess,
3142        }
3143    }
3144
3145    /// Get the dialect type
3146    pub fn dialect_type(&self) -> DialectType {
3147        self.dialect_type
3148    }
3149
3150    /// Get the generator configuration
3151    #[cfg(feature = "generate")]
3152    pub fn generator_config(&self) -> &GeneratorConfig {
3153        &self.generator_config
3154    }
3155
3156    /// Parses a SQL string into a list of [`Expression`] AST nodes.
3157    ///
3158    /// The input may contain multiple semicolon-separated statements; each one
3159    /// produces a separate element in the returned vector. Tokenization uses
3160    /// this dialect's configured tokenizer, and parsing uses the dialect-aware parser.
3161    pub fn parse(&self, sql: &str) -> Result<Vec<Expression>> {
3162        self.parse_with_guard(sql, self.default_complexity_guard())
3163    }
3164
3165    /// Parse using per-call limits without changing this dialect's defaults.
3166    pub fn parse_with_options(&self, sql: &str, options: &ParseOptions) -> Result<Vec<Expression>> {
3167        self.parse_with_guard(
3168            sql,
3169            options
3170                .complexity_guard
3171                .unwrap_or_else(|| self.default_complexity_guard()),
3172        )
3173    }
3174
3175    fn parse_with_guard(
3176        &self,
3177        sql: &str,
3178        complexity_guard: ComplexityGuardOptions,
3179    ) -> Result<Vec<Expression>> {
3180        enforce_input(sql, &complexity_guard)?;
3181        let source: Arc<str> = Arc::from(sql);
3182        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3183        let config = crate::parser::ParserConfig {
3184            dialect: Some(self.dialect_type),
3185            complexity_guard,
3186            ..Default::default()
3187        };
3188        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3189        parser.parse()
3190    }
3191
3192    fn default_complexity_guard(&self) -> ComplexityGuardOptions {
3193        let mut guard = ComplexityGuardOptions::default();
3194        if matches!(self.dialect_type, DialectType::ClickHouse) {
3195            guard.max_ast_depth = Some(4_096);
3196            guard.max_function_call_depth = Some(512);
3197        }
3198        guard
3199    }
3200
3201    #[cfg(feature = "transpile")]
3202    fn default_transpile_complexity_guard(
3203        &self,
3204        target_dialect: &Dialect,
3205        guard: ComplexityGuardOptions,
3206    ) -> ComplexityGuardOptions {
3207        if guard != ComplexityGuardOptions::default() {
3208            return guard;
3209        }
3210
3211        if matches!(self.dialect_type, DialectType::ClickHouse)
3212            || matches!(target_dialect.dialect_type, DialectType::ClickHouse)
3213        {
3214            let mut guard = guard;
3215            guard.max_ast_depth = Some(4_096);
3216            guard.max_function_call_depth = Some(512);
3217            guard
3218        } else {
3219            guard
3220        }
3221    }
3222
3223    /// Parse a standalone SQL data type using this dialect's tokenizer and parser.
3224    ///
3225    /// This accepts type strings such as `DECIMAL(10, 2)`, `INT[]`, or
3226    /// `STRUCT(a INT, b VARCHAR)` without requiring a surrounding statement.
3227    pub fn parse_data_type(&self, sql: &str) -> Result<DataType> {
3228        self.parse_data_type_with_options(sql, &ParseOptions::default())
3229    }
3230
3231    /// Parse a standalone data type using per-call complexity limits.
3232    pub fn parse_data_type_with_options(
3233        &self,
3234        sql: &str,
3235        options: &ParseOptions,
3236    ) -> Result<DataType> {
3237        let complexity_guard = options
3238            .complexity_guard
3239            .unwrap_or_else(|| self.default_complexity_guard());
3240        enforce_input(sql, &complexity_guard)?;
3241        let source: Arc<str> = Arc::from(sql);
3242        let (tokens, token_guard_stats) = self.tokenizer.tokenize_for_parser(&source)?;
3243        let config = crate::parser::ParserConfig {
3244            dialect: Some(self.dialect_type),
3245            complexity_guard,
3246            ..Default::default()
3247        };
3248        let mut parser = Parser::with_parser_tokens(tokens, token_guard_stats, config, source);
3249        parser.parse_standalone_data_type()
3250    }
3251
3252    /// Tokenize SQL using this dialect's tokenizer configuration.
3253    pub fn tokenize(&self, sql: &str) -> Result<Vec<Token>> {
3254        self.tokenizer.tokenize(sql)
3255    }
3256
3257    /// Get the generator config for a specific expression (supports hybrid dialects).
3258    /// Returns an owned `GeneratorConfig` suitable for mutation before generation.
3259    #[cfg(feature = "generate")]
3260    fn get_config_for_expr(&self, expr: &Expression) -> GeneratorConfig {
3261        if let Some(ref config_fn) = self.generator_config_for_expr {
3262            config_fn(expr)
3263        } else {
3264            (*self.generator_config).clone()
3265        }
3266    }
3267
3268    /// Generates a SQL string from an [`Expression`] AST node.
3269    ///
3270    /// The output uses this dialect's generator configuration for identifier quoting,
3271    /// keyword casing, function name normalization, and syntax style. The result is
3272    /// a single-line (non-pretty) SQL string.
3273    #[cfg(feature = "generate")]
3274    pub fn generate(&self, expr: &Expression) -> Result<String> {
3275        // Fast path: when no per-expression config override, share the Arc cheaply.
3276        if self.generator_config_for_expr.is_none() {
3277            let mut generator = Generator::with_arc_config(self.generator_config.clone());
3278            return generator.generate(expr);
3279        }
3280        let config = self.get_config_for_expr(expr);
3281        let mut generator = Generator::with_config(config);
3282        generator.generate(expr)
3283    }
3284
3285    /// Generate SQL with pretty printing, retaining explicit null-ordering clauses.
3286    #[cfg(feature = "generate")]
3287    pub fn generate_pretty(&self, expr: &Expression) -> Result<String> {
3288        let mut config = self.get_config_for_expr(expr);
3289        config.pretty = true;
3290        let mut generator = Generator::with_config(config).with_preserved_null_ordering();
3291        generator.generate(expr)
3292    }
3293
3294    /// Render analysis facts with the same AST limits used to accept the input.
3295    #[cfg(all(feature = "semantic", feature = "generate"))]
3296    pub(crate) fn generate_with_guard(
3297        &self,
3298        expr: &Expression,
3299        guard: Option<ComplexityGuardOptions>,
3300    ) -> Result<String> {
3301        let Some(guard) = guard else {
3302            return self.generate(expr);
3303        };
3304        let mut config = self.get_config_for_expr(expr);
3305        config.complexity_guard = guard;
3306        Generator::with_config(config).generate(expr)
3307    }
3308
3309    /// Generate SQL from an expression with source dialect info (for transpilation)
3310    #[cfg(feature = "generate")]
3311    pub fn generate_with_source(&self, expr: &Expression, source: DialectType) -> Result<String> {
3312        let mut config = self.get_config_for_expr(expr);
3313        config.source_dialect = Some(source);
3314        let mut generator = Generator::with_config(config);
3315        generator.generate(expr)
3316    }
3317
3318    /// Generate SQL from an expression with pretty printing and source dialect info
3319    #[cfg(feature = "generate")]
3320    pub fn generate_pretty_with_source(
3321        &self,
3322        expr: &Expression,
3323        source: DialectType,
3324    ) -> Result<String> {
3325        let mut config = self.get_config_for_expr(expr);
3326        config.pretty = true;
3327        config.source_dialect = Some(source);
3328        let mut generator = Generator::with_config(config);
3329        generator.generate(expr)
3330    }
3331
3332    /// Generate SQL from an expression with source dialect and transpile options.
3333    #[cfg(all(feature = "generate", feature = "transpile"))]
3334    fn generate_with_transpile_options(
3335        &self,
3336        expr: &Expression,
3337        source: DialectType,
3338        opts: &TranspileOptions,
3339    ) -> Result<String> {
3340        let mut config = self.get_config_for_expr(expr);
3341        config.source_dialect = Some(source);
3342        config.pretty = opts.pretty;
3343        config.unsupported_level = opts.unsupported_level;
3344        config.max_unsupported = opts.max_unsupported.max(1);
3345        config.complexity_guard = opts.complexity_guard;
3346        let mut generator = Generator::with_config(config);
3347        generator.generate(expr)
3348    }
3349
3350    /// Generate SQL from an expression with forced identifier quoting (identify=True)
3351    #[cfg(feature = "generate")]
3352    pub fn generate_with_identify(&self, expr: &Expression) -> Result<String> {
3353        let mut config = self.get_config_for_expr(expr);
3354        config.always_quote_identifiers = true;
3355        let mut generator = Generator::with_config(config);
3356        generator.generate(expr)
3357    }
3358
3359    /// Generate SQL from an expression with pretty printing and forced identifier quoting
3360    #[cfg(feature = "generate")]
3361    pub fn generate_pretty_with_identify(&self, expr: &Expression) -> Result<String> {
3362        let mut config = (*self.generator_config).clone();
3363        config.pretty = true;
3364        config.always_quote_identifiers = true;
3365        let mut generator = Generator::with_config(config);
3366        generator.generate(expr)
3367    }
3368
3369    /// Generate SQL from an expression with caller-specified config overrides
3370    #[cfg(feature = "generate")]
3371    pub fn generate_with_overrides(
3372        &self,
3373        expr: &Expression,
3374        overrides: impl FnOnce(&mut GeneratorConfig),
3375    ) -> Result<String> {
3376        let mut config = self.get_config_for_expr(expr);
3377        overrides(&mut config);
3378        let mut generator = Generator::with_config(config);
3379        generator.generate(expr)
3380    }
3381
3382    /// Transforms an expression tree to conform to this dialect's syntax and semantics.
3383    ///
3384    /// The transformation proceeds in two phases:
3385    /// 1. **Preprocessing** -- whole-tree structural rewrites such as eliminating QUALIFY,
3386    ///    ensuring boolean predicates, or converting DISTINCT ON to a window-function pattern.
3387    /// 2. **Recursive per-node transform** -- a bottom-up pass via [`transform_recursive`]
3388    ///    that applies this dialect's [`DialectImpl::transform_expr`] to every node.
3389    ///
3390    /// This method is used both during transpilation (to rewrite an AST for a target dialect)
3391    /// and for identity transforms (normalizing SQL within the same dialect).
3392    #[cfg(feature = "transpile")]
3393    pub fn transform(&self, expr: Expression) -> Result<Expression> {
3394        self.transform_with_guard(expr, self.default_complexity_guard())
3395    }
3396
3397    #[cfg(feature = "transpile")]
3398    fn transform_with_guard(
3399        &self,
3400        expr: Expression,
3401        complexity_guard: ComplexityGuardOptions,
3402    ) -> Result<Expression> {
3403        enforce_generate_ast(&expr, &complexity_guard)?;
3404        // Apply preprocessing transforms based on dialect
3405        let preprocessed = self.preprocess(expr)?;
3406        // Then apply recursive transformation
3407        transform_recursive(preprocessed, &|node| {
3408            // Calls with retained source semantics are lowered by the generator;
3409            // ordinary AST visitors and optimizer passes still see their shared node kind.
3410            if node.source_dialect().is_some()
3411                || matches!(&node, Expression::Function(f) if !f.qualified_name.is_empty())
3412            {
3413                Ok(node)
3414            } else {
3415                (self.transformer)(node)
3416            }
3417        })
3418    }
3419
3420    /// Apply dialect-specific preprocessing transforms
3421    #[cfg(feature = "transpile")]
3422    fn preprocess(&self, expr: Expression) -> Result<Expression> {
3423        // If a custom preprocess function is set, use it instead of the built-in logic
3424        if let Some(ref custom_preprocess) = self.custom_preprocess {
3425            return custom_preprocess(expr);
3426        }
3427
3428        #[cfg(any(
3429            feature = "dialect-mysql",
3430            feature = "dialect-postgresql",
3431            feature = "dialect-bigquery",
3432            feature = "dialect-snowflake",
3433            feature = "dialect-tsql",
3434            feature = "dialect-spark",
3435            feature = "dialect-databricks",
3436            feature = "dialect-hive",
3437            feature = "dialect-sqlite",
3438            feature = "dialect-trino",
3439            feature = "dialect-presto",
3440            feature = "dialect-athena",
3441            feature = "dialect-duckdb",
3442            feature = "dialect-redshift",
3443            feature = "dialect-starrocks",
3444            feature = "dialect-oracle",
3445            feature = "dialect-clickhouse",
3446            feature = "dialect-fabric",
3447            feature = "dialect-vertica",
3448        ))]
3449        use crate::transforms;
3450
3451        match self.dialect_type {
3452            // MySQL doesn't support QUALIFY, DISTINCT ON, FULL OUTER JOIN
3453            // MySQL doesn't natively support GENERATE_DATE_ARRAY (expand to recursive CTE)
3454            #[cfg(feature = "dialect-mysql")]
3455            DialectType::MySQL => {
3456                let expr = transforms::eliminate_qualify(expr)?;
3457                let expr = transforms::eliminate_full_outer_join(expr)?;
3458                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3459                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3460                Ok(expr)
3461            }
3462            // PostgreSQL doesn't support QUALIFY
3463            // PostgreSQL: UNNEST(GENERATE_SERIES) -> subquery wrapping
3464            // PostgreSQL: Normalize SET ... TO to SET ... = in CREATE FUNCTION
3465            #[cfg(feature = "dialect-postgresql")]
3466            DialectType::PostgreSQL => {
3467                let expr = transforms::eliminate_qualify(expr)?;
3468                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3469                let expr = transforms::unwrap_unnest_generate_series_for_postgres(expr)?;
3470                // Normalize SET ... TO to SET ... = in CREATE FUNCTION
3471                // Only normalize when sqlglot would fully parse (no body) —
3472                // sqlglot falls back to Command for complex function bodies,
3473                // preserving the original text including TO.
3474                let expr = if let Expression::CreateFunction(mut cf) = expr {
3475                    if cf.body.is_none() {
3476                        for opt in &mut cf.set_options {
3477                            if let crate::expressions::FunctionSetValue::Value { use_to, .. } =
3478                                &mut opt.value
3479                            {
3480                                *use_to = false;
3481                            }
3482                        }
3483                    }
3484                    Expression::CreateFunction(cf)
3485                } else {
3486                    expr
3487                };
3488                Ok(expr)
3489            }
3490            // BigQuery doesn't support DISTINCT ON or CTE column aliases
3491            #[cfg(feature = "dialect-bigquery")]
3492            DialectType::BigQuery => {
3493                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3494                let expr = transforms::pushdown_cte_column_names(expr)?;
3495                let expr = transforms::explode_projection_to_unnest(expr, DialectType::BigQuery)?;
3496                Ok(expr)
3497            }
3498            // Snowflake
3499            #[cfg(feature = "dialect-snowflake")]
3500            DialectType::Snowflake => {
3501                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3502                let expr = transforms::eliminate_window_clause(expr)?;
3503                let expr = transforms::snowflake_flatten_projection_to_unnest(expr)?;
3504                Ok(expr)
3505            }
3506            // TSQL doesn't support QUALIFY
3507            // TSQL requires boolean expressions in WHERE/HAVING (no implicit truthiness)
3508            // TSQL doesn't support CTEs in subqueries (hoist to top level)
3509            // NOTE: no_limit_order_by_union is handled in cross_dialect_normalize (not preprocess)
3510            // to avoid breaking TSQL identity tests where ORDER BY on UNION is valid
3511            #[cfg(feature = "dialect-tsql")]
3512            DialectType::TSQL => {
3513                let expr = transforms::eliminate_qualify(expr)?;
3514                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3515                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3516                let expr =
3517                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::TSQL)?;
3518                let expr = transforms::ensure_bools(expr)?;
3519                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3520                let expr = transforms::strip_cte_materialization(expr)?;
3521                let expr = transforms::move_ctes_to_top_level(expr)?;
3522                let expr = transforms::qualify_derived_table_outputs(expr)?;
3523                Ok(expr)
3524            }
3525            // Fabric shares T-SQL predicate rules and CTE placement restrictions,
3526            // but keeps Fabric-specific APPLY and derived-table behavior separate.
3527            #[cfg(feature = "dialect-fabric")]
3528            DialectType::Fabric => {
3529                let expr = transforms::normalize_grouping_sets_for_tsql(expr)?;
3530                let expr =
3531                    transforms::expand_distinct_grouping_sets_for_tsql(expr, DialectType::Fabric)?;
3532                let expr = transforms::ensure_bools(expr)?;
3533                let expr = transforms::strip_cte_materialization(expr)?;
3534                let expr = transforms::move_ctes_to_top_level(expr)?;
3535                Ok(expr)
3536            }
3537            // Spark doesn't support QUALIFY (but Databricks does)
3538            // Spark doesn't support CTEs in subqueries (hoist to top level)
3539            #[cfg(feature = "dialect-spark")]
3540            DialectType::Spark => {
3541                let expr = transforms::eliminate_qualify(expr)?;
3542                let expr = transforms::add_auto_table_alias(expr)?;
3543                let expr = transforms::simplify_nested_paren_values(expr)?;
3544                let expr = transforms::move_ctes_to_top_level(expr)?;
3545                Ok(expr)
3546            }
3547            // Databricks supports QUALIFY natively
3548            // Databricks doesn't support CTEs in subqueries (hoist to top level)
3549            #[cfg(feature = "dialect-databricks")]
3550            DialectType::Databricks => {
3551                let expr = transforms::add_auto_table_alias(expr)?;
3552                let expr = transforms::simplify_nested_paren_values(expr)?;
3553                let expr = transforms::move_ctes_to_top_level(expr)?;
3554                Ok(expr)
3555            }
3556            // Hive doesn't support QUALIFY or CTEs in subqueries
3557            #[cfg(feature = "dialect-hive")]
3558            DialectType::Hive => {
3559                let expr = transforms::eliminate_qualify(expr)?;
3560                let expr = transforms::move_ctes_to_top_level(expr)?;
3561                Ok(expr)
3562            }
3563            // SQLite doesn't support QUALIFY
3564            #[cfg(feature = "dialect-sqlite")]
3565            DialectType::SQLite => {
3566                let expr = transforms::eliminate_qualify(expr)?;
3567                Ok(expr)
3568            }
3569            // Trino doesn't support QUALIFY
3570            #[cfg(feature = "dialect-trino")]
3571            DialectType::Trino => {
3572                let expr = transforms::eliminate_qualify(expr)?;
3573                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Trino)?;
3574                Ok(expr)
3575            }
3576            // Presto doesn't support QUALIFY or WINDOW clause
3577            #[cfg(feature = "dialect-presto")]
3578            DialectType::Presto => {
3579                let expr = transforms::eliminate_qualify(expr)?;
3580                let expr = transforms::eliminate_window_clause(expr)?;
3581                let expr = transforms::explode_projection_to_unnest(expr, DialectType::Presto)?;
3582                Ok(expr)
3583            }
3584            #[cfg(feature = "dialect-athena")]
3585            DialectType::Athena => {
3586                let expr = transforms::eliminate_qualify(expr)?;
3587                let expr = transforms::eliminate_window_clause(expr)?;
3588                transforms::explode_projection_to_unnest(expr, DialectType::Athena)
3589            }
3590            // DuckDB supports QUALIFY - no elimination needed
3591            // Expand POSEXPLODE to GENERATE_SUBSCRIPTS + UNNEST
3592            // Expand LIKE ANY / ILIKE ANY to OR chains (DuckDB doesn't support quantifiers)
3593            #[cfg(feature = "dialect-duckdb")]
3594            DialectType::DuckDB => {
3595                let expr = transforms::expand_posexplode_duckdb(expr)?;
3596                let expr = transforms::expand_like_any(expr)?;
3597                Ok(expr)
3598            }
3599            // Redshift supports QUALIFY, but not WINDOW or GENERATE_DATE_ARRAY.
3600            #[cfg(feature = "dialect-redshift")]
3601            DialectType::Redshift => {
3602                let expr = transforms::eliminate_window_clause(expr)?;
3603                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3604                Ok(expr)
3605            }
3606            // StarRocks supports only restricted QUALIFY forms; lower the general form.
3607            #[cfg(feature = "dialect-starrocks")]
3608            DialectType::StarRocks => {
3609                let expr = transforms::eliminate_qualify(expr)?;
3610                let expr = transforms::expand_between_in_delete(expr)?;
3611                let expr = transforms::eliminate_distinct_on_for_dialect(
3612                    expr,
3613                    Some(DialectType::StarRocks),
3614                    Some(DialectType::StarRocks),
3615                )?;
3616                let expr = transforms::unnest_generate_date_array_using_recursive_cte(expr)?;
3617                Ok(expr)
3618            }
3619            // DataFusion supports QUALIFY and semi/anti joins natively
3620            #[cfg(feature = "dialect-datafusion")]
3621            DialectType::DataFusion => Ok(expr),
3622            // Vertica doesn't support QUALIFY or semi/anti join syntax
3623            #[cfg(feature = "dialect-vertica")]
3624            DialectType::Vertica => {
3625                let expr = transforms::eliminate_qualify(expr)?;
3626                let expr = transforms::eliminate_semi_and_anti_joins(expr)?;
3627                Ok(expr)
3628            }
3629            // Oracle doesn't support QUALIFY
3630            #[cfg(feature = "dialect-oracle")]
3631            DialectType::Oracle => {
3632                let expr = transforms::eliminate_qualify(expr)?;
3633                Ok(expr)
3634            }
3635            // Drill - no special preprocessing needed
3636            #[cfg(feature = "dialect-drill")]
3637            DialectType::Drill => Ok(expr),
3638            // Teradata - no special preprocessing needed
3639            #[cfg(feature = "dialect-teradata")]
3640            DialectType::Teradata => Ok(expr),
3641            // ClickHouse doesn't support ORDER BY/LIMIT directly on UNION
3642            #[cfg(feature = "dialect-clickhouse")]
3643            DialectType::ClickHouse => {
3644                let expr = transforms::no_limit_order_by_union(expr)?;
3645                Ok(expr)
3646            }
3647            // Other dialects - no preprocessing
3648            _ => Ok(expr),
3649        }
3650    }
3651
3652    /// Transpile SQL from this dialect to the given target dialect.
3653    ///
3654    /// The target may be specified as either a built-in [`DialectType`] enum variant
3655    /// or as a reference to a [`Dialect`] handle (built-in or custom). Both work:
3656    ///
3657    /// ```rust,ignore
3658    /// let pg = Dialect::get(DialectType::PostgreSQL);
3659    /// pg.transpile("SELECT NOW()", DialectType::BigQuery)?;   // enum
3660    /// pg.transpile("SELECT NOW()", &custom_dialect)?;         // handle
3661    /// ```
3662    ///
3663    /// For pretty-printing or other options, use [`transpile_with`](Self::transpile_with).
3664    #[cfg(feature = "transpile")]
3665    pub fn transpile<T: TranspileTarget>(&self, sql: &str, target: T) -> Result<Vec<String>> {
3666        self.transpile_with(sql, target, TranspileOptions::default())
3667    }
3668
3669    /// Transpile SQL with configurable [`TranspileOptions`] (e.g. pretty-printing).
3670    #[cfg(feature = "transpile")]
3671    pub fn transpile_with<T: TranspileTarget>(
3672        &self,
3673        sql: &str,
3674        target: T,
3675        opts: TranspileOptions,
3676    ) -> Result<Vec<String>> {
3677        target.with_dialect(|td| self.transpile_inner(sql, td, &opts))
3678    }
3679
3680    #[cfg(feature = "transpile")]
3681    fn transpile_inner(
3682        &self,
3683        sql: &str,
3684        target_dialect: &Dialect,
3685        opts: &TranspileOptions,
3686    ) -> Result<Vec<String>> {
3687        let mut effective_opts = opts.clone();
3688        effective_opts.complexity_guard =
3689            self.default_transpile_complexity_guard(target_dialect, opts.complexity_guard);
3690        let opts = &effective_opts;
3691        let target = target_dialect.dialect_type;
3692        if matches!(self.dialect_type, DialectType::PostgreSQL)
3693            && matches!(target, DialectType::SQLite)
3694        {
3695            self.reject_pgvector_distance_operators_for_sqlite(sql)?;
3696        }
3697        let expressions = self.parse_with_guard(sql, opts.complexity_guard)?;
3698        let generic_identity =
3699            self.dialect_type == DialectType::Generic && target == DialectType::Generic;
3700
3701        if generic_identity {
3702            return expressions
3703                .into_iter()
3704                .map(|expr| {
3705                    Self::reject_strict_unsupported(&expr, self.dialect_type, target, opts)?;
3706                    target_dialect.generate_with_transpile_options(&expr, self.dialect_type, opts)
3707                })
3708                .collect();
3709        }
3710
3711        expressions
3712            .into_iter()
3713            .map(|expr| {
3714                // Reject unsupported source semantics before normalization can
3715                // erase them. This AST-only pass shares conversion decisions with
3716                // the generator and never renders throwaway SQL.
3717                Self::reject_hana_source_semantics(
3718                    &expr,
3719                    self.dialect_type,
3720                    target_dialect.generator_config.dialect.unwrap_or_default(),
3721                )?;
3722
3723                let expr =
3724                    normalization::vertica::prepare_conversion(expr, self.dialect_type, target)?;
3725                normalization::vertica::validate_conversion(&expr, self.dialect_type, target)?;
3726                let expr = normalization::duckdb::prepare_integer_division(
3727                    expr,
3728                    self.dialect_type,
3729                    target,
3730                )?;
3731                // DuckDB source: normalize VARCHAR/CHAR to TEXT (DuckDB doesn't support
3732                // VARCHAR length constraints). This emulates Python sqlglot's DuckDB parser
3733                // where VARCHAR_LENGTH = None and VARCHAR maps to TEXT.
3734                //
3735                // Any source with native `ORDER BY ALL`: expand it to positional
3736                // `ORDER BY 1..n` for targets without it, on both plain SELECTs and
3737                // compound queries (whose ORDER BY lives on the set-op node).
3738                let source = self.dialect_type;
3739                let is_duckdb = matches!(source, DialectType::DuckDB);
3740                let expand_all = source.supports_order_by_all() && !target.supports_order_by_all();
3741                let expr = if source.supports_order_by_all() {
3742                    use crate::expressions::DataType as DT;
3743                    transform_recursive(expr, &|e| match e {
3744                        Expression::DataType(DT::VarChar { .. }) if is_duckdb => {
3745                            Ok(Expression::DataType(DT::Text))
3746                        }
3747                        Expression::DataType(DT::Char { .. }) if is_duckdb => {
3748                            Ok(Expression::DataType(DT::Text))
3749                        }
3750                        Expression::Select(sel) => {
3751                            expand_select_order_by_all(sel, source, target).map(Expression::Select)
3752                        }
3753                        Expression::Subquery(mut subquery) if expand_all => {
3754                            let width = query_output_width(&subquery.this);
3755                            expand_order_by_all(&mut subquery.order_by, width, source, target)?;
3756                            Ok(Expression::Subquery(subquery))
3757                        }
3758                        Expression::Union(mut u) if expand_all => {
3759                            let width = if u.by_name {
3760                                None
3761                            } else {
3762                                query_output_width(&u.left)
3763                            };
3764                            expand_order_by_all(&mut u.order_by, width, source, target)?;
3765                            Ok(Expression::Union(u))
3766                        }
3767                        Expression::Intersect(mut i) if expand_all => {
3768                            let width = query_output_width(&i.left);
3769                            expand_order_by_all(&mut i.order_by, width, source, target)?;
3770                            Ok(Expression::Intersect(i))
3771                        }
3772                        Expression::Except(mut ex) if expand_all => {
3773                            let width = query_output_width(&ex.left);
3774                            expand_order_by_all(&mut ex.order_by, width, source, target)?;
3775                            Ok(Expression::Except(ex))
3776                        }
3777                        _ => Ok(e),
3778                    })?
3779                } else {
3780                    expr
3781                };
3782
3783                Self::reject_clickhouse_session_semantics(&expr, self.dialect_type, target, opts)?;
3784                Self::reject_postgres_tsql_strict_regex_predicates(
3785                    &expr,
3786                    self.dialect_type,
3787                    target,
3788                    opts,
3789                )?;
3790                Self::reject_tsql_strict_json_constructor_return_types(
3791                    &expr,
3792                    self.dialect_type,
3793                    target,
3794                    opts,
3795                )?;
3796                Self::reject_postgres_tsql_strict_json_aggregate_modifiers(
3797                    &expr,
3798                    self.dialect_type,
3799                    target,
3800                    opts,
3801                )?;
3802
3803                // When source and target differ, first normalize the source dialect's
3804                // AST constructs to standard SQL, so that the target dialect can handle them.
3805                // This handles cases like Snowflake's SQUARE -> POWER, DIV0 -> CASE, etc.
3806                let normalized =
3807                    if self.dialect_type != target && self.dialect_type != DialectType::Generic {
3808                        self.transform_with_guard(expr, opts.complexity_guard)?
3809                    } else {
3810                        expr
3811                    };
3812
3813                // For TSQL source targeting non-TSQL: unwrap ISNULL(JSON_QUERY(...), JSON_VALUE(...))
3814                // to just JSON_QUERY(...) so cross_dialect_normalize can convert it cleanly.
3815                // The TSQL read transform wraps JsonQuery in ISNULL for identity, but for
3816                // cross-dialect transpilation we need the unwrapped JSON_QUERY.
3817                let normalized =
3818                    if matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
3819                        && !matches!(target, DialectType::TSQL | DialectType::Fabric)
3820                    {
3821                        transform_recursive(normalized, &|e| {
3822                            if let Expression::Function(ref f) = e {
3823                                if f.name.eq_ignore_ascii_case("ISNULL") && f.args.len() == 2 {
3824                                    // Check if first arg is JSON_QUERY and second is JSON_VALUE
3825                                    if let (
3826                                        Expression::Function(ref jq),
3827                                        Expression::Function(ref jv),
3828                                    ) = (&f.args[0], &f.args[1])
3829                                    {
3830                                        if jq.name.eq_ignore_ascii_case("JSON_QUERY")
3831                                            && jv.name.eq_ignore_ascii_case("JSON_VALUE")
3832                                        {
3833                                            // Unwrap: return just JSON_QUERY(...)
3834                                            return Ok(f.args[0].clone());
3835                                        }
3836                                    }
3837                                }
3838                            }
3839                            Ok(e)
3840                        })?
3841                    } else {
3842                        normalized
3843                    };
3844
3845                // Snowflake source to non-Snowflake target: CURRENT_TIME -> LOCALTIME.
3846                // Preserve precision for ClickHouse's Time64 lowering; retain the existing
3847                // precision-less LOCALTIME compatibility output for other targets.
3848                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3849                    && !matches!(target, DialectType::Snowflake)
3850                {
3851                    transform_recursive(normalized, &|e| match e {
3852                        Expression::Function(ref f)
3853                            if f.name.eq_ignore_ascii_case("CURRENT_TIME") =>
3854                        {
3855                            let precision = if matches!(target, DialectType::ClickHouse) {
3856                                f.args.first().cloned().map(Box::new)
3857                            } else {
3858                                None
3859                            };
3860                            Ok(Expression::Localtime(Box::new(
3861                                crate::expressions::Localtime { this: precision },
3862                            )))
3863                        }
3864                        Expression::Localtime(ref localtime)
3865                            if !matches!(target, DialectType::ClickHouse)
3866                                && localtime.this.is_some() =>
3867                        {
3868                            Ok(Expression::Localtime(Box::new(
3869                                crate::expressions::Localtime { this: None },
3870                            )))
3871                        }
3872                        _ => Ok(e),
3873                    })?
3874                } else {
3875                    normalized
3876                };
3877
3878                // Snowflake source to DuckDB target: REPEAT(' ', n) -> REPEAT(' ', CAST(n AS BIGINT))
3879                // Snowflake's SPACE(n) is converted to REPEAT(' ', n) by the Snowflake source
3880                // transform. DuckDB requires the count argument to be BIGINT.
3881                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3882                    && matches!(target, DialectType::DuckDB)
3883                {
3884                    transform_recursive(normalized, &|e| {
3885                        if let Expression::Function(ref f) = e {
3886                            if f.name.eq_ignore_ascii_case("REPEAT") && f.args.len() == 2 {
3887                                // Check if first arg is space string literal
3888                                if let Expression::Literal(ref lit) = f.args[0] {
3889                                    if let crate::expressions::Literal::String(ref s) = lit.as_ref()
3890                                    {
3891                                        if s == " " {
3892                                            // Wrap second arg in CAST(... AS BIGINT) if not already
3893                                            if !matches!(f.args[1], Expression::Cast(_)) {
3894                                                let mut new_args = f.args.clone();
3895                                                new_args[1] = Expression::Cast(Box::new(
3896                                                    crate::expressions::Cast {
3897                                                        this: new_args[1].clone(),
3898                                                        to: crate::expressions::DataType::BigInt {
3899                                                            length: None,
3900                                                        },
3901                                                        trailing_comments: Vec::new(),
3902                                                        double_colon_syntax: false,
3903                                                        format: None,
3904                                                        default: None,
3905                                                        inferred_type: None,
3906                                                    },
3907                                                ));
3908                                                return Ok(Expression::Function(Box::new(
3909                                                    crate::expressions::Function {
3910                                                        on_error: None,
3911                                                        qualified_name: Vec::new(),
3912                                                        source_dialect: None,
3913
3914                                                        name: f.name.clone(),
3915                                                        args: new_args,
3916                                                        distinct: f.distinct,
3917                                                        trailing_comments: f
3918                                                            .trailing_comments
3919                                                            .clone(),
3920                                                        use_bracket_syntax: f.use_bracket_syntax,
3921                                                        no_parens: f.no_parens,
3922                                                        quoted: f.quoted,
3923                                                        span: None,
3924                                                        inferred_type: None,
3925                                                    },
3926                                                )));
3927                                            }
3928                                        }
3929                                    }
3930                                }
3931                            }
3932                        }
3933                        Ok(e)
3934                    })?
3935                } else {
3936                    normalized
3937                };
3938
3939                // Propagate struct field names in arrays (for BigQuery source to non-BigQuery target)
3940                // BigQuery->BigQuery should NOT propagate names (BigQuery handles implicit inheritance)
3941                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
3942                    && !matches!(target, DialectType::BigQuery)
3943                {
3944                    crate::transforms::propagate_struct_field_names(normalized)?
3945                } else {
3946                    normalized
3947                };
3948
3949                // Snowflake source to DuckDB target: RANDOM()/RANDOM(seed) -> scaled RANDOM()
3950                // Snowflake RANDOM() returns integer in [-2^63, 2^63-1], DuckDB RANDOM() returns float [0, 1)
3951                // Skip RANDOM inside UNIFORM/NORMAL/ZIPF/RANDSTR generator args since those
3952                // functions handle their generator args differently (as float seeds).
3953                let normalized = if matches!(self.dialect_type, DialectType::Snowflake)
3954                    && matches!(target, DialectType::DuckDB)
3955                {
3956                    fn make_scaled_random() -> Expression {
3957                        let lower =
3958                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3959                                "-9.223372036854776E+18".to_string(),
3960                            )));
3961                        let upper =
3962                            Expression::Literal(Box::new(crate::expressions::Literal::Number(
3963                                "9.223372036854776e+18".to_string(),
3964                            )));
3965                        let random_call = Expression::Random(crate::expressions::Random);
3966                        let range_size = Expression::Paren(Box::new(crate::expressions::Paren {
3967                            this: Expression::Sub(Box::new(crate::expressions::BinaryOp {
3968                                left: upper,
3969                                right: lower.clone(),
3970                                left_comments: vec![],
3971                                operator_comments: vec![],
3972                                trailing_comments: vec![],
3973                                inferred_type: None,
3974                            })),
3975                            trailing_comments: vec![],
3976                        }));
3977                        let scaled = Expression::Mul(Box::new(crate::expressions::BinaryOp {
3978                            left: random_call,
3979                            right: range_size,
3980                            left_comments: vec![],
3981                            operator_comments: vec![],
3982                            trailing_comments: vec![],
3983                            inferred_type: None,
3984                        }));
3985                        let shifted = Expression::Add(Box::new(crate::expressions::BinaryOp {
3986                            left: lower,
3987                            right: scaled,
3988                            left_comments: vec![],
3989                            operator_comments: vec![],
3990                            trailing_comments: vec![],
3991                            inferred_type: None,
3992                        }));
3993                        Expression::Cast(Box::new(crate::expressions::Cast {
3994                            this: shifted,
3995                            to: crate::expressions::DataType::BigInt { length: None },
3996                            trailing_comments: vec![],
3997                            double_colon_syntax: false,
3998                            format: None,
3999                            default: None,
4000                            inferred_type: None,
4001                        }))
4002                    }
4003
4004                    // Pre-process: protect seeded RANDOM(seed) inside UNIFORM/NORMAL/ZIPF/RANDSTR
4005                    // by converting Rand{seed: Some(s)} to Function{name:"RANDOM", args:[s]}.
4006                    // This prevents transform_recursive (which is bottom-up) from expanding
4007                    // seeded RANDOM into make_scaled_random() and losing the seed value.
4008                    // Unseeded RANDOM()/Rand{seed:None} is left as-is so it gets expanded
4009                    // and then un-expanded back to Expression::Random by the code below.
4010                    let normalized = transform_recursive(normalized, &|e| {
4011                        if let Expression::Function(ref f) = e {
4012                            let n = f.name.to_ascii_uppercase();
4013                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" || n == "RANDSTR" {
4014                                if let Expression::Function(mut f) = e {
4015                                    for arg in f.args.iter_mut() {
4016                                        if let Expression::Rand(ref r) = arg {
4017                                            if r.lower.is_none() && r.upper.is_none() {
4018                                                if let Some(ref seed) = r.seed {
4019                                                    // Convert Rand{seed: Some(s)} to Function("RANDOM", [s])
4020                                                    // so it won't be expanded by the RANDOM expansion below
4021                                                    *arg = Expression::Function(Box::new(
4022                                                        crate::expressions::Function::new(
4023                                                            "RANDOM".to_string(),
4024                                                            vec![*seed.clone()],
4025                                                        ),
4026                                                    ));
4027                                                }
4028                                            }
4029                                        }
4030                                    }
4031                                    return Ok(Expression::Function(f));
4032                                }
4033                            }
4034                        }
4035                        Ok(e)
4036                    })?;
4037
4038                    // transform_recursive processes bottom-up, so RANDOM() (unseeded) inside
4039                    // generator functions (UNIFORM, NORMAL, ZIPF) gets expanded before
4040                    // we see the parent. We detect this and undo the expansion by replacing
4041                    // the expanded pattern back with Expression::Random.
4042                    // Seeded RANDOM(seed) was already protected above as Function("RANDOM", [seed]).
4043                    // Note: RANDSTR is NOT included here — it needs the expanded form for unseeded
4044                    // RANDOM() since the DuckDB handler uses the expanded SQL as-is in the hash.
4045                    transform_recursive(normalized, &|e| {
4046                        if let Expression::Function(ref f) = e {
4047                            let n = f.name.to_ascii_uppercase();
4048                            if n == "UNIFORM" || n == "NORMAL" || n == "ZIPF" {
4049                                if let Expression::Function(mut f) = e {
4050                                    for arg in f.args.iter_mut() {
4051                                        // Detect expanded RANDOM pattern: CAST(-9.22... + RANDOM() * (...) AS BIGINT)
4052                                        if let Expression::Cast(ref cast) = arg {
4053                                            if matches!(
4054                                                cast.to,
4055                                                crate::expressions::DataType::BigInt { .. }
4056                                            ) {
4057                                                if let Expression::Add(ref add) = cast.this {
4058                                                    if let Expression::Literal(ref lit) = add.left {
4059                                                        if let crate::expressions::Literal::Number(
4060                                                            ref num,
4061                                                        ) = lit.as_ref()
4062                                                        {
4063                                                            if num == "-9.223372036854776E+18" {
4064                                                                *arg = Expression::Random(
4065                                                                    crate::expressions::Random,
4066                                                                );
4067                                                            }
4068                                                        }
4069                                                    }
4070                                                }
4071                                            }
4072                                        }
4073                                    }
4074                                    return Ok(Expression::Function(f));
4075                                }
4076                                return Ok(e);
4077                            }
4078                        }
4079                        match e {
4080                            Expression::Random(_) => Ok(make_scaled_random()),
4081                            // Rand(seed) with no bounds: drop seed and expand
4082                            // (DuckDB RANDOM doesn't support seeds)
4083                            Expression::Rand(ref r) if r.lower.is_none() && r.upper.is_none() => {
4084                                Ok(make_scaled_random())
4085                            }
4086                            _ => Ok(e),
4087                        }
4088                    })?
4089                } else {
4090                    normalized
4091                };
4092
4093                // Apply cross-dialect semantic normalizations
4094                let normalized = normalization::normalize(
4095                    normalized,
4096                    self.dialect_type,
4097                    target,
4098                    matches!(
4099                        opts.unsupported_level,
4100                        UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4101                    ),
4102                )?;
4103
4104                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4105                    Self::normalize_tsql_fetch_overlaps_date_bin(normalized)?
4106                } else {
4107                    normalized
4108                };
4109
4110                let normalized =
4111                    if matches!(
4112                        self.dialect_type,
4113                        DialectType::PostgreSQL | DialectType::CockroachDB
4114                    ) && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
4115                    {
4116                        Self::normalize_postgres_type_function_casts(normalized, target)?
4117                    } else {
4118                        normalized
4119                    };
4120
4121                let normalized = if matches!(self.dialect_type, DialectType::SQLite)
4122                    && !matches!(target, DialectType::SQLite)
4123                {
4124                    Self::normalize_sqlite_double_quoted_defaults(normalized)?
4125                } else {
4126                    normalized
4127                };
4128
4129                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4130                    && matches!(target, DialectType::SQLite)
4131                {
4132                    Self::normalize_postgres_to_sqlite_types(normalized)?
4133                } else {
4134                    normalized
4135                };
4136
4137                let normalized = if matches!(self.dialect_type, DialectType::PostgreSQL)
4138                    && matches!(target, DialectType::Fabric)
4139                {
4140                    Self::normalize_postgres_to_fabric_types(normalized)?
4141                } else {
4142                    normalized
4143                };
4144
4145                // For DuckDB target from BigQuery source: wrap UNNEST of struct arrays in
4146                // (SELECT UNNEST(..., max_depth => 2)) subquery
4147                // Must run BEFORE unnest_alias_to_column_alias since it changes alias structure
4148                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4149                    && matches!(target, DialectType::DuckDB)
4150                {
4151                    crate::transforms::wrap_duckdb_unnest_struct(normalized)?
4152                } else {
4153                    normalized
4154                };
4155
4156                // Convert BigQuery UNNEST aliases to column-alias format for DuckDB/Presto/Spark
4157                // UNNEST(arr) AS x -> UNNEST(arr) AS _t0(x)
4158                let normalized = if matches!(self.dialect_type, DialectType::BigQuery)
4159                    && matches!(
4160                        target,
4161                        DialectType::DuckDB
4162                            | DialectType::Presto
4163                            | DialectType::Trino
4164                            | DialectType::Athena
4165                            | DialectType::Spark
4166                            | DialectType::Databricks
4167                    ) {
4168                    crate::transforms::unnest_alias_to_column_alias(normalized)?
4169                } else if matches!(self.dialect_type, DialectType::BigQuery)
4170                    && matches!(target, DialectType::BigQuery | DialectType::Redshift)
4171                {
4172                    // For BigQuery/Redshift targets: move UNNEST FROM items to CROSS JOINs
4173                    // but don't convert alias format (no _t0 wrapper)
4174                    let result = crate::transforms::unnest_from_to_cross_join(normalized)?;
4175                    // For Redshift: strip UNNEST when arg is a column reference path
4176                    if matches!(target, DialectType::Redshift) {
4177                        crate::transforms::strip_unnest_column_refs(result)?
4178                    } else {
4179                        result
4180                    }
4181                } else {
4182                    normalized
4183                };
4184
4185                // For Presto/Trino targets from PostgreSQL/Redshift source:
4186                // Wrap UNNEST aliases from GENERATE_SERIES conversion: AS s -> AS _u(s)
4187                let normalized = if matches!(
4188                    self.dialect_type,
4189                    DialectType::PostgreSQL | DialectType::Redshift
4190                ) && matches!(
4191                    target,
4192                    DialectType::Presto | DialectType::Trino | DialectType::Athena
4193                ) {
4194                    crate::transforms::wrap_unnest_join_aliases(normalized)?
4195                } else {
4196                    normalized
4197                };
4198
4199                // Eliminate DISTINCT ON with target-dialect awareness
4200                // This must happen after source transform (which may produce DISTINCT ON)
4201                // and before target transform, with knowledge of the target dialect's NULL ordering behavior
4202                let normalized = crate::transforms::eliminate_distinct_on_for_dialect(
4203                    normalized,
4204                    Some(target),
4205                    Some(self.dialect_type),
4206                )?;
4207
4208                // GENERATE_DATE_ARRAY in UNNEST -> Snowflake ARRAY_GENERATE_RANGE + DATEADD
4209                let normalized = if matches!(target, DialectType::Snowflake) {
4210                    Self::transform_generate_date_array_snowflake(normalized)?
4211                } else {
4212                    normalized
4213                };
4214
4215                // CROSS JOIN UNNEST -> LATERAL VIEW EXPLODE/INLINE for Spark/Hive/Databricks
4216                let normalized = if matches!(
4217                    target,
4218                    DialectType::Spark | DialectType::Databricks | DialectType::Hive
4219                ) {
4220                    crate::transforms::unnest_to_explode_select(normalized)?
4221                } else {
4222                    normalized
4223                };
4224
4225                // Wrap UNION with ORDER BY/LIMIT in a subquery for dialects that require it
4226                let normalized = if matches!(target, DialectType::ClickHouse | DialectType::TSQL) {
4227                    crate::transforms::no_limit_order_by_union(normalized)?
4228                } else {
4229                    normalized
4230                };
4231
4232                let normalized = if matches!(
4233                    self.dialect_type,
4234                    DialectType::PostgreSQL | DialectType::CockroachDB
4235                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4236                {
4237                    Self::normalize_postgres_boolean_semantics_for_tsql(normalized)?
4238                } else {
4239                    normalized
4240                };
4241
4242                let normalized = if self.dialect_type == DialectType::PostgreSQL
4243                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4244                {
4245                    Self::normalize_postgres_bytea_literals_for_tsql(normalized)?
4246                } else {
4247                    normalized
4248                };
4249
4250                let normalized = if matches!(
4251                    self.dialect_type,
4252                    DialectType::PostgreSQL | DialectType::CockroachDB
4253                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4254                {
4255                    Self::normalize_postgres_string_semantics_for_tsql(normalized)?
4256                } else {
4257                    normalized
4258                };
4259
4260                // TSQL: Convert COUNT(*) -> COUNT_BIG(*) when source is not TSQL/Fabric
4261                // Python sqlglot does this in the TSQL generator, but we can't do it there
4262                // because it would break TSQL -> TSQL identity
4263                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4264                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4265                {
4266                    transform_recursive(normalized, &|e| {
4267                        if let Expression::Count(ref c) = e {
4268                            // Build COUNT_BIG(...) as an AggregateFunction
4269                            let args = if c.star {
4270                                vec![Expression::Star(crate::expressions::Star {
4271                                    table: None,
4272                                    except: None,
4273                                    replace: None,
4274                                    rename: None,
4275                                    trailing_comments: Vec::new(),
4276                                    span: None,
4277                                })]
4278                            } else if let Some(ref this) = c.this {
4279                                vec![this.clone()]
4280                            } else {
4281                                vec![]
4282                            };
4283                            Ok(Expression::AggregateFunction(Box::new(
4284                                crate::expressions::AggregateFunction {
4285                                    source_dialect: None,
4286
4287                                    name: "COUNT_BIG".to_string(),
4288                                    args,
4289                                    distinct: c.distinct,
4290                                    filter: c.filter.clone(),
4291                                    order_by: Vec::new(),
4292                                    limit: None,
4293                                    ignore_nulls: None,
4294                                    inferred_type: None,
4295                                },
4296                            )))
4297                        } else {
4298                            Ok(e)
4299                        }
4300                    })?
4301                } else {
4302                    normalized
4303                };
4304
4305                // T-SQL/Fabric do not have a scalar boolean type. Keep predicate
4306                // contexts intact, but materialize boolean-valued expressions used
4307                // as values before target transforms add ORDER BY null sort keys.
4308                let normalized = if matches!(target, DialectType::TSQL | DialectType::Fabric)
4309                    && !matches!(self.dialect_type, DialectType::TSQL | DialectType::Fabric)
4310                {
4311                    let normalized = if self.dialect_type == DialectType::PostgreSQL {
4312                        Self::rewrite_postgres_row_value_equality_for_tsql(normalized)?
4313                    } else {
4314                        normalized
4315                    };
4316                    Self::rewrite_boolean_values_for_tsql(normalized)?
4317                } else {
4318                    normalized
4319                };
4320
4321                let normalized = if matches!(
4322                    self.dialect_type,
4323                    DialectType::PostgreSQL | DialectType::CockroachDB
4324                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4325                {
4326                    Self::rewrite_postgres_format_for_tsql(normalized, target)?
4327                } else {
4328                    normalized
4329                };
4330
4331                let normalized = if self.dialect_type == DialectType::PostgreSQL
4332                    && matches!(target, DialectType::TSQL | DialectType::Fabric)
4333                {
4334                    Self::normalize_postgres_only_for_tsql(normalized)?
4335                } else {
4336                    normalized
4337                };
4338
4339                let normalized = crate::transforms::lower_set_operations_by_name(
4340                    normalized,
4341                    self.dialect_type,
4342                    target,
4343                )?;
4344                let transformed =
4345                    target_dialect.transform_with_guard(normalized, opts.complexity_guard)?;
4346
4347                // T-SQL and Fabric do not support aggregate FILTER clauses. Rewrite any
4348                // remaining filters after target transforms so special aggregate rewrites
4349                // (for example BOOL_OR/BOOL_AND) can consume their filters first.
4350                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4351                    Self::rewrite_aggregate_filters_for_tsql(transformed)?
4352                } else {
4353                    transformed
4354                };
4355
4356                let transformed = if matches!(
4357                    self.dialect_type,
4358                    DialectType::PostgreSQL | DialectType::CockroachDB
4359                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4360                {
4361                    crate::transforms::grouped_percentiles_to_tsql_windows(transformed)?
4362                } else {
4363                    transformed
4364                };
4365
4366                let transformed = if matches!(
4367                    self.dialect_type,
4368                    DialectType::PostgreSQL | DialectType::CockroachDB
4369                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4370                {
4371                    Self::normalize_postgres_trim_for_tsql(transformed)?
4372                } else {
4373                    transformed
4374                };
4375
4376                let transformed = if matches!(
4377                    self.dialect_type,
4378                    DialectType::PostgreSQL | DialectType::CockroachDB
4379                ) && matches!(target, DialectType::TSQL | DialectType::Fabric)
4380                {
4381                    Self::rewrite_postgres_json_array_elements_select_for_tsql(transformed)?
4382                } else {
4383                    transformed
4384                };
4385
4386                // DuckDB target: when FROM is RANGE(n), replace SEQ's ROW_NUMBER pattern with `range`
4387                let transformed = if matches!(target, DialectType::DuckDB) {
4388                    Self::seq_rownum_to_range(transformed)?
4389                } else {
4390                    transformed
4391                };
4392
4393                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4394                    Self::reject_tsql_interval_casts(&transformed, target, opts)?;
4395                }
4396
4397                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4398                    Self::rewrite_tsql_interval_casts_to_varchar(transformed)?
4399                } else {
4400                    transformed
4401                };
4402
4403                let transformed = if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4404                    Self::legalize_tsql_nested_order_by(transformed)?
4405                } else {
4406                    transformed
4407                };
4408
4409                Self::reject_strict_unsupported(&transformed, self.dialect_type, target, opts)?;
4410
4411                let mut sql = target_dialect.generate_with_transpile_options(
4412                    &transformed,
4413                    self.dialect_type,
4414                    opts,
4415                )?;
4416
4417                // Align a known Snowflake pretty-print edge case with Python sqlglot output.
4418                if opts.pretty && target == DialectType::Snowflake {
4419                    sql = Self::normalize_snowflake_pretty(sql);
4420                }
4421
4422                Ok(sql)
4423            })
4424            .collect()
4425    }
4426}
4427
4428// Transpile-only methods: cross-dialect normalization and helpers
4429#[cfg(feature = "transpile")]
4430impl Dialect {
4431    fn legalize_tsql_nested_order_by(expr: Expression) -> Result<Expression> {
4432        let preserve_root_order = matches!(&expr, Expression::Select(select) if Self::tsql_select_needs_order_offset(select));
4433
4434        let mut transformed = transform_recursive(expr, &|node| match node {
4435            Expression::Select(mut select) => {
4436                Self::legalize_tsql_select_offset(&mut select);
4437                if Self::tsql_select_needs_order_offset(&select) {
4438                    select.offset = Some(Offset {
4439                        this: Expression::Literal(Box::new(Literal::Number("0".to_string()))),
4440                        rows: Some(true),
4441                    });
4442                }
4443                Ok(Expression::Select(select))
4444            }
4445            Expression::Subquery(mut subquery) => {
4446                Self::legalize_tsql_offset(&mut subquery.order_by, &mut subquery.offset, false);
4447                Ok(Expression::Subquery(subquery))
4448            }
4449            Expression::Union(mut union) => {
4450                Self::legalize_tsql_set_offset(&mut union.order_by, &mut union.offset);
4451                Ok(Expression::Union(union))
4452            }
4453            Expression::Intersect(mut intersect) => {
4454                Self::legalize_tsql_set_offset(&mut intersect.order_by, &mut intersect.offset);
4455                Ok(Expression::Intersect(intersect))
4456            }
4457            Expression::Except(mut except) => {
4458                Self::legalize_tsql_set_offset(&mut except.order_by, &mut except.offset);
4459                Ok(Expression::Except(except))
4460            }
4461            other => Ok(other),
4462        })?;
4463
4464        if preserve_root_order {
4465            if let Expression::Select(select) = &mut transformed {
4466                select.offset = None;
4467            }
4468        }
4469
4470        Self::drop_tsql_unbounded_nested_set_order_by(transformed)
4471    }
4472
4473    fn drop_tsql_unbounded_nested_set_order_by(mut expr: Expression) -> Result<Expression> {
4474        let root_order_by = Self::take_tsql_root_set_order_by(&mut expr);
4475
4476        let mut transformed = transform_recursive(expr, &|node| match node {
4477            Expression::Union(mut union) => {
4478                if union.limit.is_none() && union.offset.is_none() {
4479                    union.order_by = None;
4480                }
4481                Ok(Expression::Union(union))
4482            }
4483            Expression::Intersect(mut intersect) => {
4484                if intersect.limit.is_none() && intersect.offset.is_none() {
4485                    intersect.order_by = None;
4486                }
4487                Ok(Expression::Intersect(intersect))
4488            }
4489            Expression::Except(mut except) => {
4490                if except.limit.is_none() && except.offset.is_none() {
4491                    except.order_by = None;
4492                }
4493                Ok(Expression::Except(except))
4494            }
4495            other => Ok(other),
4496        })?;
4497
4498        if let Some(order_by) = root_order_by {
4499            Self::restore_tsql_root_set_order_by(&mut transformed, order_by);
4500        }
4501
4502        Ok(transformed)
4503    }
4504
4505    fn take_tsql_root_set_order_by(expr: &mut Expression) -> Option<OrderBy> {
4506        match expr {
4507            Expression::Union(union) => union.order_by.take(),
4508            Expression::Intersect(intersect) => intersect.order_by.take(),
4509            Expression::Except(except) => except.order_by.take(),
4510            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4511                Self::take_tsql_root_set_order_by(&mut subquery.this)
4512            }
4513            Expression::Paren(paren) => Self::take_tsql_root_set_order_by(&mut paren.this),
4514            _ => None,
4515        }
4516    }
4517
4518    fn restore_tsql_root_set_order_by(expr: &mut Expression, order_by: OrderBy) {
4519        match expr {
4520            Expression::Union(union) => union.order_by = Some(order_by),
4521            Expression::Intersect(intersect) => intersect.order_by = Some(order_by),
4522            Expression::Except(except) => except.order_by = Some(order_by),
4523            Expression::Subquery(subquery) if subquery.alias.is_none() => {
4524                Self::restore_tsql_root_set_order_by(&mut subquery.this, order_by);
4525            }
4526            Expression::Paren(paren) => {
4527                Self::restore_tsql_root_set_order_by(&mut paren.this, order_by);
4528            }
4529            _ => {}
4530        }
4531    }
4532
4533    fn legalize_tsql_select_offset(select: &mut crate::expressions::Select) {
4534        let has_fetch = select.fetch.is_some();
4535        Self::legalize_tsql_offset(&mut select.order_by, &mut select.offset, has_fetch);
4536    }
4537
4538    fn legalize_tsql_offset(
4539        order_by: &mut Option<OrderBy>,
4540        offset: &mut Option<Offset>,
4541        retain_inert_offset: bool,
4542    ) {
4543        if order_by.is_some() {
4544            return;
4545        }
4546
4547        if offset
4548            .as_ref()
4549            .is_some_and(|offset| Self::tsql_offset_is_inert(&offset.this))
4550            && !retain_inert_offset
4551        {
4552            *offset = None;
4553        } else if offset.is_some() {
4554            *order_by = Some(Generator::dummy_tsql_order_by());
4555        }
4556    }
4557
4558    fn legalize_tsql_set_offset(
4559        order_by: &mut Option<OrderBy>,
4560        offset: &mut Option<Box<Expression>>,
4561    ) {
4562        if order_by.is_some() {
4563            return;
4564        }
4565
4566        if offset.as_deref().is_some_and(Self::tsql_offset_is_inert) {
4567            *offset = None;
4568        } else if offset.is_some() {
4569            *order_by = Some(Generator::dummy_tsql_order_by());
4570        }
4571    }
4572
4573    fn tsql_offset_is_inert(expr: &Expression) -> bool {
4574        match expr {
4575            Expression::Null(_) => true,
4576            Expression::Literal(literal) => match literal.as_ref() {
4577                Literal::Number(value) => value.parse::<i128>().is_ok_and(|value| value == 0),
4578                _ => false,
4579            },
4580            _ => false,
4581        }
4582    }
4583
4584    fn tsql_select_needs_order_offset(select: &crate::expressions::Select) -> bool {
4585        select.order_by.is_some()
4586            && select.top.is_none()
4587            && select.limit.is_none()
4588            && select.offset.is_none()
4589            && select.fetch.is_none()
4590            && select.for_xml.is_empty()
4591            && select.for_json.is_empty()
4592    }
4593
4594    fn reject_hana_source_semantics(
4595        expression: &Expression,
4596        source: DialectType,
4597        target: DialectType,
4598    ) -> Result<()> {
4599        if source != DialectType::HANA || target == DialectType::HANA {
4600            return Ok(());
4601        }
4602        let mut pending = vec![expression];
4603        while let Some(node) = pending.pop() {
4604            if node.source_dialect() == Some(DialectType::HANA) {
4605                Generator::validate_hana_source_node(node, target)?;
4606            }
4607            // A supported outer call does not establish support for its arguments.
4608            // Types in column definitions, casts, routine signatures, etc. are
4609            // embedded syntax, not Expression children. Validate each type once,
4610            // before normalization can replace its enclosing ARRAY/STRUCT type.
4611            // A scalar native CAST already uses a value-aware conversion check
4612            // (e.g. a HANA TINYINT literal can fit a target signed integer).
4613            let cast_type_validated = matches!(node, Expression::Cast(c)
4614                if matches!(c.to, crate::expressions::DataType::Hana { .. }));
4615            let mut type_result = Ok(());
4616            crate::ast_children::for_each_child_and_type_untracked(
4617                node,
4618                |child| pending.push(child),
4619                |data_type| {
4620                    if !cast_type_validated && type_result.is_ok() {
4621                        type_result = Generator::validate_hana_source_type(data_type, target);
4622                    }
4623                },
4624            );
4625            type_result?;
4626        }
4627        Ok(())
4628    }
4629
4630    fn reject_clickhouse_session_semantics(
4631        expr: &Expression,
4632        source: DialectType,
4633        target: DialectType,
4634        opts: &TranspileOptions,
4635    ) -> Result<()> {
4636        if !matches!(
4637            opts.unsupported_level,
4638            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4639        ) || target != DialectType::ClickHouse
4640            || source == DialectType::ClickHouse
4641        {
4642            return Ok(());
4643        }
4644
4645        const JOIN_USE_NULLS_DIAGNOSTIC: &str = "ClickHouse outer joins require the target setting join_use_nulls = 1 to preserve unmatched-row NULL semantics";
4646        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";
4647
4648        fn is_current_session_time(node: &Expression) -> bool {
4649            match node {
4650                Expression::CurrentDate(_)
4651                | Expression::CurrentTime(_)
4652                | Expression::CurrentTimestamp(_)
4653                | Expression::CurrentTimestampLTZ(_)
4654                | Expression::Localtime(_)
4655                | Expression::Localtimestamp(_)
4656                | Expression::Systimestamp(_) => true,
4657                Expression::Function(function) if !function.quoted => matches!(
4658                    function.name.to_ascii_uppercase().as_str(),
4659                    "CURRENT_DATE"
4660                        | "CURRENT_TIME"
4661                        | "LOCALTIME"
4662                        | "CURRENT_TIMESTAMP"
4663                        | "CURRENT_TIMESTAMP_LTZ"
4664                        | "LOCALTIMESTAMP"
4665                        | "NOW"
4666                        | "GETDATE"
4667                        | "SYSTIMESTAMP"
4668                ),
4669                _ => false,
4670            }
4671        }
4672
4673        fn is_session_week_part(expression: &Expression) -> bool {
4674            let part = match expression {
4675                Expression::Literal(literal) => match literal.as_ref() {
4676                    Literal::String(value) => Some(value.as_str()),
4677                    _ => None,
4678                },
4679                Expression::Identifier(identifier) => Some(identifier.name.as_str()),
4680                Expression::Var(var) => Some(var.this.as_str()),
4681                Expression::Column(column) if column.table.is_none() => {
4682                    Some(column.name.name.as_str())
4683                }
4684                _ => None,
4685            };
4686            part.is_some_and(|part| {
4687                matches!(
4688                    part.to_ascii_uppercase().as_str(),
4689                    "WEEK" | "W" | "WK" | "WEEKOFYEAR" | "WOY" | "WY"
4690                )
4691            })
4692        }
4693
4694        fn is_session_week_trunc(node: &Expression) -> bool {
4695            match node {
4696                Expression::DateTrunc(date_trunc) => {
4697                    matches!(date_trunc.unit, DateTimeField::Week)
4698                }
4699                Expression::Function(function)
4700                    if !function.quoted && function.name.eq_ignore_ascii_case("DATE_TRUNC") =>
4701                {
4702                    function.args.first().is_some_and(is_session_week_part)
4703                }
4704                _ => false,
4705            }
4706        }
4707
4708        fn is_null_extending_clickhouse_join(node: &Expression) -> bool {
4709            fn join_is_null_extending(join: &Join) -> bool {
4710                matches!(
4711                    join.kind,
4712                    JoinKind::Left
4713                        | JoinKind::Right
4714                        | JoinKind::Full
4715                        | JoinKind::NaturalLeft
4716                        | JoinKind::NaturalRight
4717                        | JoinKind::NaturalFull
4718                        | JoinKind::AsOfLeft
4719                        | JoinKind::AsOfRight
4720                )
4721            }
4722
4723            fn joins_are_null_extending(joins: &[Join]) -> bool {
4724                joins.iter().any(join_is_null_extending)
4725            }
4726
4727            match node {
4728                Expression::Join(join) => join_is_null_extending(join),
4729                Expression::Select(select) => joins_are_null_extending(&select.joins),
4730                Expression::JoinedTable(joined) => joins_are_null_extending(&joined.joins),
4731                Expression::Update(update) => {
4732                    joins_are_null_extending(&update.table_joins)
4733                        || joins_are_null_extending(&update.from_joins)
4734                }
4735                Expression::Delete(delete) => joins_are_null_extending(&delete.joins),
4736                _ => false,
4737            }
4738        }
4739
4740        fn is_count_like_aggregate(node: &Expression) -> bool {
4741            match node {
4742                Expression::Count(_)
4743                | Expression::CountIf(_)
4744                | Expression::ApproxDistinct(_)
4745                | Expression::ApproxCountDistinct(_) => true,
4746                Expression::AggregateFunction(function) => matches!(
4747                    function.name.to_ascii_uppercase().as_str(),
4748                    "COUNT" | "COUNT_IF" | "COUNTIF" | "APPROX_DISTINCT" | "APPROX_COUNT_DISTINCT"
4749                ),
4750                _ => false,
4751            }
4752        }
4753
4754        fn is_empty_input_sensitive_aggregate(node: &Expression, source: DialectType) -> bool {
4755            Dialect::node_is_aggregate_function(node)
4756                && !is_count_like_aggregate(node)
4757                && !matches!(node, Expression::BitwiseCount(_))
4758                && !matches!(node, Expression::Median(_) if source == DialectType::Snowflake)
4759                && !matches!(
4760                    node,
4761                    Expression::AggregateFunction(function)
4762                        if function.name.to_ascii_lowercase().ends_with("ornull")
4763                )
4764        }
4765
4766        fn setting_is_enabled(setting: &Expression, name: &str) -> bool {
4767            let Expression::Eq(equality) = setting else {
4768                return false;
4769            };
4770            let setting_name = match &equality.left {
4771                Expression::Identifier(identifier) => identifier.name.as_str(),
4772                Expression::Column(column) if column.table.is_none() => column.name.name.as_str(),
4773                Expression::Var(var) => var.this.as_str(),
4774                _ => return false,
4775            };
4776            let enabled = match &equality.right {
4777                Expression::Literal(literal) => {
4778                    matches!(literal.as_ref(), Literal::Number(value) if value.parse::<i128>().is_ok_and(|value| value == 1))
4779                }
4780                Expression::Boolean(boolean) => boolean.value,
4781                _ => false,
4782            };
4783            setting_name.eq_ignore_ascii_case(name) && enabled
4784        }
4785
4786        fn query_has_enabled_setting(expr: &Expression, name: &str) -> bool {
4787            expr.dfs().any(|node| {
4788                matches!(
4789                    node,
4790                    Expression::Select(select)
4791                        if select.settings.as_ref().is_some_and(|settings| {
4792                            settings.iter().any(|setting| setting_is_enabled(setting, name))
4793                        })
4794                )
4795            })
4796        }
4797
4798        let mut diagnostics = Vec::new();
4799        let join_use_nulls_enabled = query_has_enabled_setting(expr, "join_use_nulls");
4800        let aggregate_null_for_empty_enabled =
4801            query_has_enabled_setting(expr, "aggregate_functions_null_for_empty");
4802        for node in expr.dfs() {
4803            if source == DialectType::Snowflake && is_current_session_time(node) {
4804                Self::push_unsupported_diagnostic(
4805                    &mut diagnostics,
4806                    "Snowflake current date/time expressions depend on the session TIMEZONE, which cannot be preserved for ClickHouse",
4807                );
4808            }
4809            if source == DialectType::Snowflake && is_session_week_trunc(node) {
4810                Self::push_unsupported_diagnostic(
4811                    &mut diagnostics,
4812                    "Snowflake DATE_TRUNC with a week unit depends on the session WEEK_START, which cannot be preserved for ClickHouse",
4813                );
4814            }
4815            if !join_use_nulls_enabled && is_null_extending_clickhouse_join(node) {
4816                Self::push_unsupported_diagnostic(&mut diagnostics, JOIN_USE_NULLS_DIAGNOSTIC);
4817            }
4818            if !aggregate_null_for_empty_enabled && is_empty_input_sensitive_aggregate(node, source)
4819            {
4820                Self::push_unsupported_diagnostic(
4821                    &mut diagnostics,
4822                    AGGREGATE_NULL_FOR_EMPTY_DIAGNOSTIC,
4823                );
4824            }
4825            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
4826                break;
4827            }
4828        }
4829
4830        if diagnostics.is_empty() {
4831            return Ok(());
4832        }
4833
4834        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
4835            1
4836        } else {
4837            opts.max_unsupported.max(1)
4838        };
4839        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
4840        if diagnostics.len() > limit {
4841            messages.push(format!("... and {} more", diagnostics.len() - limit));
4842        }
4843
4844        Err(crate::error::Error::unsupported(
4845            messages.join("; "),
4846            target.to_string(),
4847        ))
4848    }
4849
4850    fn reject_strict_unsupported(
4851        expr: &Expression,
4852        source: DialectType,
4853        target: DialectType,
4854        opts: &TranspileOptions,
4855    ) -> Result<()> {
4856        if !matches!(
4857            opts.unsupported_level,
4858            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
4859        ) {
4860            return Ok(());
4861        }
4862
4863        let mut diagnostics = Vec::new();
4864        if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4865            && matches!(target, DialectType::TSQL | DialectType::Fabric)
4866            && Self::tsql_apply_has_invalid_outer_aggregate(expr)
4867        {
4868            Self::push_unsupported_diagnostic(
4869                &mut diagnostics,
4870                "APPLY aggregate expressions that combine an outer reference with another column reference",
4871            );
4872        }
4873        let structural_grouping_tuples =
4874            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
4875                && matches!(target, DialectType::TSQL | DialectType::Fabric)
4876            {
4877                Self::collect_tsql_grouping_tuple_nodes(expr)
4878            } else {
4879                HashSet::new()
4880            };
4881
4882        for node in expr.dfs() {
4883            if source == DialectType::Snowflake && target == DialectType::ClickHouse {
4884                if Self::node_is_function_named(node, "TO_CHAR") {
4885                    Self::push_unsupported_diagnostic(
4886                        &mut diagnostics,
4887                        "Snowflake TO_CHAR overload or dynamic format",
4888                    );
4889                }
4890                if Self::node_is_function_named(node, "TRY_TO_DOUBLE")
4891                    || matches!(node, Expression::ToDouble(to_double) if to_double.safe.is_some() && to_double.format.is_some())
4892                {
4893                    Self::push_unsupported_diagnostic(
4894                        &mut diagnostics,
4895                        "Snowflake TRY_TO_DOUBLE with a format model",
4896                    );
4897                }
4898                if Self::node_is_function_named(node, "TRY_TO_NUMBER")
4899                    || Self::node_is_function_named(node, "TRY_TO_NUMERIC")
4900                    || Self::node_is_function_named(node, "TRY_TO_DECIMAL")
4901                    || matches!(node, Expression::ToNumber(to_number) if to_number.safe.is_some())
4902                {
4903                    Self::push_unsupported_diagnostic(
4904                        &mut diagnostics,
4905                        "Snowflake TRY_TO_NUMBER decimal conversion semantics",
4906                    );
4907                }
4908                if Self::node_is_function_named(node, "FLATTEN") {
4909                    Self::push_unsupported_diagnostic(
4910                        &mut diagnostics,
4911                        "Snowflake FLATTEN table-function semantics",
4912                    );
4913                }
4914            }
4915
4916            if matches!(target, DialectType::Fabric | DialectType::Hive)
4917                && Self::node_has_recursive_with(node)
4918            {
4919                Self::push_unsupported_diagnostic(&mut diagnostics, "recursive CTEs");
4920            }
4921
4922            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4923                && Self::node_has_lateral(node)
4924            {
4925                Self::push_unsupported_diagnostic(&mut diagnostics, "LATERAL joins and subqueries");
4926            }
4927
4928            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4929                if Self::node_has_join_using(node) {
4930                    Self::push_unsupported_diagnostic(&mut diagnostics, "JOIN USING clauses");
4931                }
4932                if Self::node_has_natural_join(node) {
4933                    Self::push_unsupported_diagnostic(&mut diagnostics, "NATURAL JOIN");
4934                }
4935                if Self::node_has_unsupported_relation_column_aliases(node) {
4936                    Self::push_unsupported_diagnostic(
4937                        &mut diagnostics,
4938                        "column alias lists on base or joined table references",
4939                    );
4940                }
4941                if Self::node_has_qualified_whole_row_aggregate_argument(node) {
4942                    Self::push_unsupported_diagnostic(
4943                        &mut diagnostics,
4944                        "qualified whole-row aggregate arguments",
4945                    );
4946                }
4947                if Self::node_has_subquery_in_aggregate_argument(node) {
4948                    Self::push_unsupported_diagnostic(
4949                        &mut diagnostics,
4950                        "aggregate arguments containing subqueries",
4951                    );
4952                }
4953            }
4954
4955            if !Self::target_supports_distinct_on(target) && Self::node_has_distinct_on(node) {
4956                Self::push_unsupported_diagnostic(&mut diagnostics, "DISTINCT ON");
4957            }
4958
4959            if !Self::target_supports_remaining_unnest(target) && Self::node_is_unnest(node) {
4960                Self::push_unsupported_diagnostic(&mut diagnostics, "UNNEST");
4961            }
4962
4963            if !Self::target_supports_remaining_explode(target)
4964                && Self::node_is_explode(node)
4965                && !(source == DialectType::Vertica && target == DialectType::Vertica)
4966            {
4967                Self::push_unsupported_diagnostic(&mut diagnostics, "EXPLODE");
4968            }
4969
4970            if Self::target_lacks_array_agg(target) && Self::node_is_array_agg(node) {
4971                Self::push_unsupported_diagnostic(&mut diagnostics, "ARRAY_AGG");
4972            }
4973
4974            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4975                && Self::node_is_distinct_string_agg(node)
4976            {
4977                Self::push_unsupported_diagnostic(&mut diagnostics, "STRING_AGG with DISTINCT");
4978            }
4979
4980            if matches!(target, DialectType::TSQL | DialectType::Fabric)
4981                && matches!(node, Expression::NthValue(_))
4982            {
4983                Self::push_unsupported_diagnostic(&mut diagnostics, "NTH_VALUE");
4984            }
4985
4986            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
4987                if let Some(frame) = Self::node_window_frame(node) {
4988                    if matches!(frame.kind, WindowFrameKind::Groups) {
4989                        Self::push_unsupported_diagnostic(&mut diagnostics, "GROUPS window frames");
4990                    }
4991                    if matches!(frame.kind, WindowFrameKind::Range)
4992                        && (Self::window_frame_bound_has_value_offset(&frame.start)
4993                            || frame
4994                                .end
4995                                .as_ref()
4996                                .is_some_and(Self::window_frame_bound_has_value_offset))
4997                    {
4998                        Self::push_unsupported_diagnostic(
4999                            &mut diagnostics,
5000                            "value-offset RANGE window frames",
5001                        );
5002                    }
5003                    if frame.exclude.is_some() {
5004                        Self::push_unsupported_diagnostic(
5005                            &mut diagnostics,
5006                            "window frame EXCLUDE clauses",
5007                        );
5008                    }
5009                }
5010            }
5011
5012            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5013                && Self::node_is_regex_predicate(node)
5014            {
5015                Self::push_unsupported_diagnostic(
5016                    &mut diagnostics,
5017                    "regular expression predicates",
5018                );
5019            }
5020
5021            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5022                && Self::node_is_non_subquery_any(node)
5023            {
5024                Self::push_unsupported_diagnostic(
5025                    &mut diagnostics,
5026                    "ANY over non-subquery expressions",
5027                );
5028            }
5029
5030            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5031                && Self::node_is_row_value_subquery_comparison(node)
5032            {
5033                Self::push_unsupported_diagnostic(
5034                    &mut diagnostics,
5035                    "row-value subquery comparisons",
5036                );
5037            }
5038
5039            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5040                && Self::node_is_row_value_values_membership(node)
5041            {
5042                Self::push_unsupported_diagnostic(
5043                    &mut diagnostics,
5044                    "row-value VALUES membership comparisons",
5045                );
5046            }
5047
5048            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5049                && Self::node_has_fetch_with_ties(node)
5050            {
5051                Self::push_unsupported_diagnostic(&mut diagnostics, "FETCH WITH TIES without TOP");
5052            }
5053
5054            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5055                && Self::node_is_overlaps(node)
5056            {
5057                Self::push_unsupported_diagnostic(&mut diagnostics, "OVERLAPS");
5058            }
5059
5060            if matches!(target, DialectType::TSQL | DialectType::Fabric)
5061                && Self::node_is_date_bin(node)
5062            {
5063                Self::push_unsupported_diagnostic(&mut diagnostics, "DATE_BIN");
5064            }
5065
5066            if source == DialectType::PostgreSQL
5067                && matches!(target, DialectType::TSQL | DialectType::Fabric)
5068                && Self::node_is_unresolved_postgres_date_subtraction(node)
5069            {
5070                Self::push_unsupported_diagnostic(
5071                    &mut diagnostics,
5072                    "PostgreSQL date subtraction with an unresolved column type",
5073                );
5074            }
5075
5076            if matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5077                && !matches!(target, DialectType::PostgreSQL | DialectType::CockroachDB)
5078            {
5079                if Self::node_is_postgres_json_build_object(node)
5080                    && !(matches!(target, DialectType::TSQL | DialectType::Fabric)
5081                        && Self::postgres_json_build_object_can_lower_to_json_object(node))
5082                {
5083                    Self::push_unsupported_diagnostic(
5084                        &mut diagnostics,
5085                        "PostgreSQL JSON_BUILD_OBJECT",
5086                    );
5087                }
5088                if Self::node_is_function_named(node, "TO_TSVECTOR") {
5089                    Self::push_unsupported_diagnostic(&mut diagnostics, "PostgreSQL TO_TSVECTOR");
5090                }
5091                if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5092                    if let Some(composite_semantics) =
5093                        Self::postgres_tsql_unsupported_composite_semantics(
5094                            node,
5095                            structural_grouping_tuples.contains(&(node as *const Expression)),
5096                        )
5097                    {
5098                        Self::push_unsupported_diagnostic(
5099                            &mut diagnostics,
5100                            &format!("PostgreSQL {composite_semantics}"),
5101                        );
5102                    }
5103                    if Self::node_is_postgres_unknown_cast(node) {
5104                        Self::push_unsupported_diagnostic(
5105                            &mut diagnostics,
5106                            "PostgreSQL unresolved UNKNOWN casts",
5107                        );
5108                    }
5109                    if let Some(collation_name) =
5110                        Self::postgres_tsql_unsupported_collation_name(node)
5111                    {
5112                        Self::push_unsupported_diagnostic(
5113                            &mut diagnostics,
5114                            &format!("PostgreSQL collation \"{collation_name}\""),
5115                        );
5116                    }
5117                    if let Some(array_semantics) =
5118                        Self::postgres_tsql_unsupported_array_semantics(node)
5119                    {
5120                        Self::push_unsupported_diagnostic(
5121                            &mut diagnostics,
5122                            &format!("PostgreSQL {array_semantics}"),
5123                        );
5124                    }
5125                    if let Some(string_semantics) =
5126                        Self::postgres_tsql_unsupported_string_semantics(node)
5127                    {
5128                        Self::push_unsupported_diagnostic(
5129                            &mut diagnostics,
5130                            &format!("PostgreSQL {string_semantics}"),
5131                        );
5132                    }
5133                    if source == DialectType::PostgreSQL {
5134                        if let Some(binary_semantics) =
5135                            Self::postgres_tsql_unsupported_binary_semantics(node)
5136                        {
5137                            Self::push_unsupported_diagnostic(
5138                                &mut diagnostics,
5139                                &format!("PostgreSQL {binary_semantics}"),
5140                            );
5141                        }
5142                    }
5143                    if let Some(function_name) =
5144                        Self::postgres_tsql_unsupported_function_name(node, target)
5145                    {
5146                        Self::push_unsupported_diagnostic(
5147                            &mut diagnostics,
5148                            &format!("PostgreSQL {function_name}"),
5149                        );
5150                    }
5151                }
5152                if matches!(target, DialectType::TSQL | DialectType::Fabric)
5153                    && Self::node_is_postgres_type_function_cast(node)
5154                {
5155                    Self::push_unsupported_diagnostic(
5156                        &mut diagnostics,
5157                        "PostgreSQL type-name function casts",
5158                    );
5159                }
5160            }
5161
5162            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5163                break;
5164            }
5165        }
5166
5167        if matches!(target, DialectType::TSQL | DialectType::Fabric) {
5168            Self::collect_tsql_unsupported_ordered_sets(expr, &mut diagnostics);
5169            Self::collect_tsql_windows_missing_order(expr, &HashMap::new(), &mut diagnostics);
5170        }
5171
5172        if diagnostics.is_empty() {
5173            return Ok(());
5174        }
5175
5176        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5177            1
5178        } else {
5179            opts.max_unsupported.max(1)
5180        };
5181        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5182        if diagnostics.len() > limit {
5183            messages.push(format!("... and {} more", diagnostics.len() - limit));
5184        }
5185
5186        Err(crate::error::Error::unsupported(
5187            messages.join("; "),
5188            target.to_string(),
5189        ))
5190    }
5191
5192    fn reject_postgres_tsql_strict_regex_predicates(
5193        expr: &Expression,
5194        source: DialectType,
5195        target: DialectType,
5196        opts: &TranspileOptions,
5197    ) -> Result<()> {
5198        if !matches!(
5199            opts.unsupported_level,
5200            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5201        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5202            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5203        {
5204            return Ok(());
5205        }
5206
5207        if expr.dfs().any(Self::node_is_regex_predicate) {
5208            return Err(crate::error::Error::unsupported(
5209                "regular expression predicates",
5210                target.to_string(),
5211            ));
5212        }
5213
5214        Ok(())
5215    }
5216
5217    fn reject_tsql_strict_json_constructor_return_types(
5218        expr: &Expression,
5219        source: DialectType,
5220        target: DialectType,
5221        opts: &TranspileOptions,
5222    ) -> Result<()> {
5223        if !matches!(
5224            opts.unsupported_level,
5225            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5226        ) || source == target
5227            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5228        {
5229            return Ok(());
5230        }
5231
5232        let mut diagnostics = Vec::new();
5233        for node in expr.dfs() {
5234            if let Some(return_type) =
5235                normalization::unsupported_tsql_json_constructor_return_type(node)
5236            {
5237                let message =
5238                    format!("SQL/JSON constructor RETURNING {return_type} cannot be preserved");
5239                Self::push_unsupported_diagnostic(&mut diagnostics, &message);
5240                if opts.unsupported_level == UnsupportedLevel::Immediate {
5241                    break;
5242                }
5243            }
5244        }
5245
5246        if diagnostics.is_empty() {
5247            return Ok(());
5248        }
5249
5250        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5251            1
5252        } else {
5253            opts.max_unsupported.max(1)
5254        };
5255        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5256        if diagnostics.len() > limit {
5257            messages.push(format!("... and {} more", diagnostics.len() - limit));
5258        }
5259
5260        Err(crate::error::Error::unsupported(
5261            messages.join("; "),
5262            target.to_string(),
5263        ))
5264    }
5265
5266    fn reject_postgres_tsql_strict_json_aggregate_modifiers(
5267        expr: &Expression,
5268        source: DialectType,
5269        target: DialectType,
5270        opts: &TranspileOptions,
5271    ) -> Result<()> {
5272        if !matches!(
5273            opts.unsupported_level,
5274            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
5275        ) || !matches!(source, DialectType::PostgreSQL | DialectType::CockroachDB)
5276            || !matches!(target, DialectType::TSQL | DialectType::Fabric)
5277        {
5278            return Ok(());
5279        }
5280
5281        let mut diagnostics = Vec::new();
5282        for node in expr.dfs() {
5283            match node {
5284                Expression::Function(function)
5285                    if !function.quoted
5286                        && matches!(
5287                            function.name.to_ascii_uppercase().as_str(),
5288                            "JSON_AGG" | "JSONB_AGG"
5289                        ) =>
5290                {
5291                    let name = function.name.to_ascii_uppercase();
5292                    if function.args.len() != 1 {
5293                        Self::push_unsupported_diagnostic(
5294                            &mut diagnostics,
5295                            &format!("PostgreSQL {name} with invalid argument count"),
5296                        );
5297                    }
5298                    if function.distinct {
5299                        Self::push_unsupported_diagnostic(
5300                            &mut diagnostics,
5301                            &format!("PostgreSQL {name} with DISTINCT"),
5302                        );
5303                    }
5304                }
5305                Expression::AggregateFunction(function)
5306                    if matches!(
5307                        function.name.to_ascii_uppercase().as_str(),
5308                        "JSON_AGG" | "JSONB_AGG"
5309                    ) =>
5310                {
5311                    let name = function.name.to_ascii_uppercase();
5312                    if function.args.len() != 1 {
5313                        Self::push_unsupported_diagnostic(
5314                            &mut diagnostics,
5315                            &format!("PostgreSQL {name} with invalid argument count"),
5316                        );
5317                    }
5318                    if function.distinct {
5319                        Self::push_unsupported_diagnostic(
5320                            &mut diagnostics,
5321                            &format!("PostgreSQL {name} with DISTINCT"),
5322                        );
5323                    }
5324                    if function.filter.is_some() {
5325                        Self::push_unsupported_diagnostic(
5326                            &mut diagnostics,
5327                            &format!("PostgreSQL {name} with FILTER"),
5328                        );
5329                    }
5330                    if function.limit.is_some() || function.ignore_nulls.is_some() {
5331                        Self::push_unsupported_diagnostic(
5332                            &mut diagnostics,
5333                            &format!("PostgreSQL {name} with unsupported aggregate modifiers"),
5334                        );
5335                    }
5336                }
5337                Expression::Filter(filter) => {
5338                    if let Some(name) = Self::postgres_json_aggregate_name(&filter.this) {
5339                        Self::push_unsupported_diagnostic(
5340                            &mut diagnostics,
5341                            &format!("PostgreSQL {name} with FILTER"),
5342                        );
5343                    }
5344                }
5345                _ => {}
5346            }
5347
5348            if opts.unsupported_level == UnsupportedLevel::Immediate && !diagnostics.is_empty() {
5349                break;
5350            }
5351        }
5352
5353        if diagnostics.is_empty() {
5354            return Ok(());
5355        }
5356
5357        let limit = if opts.unsupported_level == UnsupportedLevel::Immediate {
5358            1
5359        } else {
5360            opts.max_unsupported.max(1)
5361        };
5362        let mut messages = diagnostics.iter().take(limit).cloned().collect::<Vec<_>>();
5363        if diagnostics.len() > limit {
5364            messages.push(format!("... and {} more", diagnostics.len() - limit));
5365        }
5366
5367        Err(crate::error::Error::unsupported(
5368            messages.join("; "),
5369            target.to_string(),
5370        ))
5371    }
5372
5373    fn postgres_json_aggregate_name(expr: &Expression) -> Option<String> {
5374        let name = match expr {
5375            Expression::Function(function) if !function.quoted => &function.name,
5376            Expression::AggregateFunction(function) => &function.name,
5377            _ => return None,
5378        };
5379        let name = name.to_ascii_uppercase();
5380        matches!(name.as_str(), "JSON_AGG" | "JSONB_AGG").then_some(name)
5381    }
5382
5383    fn push_unsupported_diagnostic(diagnostics: &mut Vec<String>, message: &str) {
5384        if !diagnostics.iter().any(|existing| existing == message) {
5385            diagnostics.push(message.to_string());
5386        }
5387    }
5388
5389    fn node_is_unresolved_postgres_date_subtraction(expr: &Expression) -> bool {
5390        let Expression::Sub(op) = expr else {
5391            return false;
5392        };
5393
5394        (Self::is_explicit_date_expr(&op.left) && Self::is_column_expr(&op.right))
5395            || (Self::is_column_expr(&op.left) && Self::is_explicit_date_expr(&op.right))
5396    }
5397
5398    fn is_column_expr(expr: &Expression) -> bool {
5399        match expr {
5400            Expression::Column(_) => true,
5401            Expression::Paren(paren) => Self::is_column_expr(&paren.this),
5402            _ => false,
5403        }
5404    }
5405
5406    fn node_window_frame(expr: &Expression) -> Option<&WindowFrame> {
5407        match expr {
5408            Expression::WindowFunction(window) => window.over.frame.as_ref(),
5409            Expression::Window(window) | Expression::WindowSpec(window) => window.frame.as_ref(),
5410            _ => None,
5411        }
5412    }
5413
5414    fn window_frame_bound_has_value_offset(bound: &WindowFrameBound) -> bool {
5415        matches!(
5416            bound,
5417            WindowFrameBound::Preceding(_)
5418                | WindowFrameBound::Following(_)
5419                | WindowFrameBound::Value(_)
5420                | WindowFrameBound::BarePreceding
5421                | WindowFrameBound::BareFollowing
5422        )
5423    }
5424
5425    fn collect_tsql_windows_missing_order(
5426        expr: &Expression,
5427        active_windows: &HashMap<String, Over>,
5428        diagnostics: &mut Vec<String>,
5429    ) {
5430        if let Expression::Select(select) = expr {
5431            let local_windows = select
5432                .windows
5433                .as_ref()
5434                .map(|windows| {
5435                    windows
5436                        .iter()
5437                        .map(|window| (window.name.name.to_ascii_lowercase(), window.spec.clone()))
5438                        .collect()
5439                })
5440                .unwrap_or_default();
5441
5442            for child in expr.children() {
5443                Self::collect_tsql_windows_missing_order(child, &local_windows, diagnostics);
5444            }
5445            return;
5446        }
5447
5448        if let Expression::WindowFunction(window) = expr {
5449            let (has_order, has_frame) = Self::effective_window_order_and_frame(
5450                &window.over,
5451                active_windows,
5452                &mut Vec::new(),
5453            );
5454
5455            if !has_order {
5456                if has_frame {
5457                    Self::push_unsupported_diagnostic(
5458                        diagnostics,
5459                        "window frames without ORDER BY",
5460                    );
5461                }
5462                if let Some(function_name) =
5463                    Self::tsql_window_function_requiring_order(&window.this)
5464                {
5465                    Self::push_unsupported_diagnostic(
5466                        diagnostics,
5467                        &format!("{function_name} without ORDER BY"),
5468                    );
5469                }
5470            }
5471        }
5472
5473        for child in expr.children() {
5474            Self::collect_tsql_windows_missing_order(child, active_windows, diagnostics);
5475        }
5476    }
5477
5478    fn effective_window_order_and_frame(
5479        over: &Over,
5480        active_windows: &HashMap<String, Over>,
5481        seen: &mut Vec<String>,
5482    ) -> (bool, bool) {
5483        let inherited = over
5484            .window_name
5485            .as_ref()
5486            .and_then(|name| {
5487                let key = name.name.to_ascii_lowercase();
5488                if seen.iter().any(|seen_name| seen_name == &key) {
5489                    return None;
5490                }
5491                let named = active_windows.get(&key)?;
5492                seen.push(key);
5493                let properties =
5494                    Self::effective_window_order_and_frame(named, active_windows, seen);
5495                seen.pop();
5496                Some(properties)
5497            })
5498            .unwrap_or((false, false));
5499
5500        (
5501            !over.order_by.is_empty() || inherited.0,
5502            over.frame.is_some() || inherited.1,
5503        )
5504    }
5505
5506    fn tsql_window_function_requiring_order(expr: &Expression) -> Option<&'static str> {
5507        match expr {
5508            Expression::FirstValue(_) => Some("FIRST_VALUE"),
5509            Expression::LastValue(_) => Some("LAST_VALUE"),
5510            Expression::Function(function) if function.name.eq_ignore_ascii_case("FIRST_VALUE") => {
5511                Some("FIRST_VALUE")
5512            }
5513            Expression::Function(function) if function.name.eq_ignore_ascii_case("LAST_VALUE") => {
5514                Some("LAST_VALUE")
5515            }
5516            _ => None,
5517        }
5518    }
5519
5520    fn collect_tsql_unsupported_ordered_sets(expr: &Expression, diagnostics: &mut Vec<String>) {
5521        match expr {
5522            Expression::WindowFunction(window) => {
5523                if let Expression::WithinGroup(within_group) = &window.this {
5524                    if Self::within_group_is_hypothetical_set(within_group) {
5525                        Self::push_unsupported_diagnostic(
5526                            diagnostics,
5527                            "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5528                        );
5529                        return;
5530                    }
5531
5532                    if Self::within_group_is_mode(within_group) {
5533                        Self::push_unsupported_diagnostic(
5534                            diagnostics,
5535                            "MODE ordered-set aggregates",
5536                        );
5537                        return;
5538                    }
5539
5540                    if Self::within_group_is_percentile(within_group) {
5541                        if !window.over.order_by.is_empty() || window.over.frame.is_some() {
5542                            Self::push_unsupported_diagnostic(
5543                                diagnostics,
5544                                "PERCENTILE_CONT/PERCENTILE_DISC window ORDER BY or frame clauses",
5545                            );
5546                        }
5547                        return;
5548                    }
5549                }
5550            }
5551            Expression::WithinGroup(within_group) => {
5552                if Self::within_group_is_hypothetical_set(within_group) {
5553                    Self::push_unsupported_diagnostic(
5554                        diagnostics,
5555                        "RANK/DENSE_RANK/CUME_DIST/PERCENT_RANK hypothetical-set aggregates",
5556                    );
5557                    return;
5558                }
5559
5560                if Self::within_group_is_mode(within_group) {
5561                    Self::push_unsupported_diagnostic(diagnostics, "MODE ordered-set aggregates");
5562                    return;
5563                }
5564
5565                if Self::within_group_is_percentile(within_group) {
5566                    Self::push_unsupported_diagnostic(
5567                        diagnostics,
5568                        "PERCENTILE_CONT/PERCENTILE_DISC ordered-set aggregates without OVER",
5569                    );
5570                    return;
5571                }
5572            }
5573            _ => {}
5574        }
5575
5576        for child in expr.children() {
5577            Self::collect_tsql_unsupported_ordered_sets(child, diagnostics);
5578        }
5579    }
5580
5581    fn within_group_is_hypothetical_set(within_group: &crate::expressions::WithinGroup) -> bool {
5582        match &within_group.this {
5583            Expression::Function(function) => Self::is_hypothetical_set_name(&function.name),
5584            Expression::AggregateFunction(function) => {
5585                Self::is_hypothetical_set_name(&function.name)
5586            }
5587            Expression::Rank(_)
5588            | Expression::DenseRank(_)
5589            | Expression::CumeDist(_)
5590            | Expression::PercentRank(_) => true,
5591            _ => false,
5592        }
5593    }
5594
5595    fn within_group_is_percentile(within_group: &crate::expressions::WithinGroup) -> bool {
5596        match &within_group.this {
5597            Expression::Function(function) => Self::is_percentile_ordered_set_name(&function.name),
5598            Expression::AggregateFunction(function) => {
5599                Self::is_percentile_ordered_set_name(&function.name)
5600            }
5601            Expression::PercentileCont(_) | Expression::PercentileDisc(_) => true,
5602            _ => false,
5603        }
5604    }
5605
5606    fn within_group_is_mode(within_group: &crate::expressions::WithinGroup) -> bool {
5607        match &within_group.this {
5608            Expression::Function(function) => function.name.eq_ignore_ascii_case("MODE"),
5609            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case("MODE"),
5610            Expression::Mode(_) => true,
5611            _ => false,
5612        }
5613    }
5614
5615    fn is_percentile_ordered_set_name(name: &str) -> bool {
5616        name.eq_ignore_ascii_case("PERCENTILE_CONT") || name.eq_ignore_ascii_case("PERCENTILE_DISC")
5617    }
5618
5619    fn is_hypothetical_set_name(name: &str) -> bool {
5620        name.eq_ignore_ascii_case("RANK")
5621            || name.eq_ignore_ascii_case("DENSE_RANK")
5622            || name.eq_ignore_ascii_case("CUME_DIST")
5623            || name.eq_ignore_ascii_case("PERCENT_RANK")
5624    }
5625
5626    fn target_supports_distinct_on(target: DialectType) -> bool {
5627        matches!(target, DialectType::PostgreSQL | DialectType::DuckDB)
5628    }
5629
5630    fn node_has_distinct_on(expr: &Expression) -> bool {
5631        matches!(
5632            expr,
5633            Expression::Select(select)
5634                if select
5635                    .distinct_on
5636                    .as_ref()
5637                    .is_some_and(|distinct_on| !distinct_on.is_empty())
5638        )
5639    }
5640
5641    fn node_has_recursive_with(expr: &Expression) -> bool {
5642        fn recursive(with: &Option<With>) -> bool {
5643            with.as_ref().is_some_and(|with| with.recursive)
5644        }
5645
5646        match expr {
5647            Expression::With(with) => with.recursive,
5648            Expression::Select(select) => recursive(&select.with),
5649            Expression::Union(union) => recursive(&union.with),
5650            Expression::Intersect(intersect) => recursive(&intersect.with),
5651            Expression::Except(except) => recursive(&except.with),
5652            Expression::Pivot(pivot) => recursive(&pivot.with),
5653            Expression::Insert(insert) => recursive(&insert.with),
5654            Expression::Update(update) => recursive(&update.with),
5655            Expression::Delete(delete) => recursive(&delete.with),
5656            _ => false,
5657        }
5658    }
5659
5660    fn node_has_lateral(expr: &Expression) -> bool {
5661        fn join_has_lateral(join: &Join) -> bool {
5662            matches!(
5663                join.kind,
5664                crate::expressions::JoinKind::Lateral | crate::expressions::JoinKind::LeftLateral
5665            ) || Dialect::node_has_lateral(&join.this)
5666                || join.on.as_ref().is_some_and(Dialect::node_has_lateral)
5667                || join
5668                    .match_condition
5669                    .as_ref()
5670                    .is_some_and(Dialect::node_has_lateral)
5671                || join.pivots.iter().any(Dialect::node_has_lateral)
5672        }
5673
5674        fn joins_have_lateral(joins: &[Join]) -> bool {
5675            joins.iter().any(join_has_lateral)
5676        }
5677
5678        match expr {
5679            Expression::Subquery(subquery) => {
5680                subquery.lateral || Dialect::node_has_lateral(&subquery.this)
5681            }
5682            Expression::Lateral(_) | Expression::LateralView(_) => true,
5683            Expression::Join(join) => join_has_lateral(join),
5684            Expression::Select(select) => {
5685                !select.lateral_views.is_empty()
5686                    || joins_have_lateral(&select.joins)
5687                    || select
5688                        .from
5689                        .as_ref()
5690                        .is_some_and(|from| from.expressions.iter().any(Dialect::node_has_lateral))
5691            }
5692            Expression::JoinedTable(joined) => {
5693                !joined.lateral_views.is_empty()
5694                    || Dialect::node_has_lateral(&joined.left)
5695                    || joins_have_lateral(&joined.joins)
5696            }
5697            Expression::Update(update) => {
5698                joins_have_lateral(&update.table_joins) || joins_have_lateral(&update.from_joins)
5699            }
5700            _ => false,
5701        }
5702    }
5703
5704    fn node_has_join_using(expr: &Expression) -> bool {
5705        fn has_using(joins: &[Join]) -> bool {
5706            joins.iter().any(|join| !join.using.is_empty())
5707        }
5708
5709        match expr {
5710            Expression::Join(join) => !join.using.is_empty(),
5711            Expression::Select(select) => has_using(&select.joins),
5712            Expression::JoinedTable(joined) => has_using(&joined.joins),
5713            Expression::Update(update) => {
5714                has_using(&update.table_joins) || has_using(&update.from_joins)
5715            }
5716            Expression::Delete(delete) => has_using(&delete.joins),
5717            _ => false,
5718        }
5719    }
5720
5721    fn node_has_natural_join(expr: &Expression) -> bool {
5722        fn is_natural(join: &Join) -> bool {
5723            matches!(
5724                join.kind,
5725                crate::expressions::JoinKind::Natural
5726                    | crate::expressions::JoinKind::NaturalLeft
5727                    | crate::expressions::JoinKind::NaturalRight
5728                    | crate::expressions::JoinKind::NaturalFull
5729            )
5730        }
5731
5732        fn has_natural(joins: &[Join]) -> bool {
5733            joins.iter().any(is_natural)
5734        }
5735
5736        match expr {
5737            Expression::Join(join) => is_natural(join),
5738            Expression::Select(select) => has_natural(&select.joins),
5739            Expression::JoinedTable(joined) => has_natural(&joined.joins),
5740            Expression::Update(update) => {
5741                has_natural(&update.table_joins) || has_natural(&update.from_joins)
5742            }
5743            Expression::Delete(delete) => has_natural(&delete.joins),
5744            _ => false,
5745        }
5746    }
5747
5748    fn node_has_unsupported_relation_column_aliases(expr: &Expression) -> bool {
5749        match expr {
5750            Expression::Table(table) => !table.column_aliases.is_empty(),
5751            Expression::Alias(alias) => {
5752                !alias.column_aliases.is_empty()
5753                    && matches!(
5754                        alias.this,
5755                        Expression::Table(_) | Expression::JoinedTable(_)
5756                    )
5757            }
5758            _ => false,
5759        }
5760    }
5761
5762    fn node_is_aggregate_function(expr: &Expression) -> bool {
5763        is_aggregate(expr)
5764    }
5765
5766    fn node_has_qualified_whole_row_aggregate_argument(expr: &Expression) -> bool {
5767        fn contains_qualified_star(expr: &Expression) -> bool {
5768            match expr {
5769                Expression::Star(star) => star.table.is_some(),
5770                // A star projected by an embedded query is not an argument of
5771                // the surrounding aggregate (for example, inside EXISTS).
5772                Expression::Select(_)
5773                | Expression::Subquery(_)
5774                | Expression::Union(_)
5775                | Expression::Intersect(_)
5776                | Expression::Except(_) => false,
5777                _ => expr.children().into_iter().any(contains_qualified_star),
5778            }
5779        }
5780
5781        Self::node_is_aggregate_function(expr)
5782            && expr.children().into_iter().any(contains_qualified_star)
5783    }
5784
5785    fn node_has_subquery_in_aggregate_argument(expr: &Expression) -> bool {
5786        fn contains_query(expr: &Expression) -> bool {
5787            matches!(
5788                expr,
5789                Expression::Select(_)
5790                    | Expression::Subquery(_)
5791                    | Expression::Union(_)
5792                    | Expression::Intersect(_)
5793                    | Expression::Except(_)
5794            ) || expr.children().into_iter().any(contains_query)
5795        }
5796
5797        Self::node_is_aggregate_function(expr) && expr.children().into_iter().any(contains_query)
5798    }
5799
5800    fn tsql_apply_has_invalid_outer_aggregate(expr: &Expression) -> bool {
5801        // SQL Server error 8124: if an aggregate expression contains an outer
5802        // reference, that reference must be the only column used by the expression.
5803        fn collect_source_names(expr: &Expression, names: &mut HashSet<String>) {
5804            let mut insert = |name: &Identifier| {
5805                if !name.name.is_empty() {
5806                    names.insert(name.name.to_ascii_lowercase());
5807                }
5808            };
5809
5810            match expr {
5811                Expression::Table(table) => {
5812                    insert(table.alias.as_ref().unwrap_or(&table.name));
5813                }
5814                Expression::Subquery(subquery) => {
5815                    if let Some(alias) = &subquery.alias {
5816                        insert(alias);
5817                    }
5818                }
5819                Expression::Alias(alias) => insert(&alias.alias),
5820                Expression::JoinedTable(joined) => {
5821                    if let Some(alias) = &joined.alias {
5822                        insert(alias);
5823                    } else {
5824                        collect_source_names(&joined.left, names);
5825                        for join in &joined.joins {
5826                            collect_source_names(&join.this, names);
5827                        }
5828                    }
5829                }
5830                Expression::Paren(paren) => collect_source_names(&paren.this, names),
5831                Expression::Pivot(pivot) => {
5832                    if let Some(alias) = &pivot.alias {
5833                        insert(alias);
5834                    } else {
5835                        collect_source_names(&pivot.this, names);
5836                    }
5837                }
5838                Expression::Unpivot(unpivot) => {
5839                    if let Some(alias) = &unpivot.alias {
5840                        insert(alias);
5841                    } else {
5842                        collect_source_names(&unpivot.this, names);
5843                    }
5844                }
5845                _ => {}
5846            }
5847        }
5848
5849        fn collect_columns<'a>(
5850            expr: &'a Expression,
5851            columns: &mut Vec<&'a crate::expressions::Column>,
5852        ) {
5853            match expr {
5854                Expression::Column(column) => columns.push(column),
5855                // Query expressions introduce a new name-resolution scope. Their
5856                // columns are checked when their own SELECT node is visited.
5857                Expression::Select(_)
5858                | Expression::Subquery(_)
5859                | Expression::Union(_)
5860                | Expression::Intersect(_)
5861                | Expression::Except(_) => {}
5862                _ => {
5863                    for child in expr.children() {
5864                        collect_columns(child, columns);
5865                    }
5866                }
5867            }
5868        }
5869
5870        fn same_column(
5871            left: &crate::expressions::Column,
5872            right: &crate::expressions::Column,
5873        ) -> bool {
5874            let same_identifier =
5875                |left: &Identifier, right: &Identifier| left.name.eq_ignore_ascii_case(&right.name);
5876
5877            same_identifier(&left.name, &right.name)
5878                && match (&left.table, &right.table) {
5879                    (Some(left), Some(right)) => same_identifier(left, right),
5880                    (None, None) => true,
5881                    _ => false,
5882                }
5883        }
5884
5885        fn aggregate_is_invalid(expr: &Expression, local_sources: &HashSet<String>) -> bool {
5886            let mut columns = Vec::new();
5887            collect_columns(expr, &mut columns);
5888
5889            let outer_column = columns.iter().copied().find(|column| match &column.table {
5890                Some(table) => !local_sources.contains(&table.name.to_ascii_lowercase()),
5891                // Without a local source, an unqualified reference in a lateral
5892                // query can only resolve against an outer scope.
5893                None => local_sources.is_empty(),
5894            });
5895
5896            outer_column
5897                .is_some_and(|outer| columns.iter().any(|column| !same_column(outer, column)))
5898        }
5899
5900        fn expression_has_invalid_aggregate(
5901            expr: &Expression,
5902            local_sources: &HashSet<String>,
5903        ) -> bool {
5904            if Dialect::node_is_aggregate_function(expr)
5905                && aggregate_is_invalid(expr, local_sources)
5906            {
5907                return true;
5908            }
5909
5910            match expr {
5911                Expression::Select(_)
5912                | Expression::Subquery(_)
5913                | Expression::Union(_)
5914                | Expression::Intersect(_)
5915                | Expression::Except(_) => false,
5916                _ => expr
5917                    .children()
5918                    .into_iter()
5919                    .any(|child| expression_has_invalid_aggregate(child, local_sources)),
5920            }
5921        }
5922
5923        fn select_has_invalid_aggregate(select: &Select) -> bool {
5924            let mut local_sources = HashSet::new();
5925            if let Some(from) = &select.from {
5926                for source in &from.expressions {
5927                    collect_source_names(source, &mut local_sources);
5928                }
5929            }
5930            for join in &select.joins {
5931                collect_source_names(&join.this, &mut local_sources);
5932            }
5933
5934            let invalid =
5935                |expr: &Expression| expression_has_invalid_aggregate(expr, &local_sources);
5936
5937            select.expressions.iter().any(invalid)
5938                || select.prewhere.as_ref().is_some_and(invalid)
5939                || select
5940                    .where_clause
5941                    .as_ref()
5942                    .is_some_and(|where_clause| invalid(&where_clause.this))
5943                || select
5944                    .group_by
5945                    .as_ref()
5946                    .is_some_and(|group_by| group_by.expressions.iter().any(invalid))
5947                || select
5948                    .having
5949                    .as_ref()
5950                    .is_some_and(|having| invalid(&having.this))
5951                || select
5952                    .qualify
5953                    .as_ref()
5954                    .is_some_and(|qualify| invalid(&qualify.this))
5955                || select.order_by.as_ref().is_some_and(|order_by| {
5956                    order_by
5957                        .expressions
5958                        .iter()
5959                        .any(|ordered| invalid(&ordered.this))
5960                })
5961        }
5962
5963        fn apply_rhs_is_invalid(rhs: &Expression) -> bool {
5964            rhs.dfs().any(|node| match node {
5965                Expression::Select(select) => select_has_invalid_aggregate(select),
5966                _ => false,
5967            })
5968        }
5969
5970        fn joins_have_invalid_aggregate(joins: &[Join]) -> bool {
5971            joins.iter().any(|join| {
5972                matches!(
5973                    join.kind,
5974                    crate::expressions::JoinKind::CrossApply
5975                        | crate::expressions::JoinKind::OuterApply
5976                ) && apply_rhs_is_invalid(&join.this)
5977            })
5978        }
5979
5980        expr.dfs().any(|node| match node {
5981            Expression::Select(select) => joins_have_invalid_aggregate(&select.joins),
5982            Expression::JoinedTable(joined) => joins_have_invalid_aggregate(&joined.joins),
5983            Expression::Update(update) => {
5984                joins_have_invalid_aggregate(&update.table_joins)
5985                    || joins_have_invalid_aggregate(&update.from_joins)
5986            }
5987            Expression::Delete(delete) => joins_have_invalid_aggregate(&delete.joins),
5988            _ => false,
5989        })
5990    }
5991
5992    fn target_supports_remaining_unnest(target: DialectType) -> bool {
5993        matches!(
5994            target,
5995            DialectType::PostgreSQL
5996                | DialectType::BigQuery
5997                | DialectType::DuckDB
5998                | DialectType::Presto
5999                | DialectType::Trino
6000                | DialectType::Athena
6001        )
6002    }
6003
6004    fn target_supports_remaining_explode(target: DialectType) -> bool {
6005        matches!(
6006            target,
6007            DialectType::Spark | DialectType::Databricks | DialectType::Hive
6008        )
6009    }
6010
6011    fn target_lacks_array_agg(target: DialectType) -> bool {
6012        matches!(
6013            target,
6014            DialectType::Fabric
6015                | DialectType::TSQL
6016                | DialectType::MySQL
6017                | DialectType::SQLite
6018                | DialectType::Oracle
6019        )
6020    }
6021
6022    fn node_is_unnest(expr: &Expression) -> bool {
6023        matches!(expr, Expression::Unnest(_)) || Self::node_is_function_named(expr, "UNNEST")
6024    }
6025
6026    fn node_is_explode(expr: &Expression) -> bool {
6027        matches!(expr, Expression::Explode(_) | Expression::ExplodeOuter(_))
6028            || Self::node_is_function_named(expr, "EXPLODE")
6029            || Self::node_is_function_named(expr, "EXPLODE_OUTER")
6030    }
6031
6032    fn node_is_array_agg(expr: &Expression) -> bool {
6033        matches!(expr, Expression::ArrayAgg(_)) || Self::node_is_function_named(expr, "ARRAY_AGG")
6034    }
6035
6036    fn node_is_distinct_string_agg(expr: &Expression) -> bool {
6037        match expr {
6038            Expression::StringAgg(agg) => agg.distinct,
6039            Expression::Function(function) => {
6040                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6041            }
6042            Expression::AggregateFunction(function) => {
6043                function.distinct && function.name.eq_ignore_ascii_case("STRING_AGG")
6044            }
6045            _ => false,
6046        }
6047    }
6048
6049    fn postgres_tsql_unsupported_collation_name(expr: &Expression) -> Option<&'static str> {
6050        let Expression::Collation(collation) = expr else {
6051            return None;
6052        };
6053
6054        if collation.collation.eq_ignore_ascii_case("C") {
6055            Some("C")
6056        } else if collation.collation.eq_ignore_ascii_case("POSIX") {
6057            Some("POSIX")
6058        } else {
6059            None
6060        }
6061    }
6062
6063    fn collect_tsql_grouping_tuple_nodes(expr: &Expression) -> HashSet<*const Expression> {
6064        let mut tuples = HashSet::new();
6065
6066        for node in expr.dfs() {
6067            let Expression::Select(select) = node else {
6068                continue;
6069            };
6070            let Some(group_by) = &select.group_by else {
6071                continue;
6072            };
6073
6074            for expression in &group_by.expressions {
6075                Self::collect_tsql_grouping_element_tuples(expression, &mut tuples);
6076            }
6077        }
6078
6079        tuples
6080    }
6081
6082    fn collect_tsql_grouping_element_tuples(
6083        expr: &Expression,
6084        tuples: &mut HashSet<*const Expression>,
6085    ) {
6086        match expr {
6087            Expression::GroupingSets(grouping_sets) => {
6088                for expression in &grouping_sets.expressions {
6089                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6090                }
6091            }
6092            Expression::Rollup(rollup) => {
6093                for expression in &rollup.expressions {
6094                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6095                }
6096            }
6097            Expression::Cube(cube) => {
6098                for expression in &cube.expressions {
6099                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6100                }
6101            }
6102            Expression::Function(function)
6103                if !function.quoted
6104                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6105                        || function.name.eq_ignore_ascii_case("ROLLUP")
6106                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6107            {
6108                for expression in &function.args {
6109                    Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6110                }
6111            }
6112            _ => {}
6113        }
6114    }
6115
6116    fn collect_tsql_grouping_unit_tuples(
6117        expr: &Expression,
6118        tuples: &mut HashSet<*const Expression>,
6119    ) {
6120        match expr {
6121            Expression::Tuple(tuple) => {
6122                tuples.insert(expr as *const Expression);
6123                for expression in &tuple.expressions {
6124                    match expression {
6125                        Expression::Tuple(_) | Expression::Paren(_) => {
6126                            Self::collect_tsql_grouping_unit_tuples(expression, tuples);
6127                        }
6128                        Expression::GroupingSets(_)
6129                        | Expression::Rollup(_)
6130                        | Expression::Cube(_) => {
6131                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6132                        }
6133                        Expression::Function(function)
6134                            if !function.quoted
6135                                && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6136                                    || function.name.eq_ignore_ascii_case("ROLLUP")
6137                                    || function.name.eq_ignore_ascii_case("CUBE")) =>
6138                        {
6139                            Self::collect_tsql_grouping_element_tuples(expression, tuples);
6140                        }
6141                        _ => {}
6142                    }
6143                }
6144            }
6145            Expression::Paren(paren) => {
6146                Self::collect_tsql_grouping_unit_tuples(&paren.this, tuples);
6147            }
6148            Expression::GroupingSets(_) | Expression::Rollup(_) | Expression::Cube(_) => {
6149                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6150            }
6151            Expression::Function(function)
6152                if !function.quoted
6153                    && (function.name.eq_ignore_ascii_case("GROUPING SETS")
6154                        || function.name.eq_ignore_ascii_case("ROLLUP")
6155                        || function.name.eq_ignore_ascii_case("CUBE")) =>
6156            {
6157                Self::collect_tsql_grouping_element_tuples(expr, tuples);
6158            }
6159            _ => {}
6160        }
6161    }
6162
6163    fn postgres_tsql_unsupported_composite_semantics(
6164        expr: &Expression,
6165        structural_grouping_tuple: bool,
6166    ) -> Option<&'static str> {
6167        match expr {
6168            Expression::Tuple(_) if !structural_grouping_tuple => Some("row/composite values"),
6169            Expression::Struct(_) | Expression::StructFunc(_) => Some("row/composite values"),
6170            Expression::Function(function)
6171                if !function.quoted && function.name.eq_ignore_ascii_case("ROW") =>
6172            {
6173                Some("row/composite values")
6174            }
6175            Expression::StructExtract(_) => Some("row/composite field access"),
6176            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) if matches!(&cast.this, Expression::Star(star) if star.table.is_some()) => {
6177                Some("qualified whole-row casts")
6178            }
6179            _ => None,
6180        }
6181    }
6182
6183    fn node_is_postgres_unknown_cast(expr: &Expression) -> bool {
6184        match expr {
6185            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
6186                normalization::is_postgres_unknown_type(&cast.to)
6187            }
6188            _ => false,
6189        }
6190    }
6191
6192    fn postgres_tsql_unsupported_array_semantics(expr: &Expression) -> Option<&'static str> {
6193        match expr {
6194            Expression::Array(_) | Expression::ArrayFunc(_) => Some("array literals"),
6195            Expression::Subscript(_) => Some("array subscripts"),
6196            Expression::ArraySlice(_) => Some("array slices"),
6197            Expression::DataType(DataType::Array { .. }) => Some("array data types"),
6198            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
6199                if matches!(&cast.to, DataType::Array { .. }) =>
6200            {
6201                Some("array data types")
6202            }
6203            Expression::ArrayLength(_) | Expression::ArraySize(_) => Some("ARRAY_LENGTH"),
6204            Expression::Cardinality(_) => Some("CARDINALITY"),
6205            Expression::ArrayToString(_) | Expression::ArrayJoin(_) => Some("ARRAY_TO_STRING"),
6206            Expression::StringToArray(_) => Some("STRING_TO_ARRAY"),
6207            Expression::ArrayContains(_)
6208            | Expression::ArrayPosition(_)
6209            | Expression::ArrayAppend(_)
6210            | Expression::ArrayPrepend(_)
6211            | Expression::ArrayConcat(_)
6212            | Expression::ArraySort(_)
6213            | Expression::ArrayReverse(_)
6214            | Expression::ArrayDistinct(_)
6215            | Expression::ArrayFilter(_)
6216            | Expression::ArrayTransform(_)
6217            | Expression::ArrayFlatten(_)
6218            | Expression::ArrayCompact(_)
6219            | Expression::ArrayIntersect(_)
6220            | Expression::ArrayUnion(_)
6221            | Expression::ArrayExcept(_)
6222            | Expression::ArrayRemove(_)
6223            | Expression::ArrayZip(_)
6224            | Expression::ArrayAll(_)
6225            | Expression::ArrayAny(_)
6226            | Expression::ArrayConstructCompact(_)
6227            | Expression::ArraySum(_) => Some("array functions"),
6228            Expression::ArrayContainsAll(_)
6229            | Expression::ArrayContainedBy(_)
6230            | Expression::ArrayOverlaps(_) => Some("array operators"),
6231            Expression::Function(function) => {
6232                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6233            }
6234            Expression::AggregateFunction(function) => {
6235                Self::postgres_tsql_unsupported_array_function_name_str(&function.name)
6236            }
6237            _ => None,
6238        }
6239    }
6240
6241    fn postgres_tsql_unsupported_array_function_name_str(name: &str) -> Option<&'static str> {
6242        if name.eq_ignore_ascii_case("ARRAY") {
6243            Some("array literals")
6244        } else if name.eq_ignore_ascii_case("ARRAY_LENGTH")
6245            || name.eq_ignore_ascii_case("ARRAY_SIZE")
6246        {
6247            Some("ARRAY_LENGTH")
6248        } else if name.eq_ignore_ascii_case("CARDINALITY") {
6249            Some("CARDINALITY")
6250        } else if name.eq_ignore_ascii_case("ARRAY_TO_STRING")
6251            || name.eq_ignore_ascii_case("ARRAY_JOIN")
6252        {
6253            Some("ARRAY_TO_STRING")
6254        } else if name.eq_ignore_ascii_case("STRING_TO_ARRAY") {
6255            Some("STRING_TO_ARRAY")
6256        } else {
6257            None
6258        }
6259    }
6260
6261    fn node_is_regex_predicate(expr: &Expression) -> bool {
6262        matches!(
6263            expr,
6264            Expression::SimilarTo(_) | Expression::RegexpLike(_) | Expression::RegexpILike(_)
6265        ) || Self::node_is_function_named(expr, "REGEXP_LIKE")
6266            || Self::node_is_function_named(expr, "REGEXP_I_LIKE")
6267            || Self::node_is_function_named(expr, "REGEXP_ILIKE")
6268    }
6269
6270    fn node_is_non_subquery_any(expr: &Expression) -> bool {
6271        matches!(
6272            expr,
6273            Expression::Any(q) if !Self::quantified_rhs_is_subquery(&q.subquery)
6274        )
6275    }
6276
6277    fn quantified_rhs_is_subquery(expr: &Expression) -> bool {
6278        match expr {
6279            Expression::Select(_) | Expression::Subquery(_) => true,
6280            Expression::Paren(paren) => Self::quantified_rhs_is_subquery(&paren.this),
6281            _ => false,
6282        }
6283    }
6284
6285    fn node_is_row_value_subquery_comparison(expr: &Expression) -> bool {
6286        match expr {
6287            Expression::In(in_expr) => {
6288                Self::in_rhs_is_subquery_like(in_expr) && Self::expr_is_row_value(&in_expr.this)
6289            }
6290            Expression::Eq(op) | Expression::Neq(op) => {
6291                (Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right))
6292                    || (Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left))
6293            }
6294            _ => false,
6295        }
6296    }
6297
6298    fn node_is_row_value_values_membership(expr: &Expression) -> bool {
6299        matches!(
6300            expr,
6301            Expression::In(in_expr)
6302                if Self::expr_is_row_value(&in_expr.this)
6303                    && Self::in_rhs_is_values_like(in_expr)
6304        )
6305    }
6306
6307    fn expr_is_row_value(expr: &Expression) -> bool {
6308        match expr {
6309            Expression::Tuple(tuple) => tuple.expressions.len() > 1,
6310            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
6311                function.args.len() > 1
6312            }
6313            Expression::Paren(paren) => Self::expr_is_row_value(&paren.this),
6314            _ => false,
6315        }
6316    }
6317
6318    fn expr_is_subquery_like(expr: &Expression) -> bool {
6319        match expr {
6320            Expression::Select(_) | Expression::Subquery(_) => true,
6321            Expression::Paren(paren) => Self::expr_is_subquery_like(&paren.this),
6322            _ => false,
6323        }
6324    }
6325
6326    fn in_rhs_is_subquery_like(in_expr: &crate::expressions::In) -> bool {
6327        if in_expr
6328            .query
6329            .as_ref()
6330            .is_some_and(Self::expr_is_subquery_like)
6331        {
6332            return true;
6333        }
6334
6335        in_expr.expressions.len() == 1 && Self::expr_is_subquery_like(&in_expr.expressions[0])
6336    }
6337
6338    fn in_rhs_is_values_like(in_expr: &crate::expressions::In) -> bool {
6339        if in_expr
6340            .query
6341            .as_ref()
6342            .is_some_and(Self::expr_is_values_like)
6343        {
6344            return true;
6345        }
6346
6347        (in_expr.expressions.len() == 1
6348            && Self::expr_is_values_like(&in_expr.expressions[0]))
6349            || in_expr.expressions.first().is_some_and(|expr| {
6350                matches!(expr, Expression::Function(function) if function.name.eq_ignore_ascii_case("VALUES"))
6351            })
6352    }
6353
6354    fn expr_is_values_like(expr: &Expression) -> bool {
6355        match expr {
6356            Expression::Values(_) => true,
6357            Expression::Paren(paren) => Self::expr_is_values_like(&paren.this),
6358            Expression::Subquery(subquery) => Self::expr_is_values_like(&subquery.this),
6359            _ => false,
6360        }
6361    }
6362
6363    fn normalize_tsql_fetch_overlaps_date_bin(expr: Expression) -> Result<Expression> {
6364        transform_recursive(expr, &|e| match e {
6365            Expression::Select(mut select) => {
6366                if select.top.is_none() && select.offset.is_none() {
6367                    if let Some(fetch) = select.fetch.take() {
6368                        if let Some(top) = Self::fetch_with_ties_to_top(fetch.clone()) {
6369                            select.top = Some(top);
6370                        } else {
6371                            select.fetch = Some(fetch);
6372                        }
6373                    }
6374                }
6375                Self::rewrite_tsql_overlaps_in_select_predicates(&mut select)?;
6376                Ok(Expression::Select(select))
6377            }
6378            Expression::DateBin(date_bin) => {
6379                let date_bin = *date_bin;
6380                if let Some(rewritten) = Self::date_bin_to_date_bucket(date_bin.clone()) {
6381                    Ok(rewritten)
6382                } else {
6383                    Ok(Expression::DateBin(Box::new(date_bin)))
6384                }
6385            }
6386            Expression::Function(function) => {
6387                let function = *function;
6388                if function.name.eq_ignore_ascii_case("DATE_BIN") {
6389                    if let Some(rewritten) = Self::date_bin_function_to_date_bucket(&function) {
6390                        Ok(rewritten)
6391                    } else {
6392                        Ok(Expression::Function(Box::new(function)))
6393                    }
6394                } else {
6395                    Ok(Expression::Function(Box::new(function)))
6396                }
6397            }
6398            _ => Ok(e),
6399        })
6400    }
6401
6402    fn rewrite_tsql_overlaps_in_select_predicates(
6403        select: &mut crate::expressions::Select,
6404    ) -> Result<()> {
6405        if let Some(where_clause) = &mut select.where_clause {
6406            where_clause.this = Self::rewrite_tsql_overlaps_predicate(where_clause.this.clone())?;
6407        }
6408        if let Some(having) = &mut select.having {
6409            having.this = Self::rewrite_tsql_overlaps_predicate(having.this.clone())?;
6410        }
6411        if let Some(qualify) = &mut select.qualify {
6412            qualify.this = Self::rewrite_tsql_overlaps_predicate(qualify.this.clone())?;
6413        }
6414        for join in &mut select.joins {
6415            if let Some(on) = join.on.take() {
6416                join.on = Some(Self::rewrite_tsql_overlaps_predicate(on)?);
6417            }
6418            if let Some(match_condition) = join.match_condition.take() {
6419                join.match_condition =
6420                    Some(Self::rewrite_tsql_overlaps_predicate(match_condition)?);
6421            }
6422        }
6423        Ok(())
6424    }
6425
6426    fn rewrite_tsql_overlaps_predicate(expr: Expression) -> Result<Expression> {
6427        transform_recursive(expr, &|e| match e {
6428            Expression::Overlaps(overlaps) => {
6429                let overlaps = *overlaps;
6430                if let Some(rewritten) = Self::rewrite_full_overlaps_for_tsql(&overlaps) {
6431                    Ok(rewritten)
6432                } else {
6433                    Ok(Expression::Overlaps(Box::new(overlaps)))
6434                }
6435            }
6436            _ => Ok(e),
6437        })
6438    }
6439
6440    fn fetch_with_ties_to_top(fetch: Fetch) -> Option<Top> {
6441        if !fetch.with_ties {
6442            return None;
6443        }
6444
6445        fetch.count.map(|count| Top {
6446            this: count,
6447            percent: fetch.percent,
6448            with_ties: true,
6449            parenthesized: true,
6450        })
6451    }
6452
6453    fn rewrite_full_overlaps_for_tsql(
6454        overlaps: &crate::expressions::OverlapsExpr,
6455    ) -> Option<Expression> {
6456        let (left_start, left_end, right_start, right_end) =
6457            if let (Some(left_start), Some(left_end), Some(right_start), Some(right_end)) = (
6458                overlaps.left_start.as_ref(),
6459                overlaps.left_end.as_ref(),
6460                overlaps.right_start.as_ref(),
6461                overlaps.right_end.as_ref(),
6462            ) {
6463                (left_start, left_end, right_start, right_end)
6464            } else if let (
6465                Some(Expression::Tuple(left_tuple)),
6466                Some(Expression::Tuple(right_tuple)),
6467            ) = (&overlaps.this, &overlaps.expression)
6468            {
6469                if left_tuple.expressions.len() != 2 || right_tuple.expressions.len() != 2 {
6470                    return None;
6471                }
6472                (
6473                    &left_tuple.expressions[0],
6474                    &left_tuple.expressions[1],
6475                    &right_tuple.expressions[0],
6476                    &right_tuple.expressions[1],
6477                )
6478            } else {
6479                return None;
6480            };
6481
6482        let left_min = Self::case_min(left_start.clone(), left_end.clone());
6483        let left_max = Self::case_max(left_start.clone(), left_end.clone());
6484        let right_min = Self::case_min(right_start.clone(), right_end.clone());
6485        let right_max = Self::case_max(right_start.clone(), right_end.clone());
6486
6487        Some(Expression::And(Box::new(BinaryOp::new(
6488            Expression::Lte(Box::new(BinaryOp::new(left_min, right_max))),
6489            Expression::Lte(Box::new(BinaryOp::new(right_min, left_max))),
6490        ))))
6491    }
6492
6493    fn case_min(left: Expression, right: Expression) -> Expression {
6494        Expression::Case(Box::new(Case {
6495            operand: None,
6496            whens: vec![(
6497                Expression::Lte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6498                left,
6499            )],
6500            else_: Some(right),
6501            comments: Vec::new(),
6502            inferred_type: None,
6503        }))
6504    }
6505
6506    fn case_max(left: Expression, right: Expression) -> Expression {
6507        Expression::Case(Box::new(Case {
6508            operand: None,
6509            whens: vec![(
6510                Expression::Gte(Box::new(BinaryOp::new(left.clone(), right.clone()))),
6511                left,
6512            )],
6513            else_: Some(right),
6514            comments: Vec::new(),
6515            inferred_type: None,
6516        }))
6517    }
6518
6519    fn date_bin_to_date_bucket(date_bin: DateBin) -> Option<Expression> {
6520        if date_bin.unit.is_some() || date_bin.zone.is_some() {
6521            return None;
6522        }
6523
6524        let (datepart, number) = Self::date_bucket_parts(&date_bin.this)?;
6525        let mut args = vec![
6526            Self::date_bucket_datepart(datepart),
6527            number,
6528            *date_bin.expression,
6529        ];
6530        if let Some(origin) = date_bin.origin {
6531            args.push(*origin);
6532        }
6533
6534        Some(Expression::Function(Box::new(Function::new(
6535            "DATE_BUCKET".to_string(),
6536            args,
6537        ))))
6538    }
6539
6540    fn date_bin_function_to_date_bucket(function: &Function) -> Option<Expression> {
6541        if !(2..=3).contains(&function.args.len()) {
6542            return None;
6543        }
6544
6545        let (datepart, number) = Self::date_bucket_parts(&function.args[0])?;
6546        let mut args = vec![
6547            Self::date_bucket_datepart(datepart),
6548            number,
6549            function.args[1].clone(),
6550        ];
6551        if let Some(origin) = function.args.get(2) {
6552            args.push(origin.clone());
6553        }
6554
6555        Some(Expression::Function(Box::new(Function::new(
6556            "DATE_BUCKET".to_string(),
6557            args,
6558        ))))
6559    }
6560
6561    fn date_bucket_parts(stride: &Expression) -> Option<(&'static str, Expression)> {
6562        match stride {
6563            Expression::Literal(lit) => match lit.as_ref() {
6564                Literal::String(value) => Self::date_bucket_parts_from_string(value),
6565                _ => None,
6566            },
6567            Expression::Interval(interval) => Self::date_bucket_parts_from_interval(interval),
6568            _ => None,
6569        }
6570    }
6571
6572    fn date_bucket_parts_from_interval(interval: &Interval) -> Option<(&'static str, Expression)> {
6573        match &interval.unit {
6574            Some(IntervalUnitSpec::Simple { unit, .. }) => {
6575                let datepart = Self::date_bucket_datepart_from_unit(*unit)?;
6576                let amount = interval
6577                    .this
6578                    .as_ref()
6579                    .and_then(Self::date_bucket_amount_expr)?;
6580                Some((datepart, amount))
6581            }
6582            None => interval.this.as_ref().and_then(|expr| match expr {
6583                Expression::Literal(lit) => match lit.as_ref() {
6584                    Literal::String(value) => Self::date_bucket_parts_from_string(value),
6585                    _ => None,
6586                },
6587                _ => None,
6588            }),
6589            _ => None,
6590        }
6591    }
6592
6593    fn date_bucket_parts_from_string(value: &str) -> Option<(&'static str, Expression)> {
6594        let mut parts = value.split_whitespace();
6595        let amount = parts.next()?;
6596        let unit = parts.next()?;
6597        if parts.next().is_some() {
6598            return None;
6599        }
6600
6601        Some((
6602            Self::date_bucket_datepart_from_name(unit)?,
6603            Self::positive_integer_expr(amount)?,
6604        ))
6605    }
6606
6607    fn date_bucket_amount_expr(expr: &Expression) -> Option<Expression> {
6608        match expr {
6609            Expression::Literal(lit) => match lit.as_ref() {
6610                Literal::Number(value) => Self::positive_integer_expr(value),
6611                Literal::String(value) => Self::positive_integer_expr(value),
6612                _ => None,
6613            },
6614            _ => Some(expr.clone()),
6615        }
6616    }
6617
6618    fn positive_integer_expr(value: &str) -> Option<Expression> {
6619        let parsed = value.trim().parse::<i64>().ok()?;
6620        (parsed > 0).then(|| Expression::number(parsed))
6621    }
6622
6623    fn date_bucket_datepart(datepart: &str) -> Expression {
6624        Expression::Var(Box::new(Var {
6625            this: datepart.to_string(),
6626        }))
6627    }
6628
6629    fn date_bucket_datepart_from_unit(unit: IntervalUnit) -> Option<&'static str> {
6630        match unit {
6631            IntervalUnit::Week => Some("WEEK"),
6632            IntervalUnit::Day => Some("DAY"),
6633            IntervalUnit::Hour => Some("HOUR"),
6634            IntervalUnit::Minute => Some("MINUTE"),
6635            IntervalUnit::Second => Some("SECOND"),
6636            IntervalUnit::Millisecond => Some("MILLISECOND"),
6637            _ => None,
6638        }
6639    }
6640
6641    fn date_bucket_datepart_from_name(unit: &str) -> Option<&'static str> {
6642        match unit.trim().to_ascii_uppercase().as_str() {
6643            "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" => Some("WEEK"),
6644            "DAY" | "DAYS" | "D" | "DD" => Some("DAY"),
6645            "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some("HOUR"),
6646            "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some("MINUTE"),
6647            "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some("SECOND"),
6648            "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MILLISEC" | "MILLISECS" => {
6649                Some("MILLISECOND")
6650            }
6651            _ => None,
6652        }
6653    }
6654
6655    fn node_has_fetch_with_ties(expr: &Expression) -> bool {
6656        matches!(
6657            expr,
6658            Expression::Select(select)
6659                if select
6660                    .fetch
6661                    .as_ref()
6662                    .is_some_and(|fetch| fetch.with_ties)
6663        )
6664    }
6665
6666    fn node_is_overlaps(expr: &Expression) -> bool {
6667        matches!(expr, Expression::Overlaps(_))
6668    }
6669
6670    fn node_is_date_bin(expr: &Expression) -> bool {
6671        matches!(expr, Expression::DateBin(_)) || Self::node_is_function_named(expr, "DATE_BIN")
6672    }
6673
6674    fn node_is_function_named(expr: &Expression, name: &str) -> bool {
6675        match expr {
6676            Expression::Function(function) => function.name.eq_ignore_ascii_case(name),
6677            Expression::AggregateFunction(function) => function.name.eq_ignore_ascii_case(name),
6678            _ => false,
6679        }
6680    }
6681
6682    fn node_is_postgres_json_build_object(expr: &Expression) -> bool {
6683        match expr {
6684            Expression::Function(function) => {
6685                function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6686                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT")
6687            }
6688            _ => false,
6689        }
6690    }
6691
6692    fn postgres_json_build_object_can_lower_to_json_object(expr: &Expression) -> bool {
6693        matches!(
6694            expr,
6695            Expression::Function(function)
6696                if (function.name.eq_ignore_ascii_case("JSON_BUILD_OBJECT")
6697                    || function.name.eq_ignore_ascii_case("JSONB_BUILD_OBJECT"))
6698                    && !function.distinct
6699                    && function.args.len() % 2 == 0
6700        )
6701    }
6702
6703    fn node_is_postgres_json_array_elements(expr: &Expression) -> bool {
6704        matches!(
6705            expr,
6706            Expression::Function(function)
6707                if function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS")
6708                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS")
6709                    || function.name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT")
6710                    || function.name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT")
6711        )
6712    }
6713
6714    fn postgres_tsql_unsupported_function_name(
6715        expr: &Expression,
6716        target: DialectType,
6717    ) -> Option<&'static str> {
6718        match expr {
6719            Expression::Lpad(_) => Some("LPAD"),
6720            Expression::Rpad(_) => Some("RPAD"),
6721            Expression::SplitPart(_) => Some("SPLIT_PART"),
6722            Expression::Initcap(_) => Some("INITCAP"),
6723            Expression::RegexpReplace(_) => Some("REGEXP_REPLACE"),
6724            Expression::RegexpInstr(_) => Some("REGEXP_INSTR"),
6725            Expression::RegexpCount(_) => Some("REGEXP_COUNT"),
6726            Expression::RegexpSplit(_) => Some("REGEXP_SPLIT"),
6727            Expression::DecodeCase(_) => Some("DECODE"),
6728            Expression::ToJson(_) => Some("TO_JSON"),
6729            Expression::JSONBObjectAgg(_) => Some("JSONB_OBJECT_AGG"),
6730            Expression::ToNumber(_) => Some("TO_NUMBER"),
6731            Expression::WidthBucket(_) => Some("WIDTH_BUCKET"),
6732            Expression::BitwiseAndAgg(_) => Some("BIT_AND"),
6733            Expression::BitwiseOrAgg(_) => Some("BIT_OR"),
6734            Expression::BitwiseXorAgg(_) => Some("BIT_XOR"),
6735            Expression::Corr(_) => Some("CORR"),
6736            Expression::CovarPop(_) => Some("COVAR_POP"),
6737            Expression::CovarSamp(_) => Some("COVAR_SAMP"),
6738            Expression::RegrAvgx(_) => Some("REGR_AVGX"),
6739            Expression::RegrAvgy(_) => Some("REGR_AVGY"),
6740            Expression::RegrCount(_) => Some("REGR_COUNT"),
6741            Expression::RegrIntercept(_) => Some("REGR_INTERCEPT"),
6742            Expression::RegrR2(_) => Some("REGR_R2"),
6743            Expression::RegrSlope(_) => Some("REGR_SLOPE"),
6744            Expression::RegrSxx(_) => Some("REGR_SXX"),
6745            Expression::RegrSxy(_) => Some("REGR_SXY"),
6746            Expression::RegrSyy(_) => Some("REGR_SYY"),
6747            Expression::Function(function) => {
6748                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6749            }
6750            Expression::AggregateFunction(function) => {
6751                Self::postgres_tsql_unsupported_function_name_str(&function.name, target)
6752            }
6753            _ => None,
6754        }
6755    }
6756
6757    fn postgres_tsql_unsupported_function_name_str(
6758        name: &str,
6759        target: DialectType,
6760    ) -> Option<&'static str> {
6761        if name.eq_ignore_ascii_case("LPAD") {
6762            Some("LPAD")
6763        } else if name.eq_ignore_ascii_case("RPAD") {
6764            Some("RPAD")
6765        } else if name.eq_ignore_ascii_case("SPLIT_PART") {
6766            Some("SPLIT_PART")
6767        } else if name.eq_ignore_ascii_case("INITCAP") {
6768            Some("INITCAP")
6769        } else if name.eq_ignore_ascii_case("TO_JSON") {
6770            Some("TO_JSON")
6771        } else if name.eq_ignore_ascii_case("TO_JSONB") {
6772            Some("TO_JSONB")
6773        } else if name.eq_ignore_ascii_case("JSONB_OBJECT_AGG") {
6774            Some("JSONB_OBJECT_AGG")
6775        } else if name.eq_ignore_ascii_case("ROW_TO_JSON") {
6776            Some("ROW_TO_JSON")
6777        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS") {
6778            Some("JSON_ARRAY_ELEMENTS")
6779        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS") {
6780            Some("JSONB_ARRAY_ELEMENTS")
6781        } else if name.eq_ignore_ascii_case("JSON_ARRAY_ELEMENTS_TEXT") {
6782            Some("JSON_ARRAY_ELEMENTS_TEXT")
6783        } else if name.eq_ignore_ascii_case("JSONB_ARRAY_ELEMENTS_TEXT") {
6784            Some("JSONB_ARRAY_ELEMENTS_TEXT")
6785        } else if name.eq_ignore_ascii_case("ENCODE") {
6786            Some("ENCODE")
6787        } else if name.eq_ignore_ascii_case("DECODE") {
6788            Some("DECODE")
6789        } else if name.eq_ignore_ascii_case("REGEXP_REPLACE") {
6790            Some("REGEXP_REPLACE")
6791        } else if name.eq_ignore_ascii_case("REGEXP_COUNT") {
6792            Some("REGEXP_COUNT")
6793        } else if name.eq_ignore_ascii_case("REGEXP_INSTR") {
6794            Some("REGEXP_INSTR")
6795        } else if name.eq_ignore_ascii_case("REGEXP_SUBSTR") {
6796            Some("REGEXP_SUBSTR")
6797        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT") {
6798            Some("REGEXP_SPLIT")
6799        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_ARRAY") {
6800            Some("REGEXP_SPLIT_TO_ARRAY")
6801        } else if name.eq_ignore_ascii_case("REGEXP_SPLIT_TO_TABLE") {
6802            Some("REGEXP_SPLIT_TO_TABLE")
6803        } else if name.eq_ignore_ascii_case("SHA224") {
6804            Some("SHA224")
6805        } else if name.eq_ignore_ascii_case("SHA384") {
6806            Some("SHA384")
6807        } else if name.eq_ignore_ascii_case("TO_BIN") {
6808            Some("TO_BIN")
6809        } else if name.eq_ignore_ascii_case("TO_OCT") {
6810            Some("TO_OCT")
6811        } else if target == DialectType::TSQL && name.eq_ignore_ascii_case("UNISTR") {
6812            Some("UNISTR")
6813        } else if name.eq_ignore_ascii_case("AGE") {
6814            Some("AGE")
6815        } else if name.eq_ignore_ascii_case("ERF") {
6816            Some("ERF")
6817        } else if name.eq_ignore_ascii_case("SINH") {
6818            Some("SINH")
6819        } else if name.eq_ignore_ascii_case("COSH") {
6820            Some("COSH")
6821        } else if name.eq_ignore_ascii_case("TANH") {
6822            Some("TANH")
6823        } else if name.eq_ignore_ascii_case("ASINH") {
6824            Some("ASINH")
6825        } else if name.eq_ignore_ascii_case("ACOSH") {
6826            Some("ACOSH")
6827        } else if name.eq_ignore_ascii_case("ATANH") {
6828            Some("ATANH")
6829        } else if name.eq_ignore_ascii_case("GCD") {
6830            Some("GCD")
6831        } else if name.eq_ignore_ascii_case("LCM") {
6832            Some("LCM")
6833        } else if name.eq_ignore_ascii_case("QUOTE_LITERAL") {
6834            Some("QUOTE_LITERAL")
6835        } else if name.eq_ignore_ascii_case("WIDTH_BUCKET") {
6836            Some("WIDTH_BUCKET")
6837        } else if name.eq_ignore_ascii_case("SCALE") {
6838            Some("SCALE")
6839        } else if name.eq_ignore_ascii_case("TRIM_SCALE") {
6840            Some("TRIM_SCALE")
6841        } else if name.eq_ignore_ascii_case("MIN_SCALE") {
6842            Some("MIN_SCALE")
6843        } else if name.eq_ignore_ascii_case("FACTORIAL") {
6844            Some("FACTORIAL")
6845        } else if name.eq_ignore_ascii_case("PG_LSN") {
6846            Some("PG_LSN")
6847        } else if name.eq_ignore_ascii_case("TO_CHAR") {
6848            Some("TO_CHAR")
6849        } else if name.eq_ignore_ascii_case("PG_TYPEOF") {
6850            Some("PG_TYPEOF")
6851        } else if name.eq_ignore_ascii_case("BIT_AND") {
6852            Some("BIT_AND")
6853        } else if name.eq_ignore_ascii_case("BIT_OR") {
6854            Some("BIT_OR")
6855        } else if name.eq_ignore_ascii_case("BIT_XOR") {
6856            Some("BIT_XOR")
6857        } else if name.eq_ignore_ascii_case("CORR") {
6858            Some("CORR")
6859        } else if name.eq_ignore_ascii_case("COVAR_POP") {
6860            Some("COVAR_POP")
6861        } else if name.eq_ignore_ascii_case("COVAR_SAMP") {
6862            Some("COVAR_SAMP")
6863        } else if name.eq_ignore_ascii_case("REGR_AVGX") {
6864            Some("REGR_AVGX")
6865        } else if name.eq_ignore_ascii_case("REGR_AVGY") {
6866            Some("REGR_AVGY")
6867        } else if name.eq_ignore_ascii_case("REGR_COUNT") {
6868            Some("REGR_COUNT")
6869        } else if name.eq_ignore_ascii_case("REGR_INTERCEPT") {
6870            Some("REGR_INTERCEPT")
6871        } else if name.eq_ignore_ascii_case("REGR_R2") {
6872            Some("REGR_R2")
6873        } else if name.eq_ignore_ascii_case("REGR_SLOPE") {
6874            Some("REGR_SLOPE")
6875        } else if name.eq_ignore_ascii_case("REGR_SXX") {
6876            Some("REGR_SXX")
6877        } else if name.eq_ignore_ascii_case("REGR_SXY") {
6878            Some("REGR_SXY")
6879        } else if name.eq_ignore_ascii_case("REGR_SYY") {
6880            Some("REGR_SYY")
6881        } else if name.eq_ignore_ascii_case("FLOAT8_ACCUM") {
6882            Some("FLOAT8_ACCUM")
6883        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_ACCUM") {
6884            Some("FLOAT8_REGR_ACCUM")
6885        } else if name.eq_ignore_ascii_case("FLOAT8_COMBINE") {
6886            Some("FLOAT8_COMBINE")
6887        } else if name.eq_ignore_ascii_case("FLOAT8_REGR_COMBINE") {
6888            Some("FLOAT8_REGR_COMBINE")
6889        } else if name.eq_ignore_ascii_case("BOOLAND_STATEFUNC") {
6890            Some("BOOLAND_STATEFUNC")
6891        } else if name.eq_ignore_ascii_case("BOOLOR_STATEFUNC") {
6892            Some("BOOLOR_STATEFUNC")
6893        } else {
6894            None
6895        }
6896    }
6897
6898    fn normalize_postgres_trim_for_tsql(expr: Expression) -> Result<Expression> {
6899        transform_recursive(expr, &|e| match e {
6900            Expression::Trim(trim) => {
6901                let mut trim = *trim;
6902                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6903                match trim.position {
6904                    crate::expressions::TrimPosition::Both
6905                        if trim.position_explicit && trim.characters.is_some() =>
6906                    {
6907                        trim.position_explicit = false;
6908                        trim.sql_standard_syntax = true;
6909                        Ok(Expression::Trim(Box::new(trim)))
6910                    }
6911                    crate::expressions::TrimPosition::Leading if trim.characters.is_some() => {
6912                        let characters = trim.characters.take().expect("checked above");
6913                        Ok(Expression::Function(Box::new(Function::new(
6914                            "LTRIM",
6915                            vec![trim.this, characters],
6916                        ))))
6917                    }
6918                    crate::expressions::TrimPosition::Trailing if trim.characters.is_some() => {
6919                        let characters = trim.characters.take().expect("checked above");
6920                        Ok(Expression::Function(Box::new(Function::new(
6921                            "RTRIM",
6922                            vec![trim.this, characters],
6923                        ))))
6924                    }
6925                    _ => Ok(Expression::Trim(Box::new(trim))),
6926                }
6927            }
6928            other => Ok(other),
6929        })
6930    }
6931
6932    fn normalize_postgres_string_semantics_for_tsql(expr: Expression) -> Result<Expression> {
6933        transform_recursive(expr, &|e| match e {
6934            Expression::Like(mut op) => {
6935                Self::recover_postgres_like_escape(&mut op);
6936                Ok(Expression::Like(op))
6937            }
6938            Expression::ILike(mut op) => {
6939                Self::recover_postgres_like_escape(&mut op);
6940                Ok(Expression::ILike(op))
6941            }
6942            Expression::Substring(mut substring)
6943                if substring.length.is_none()
6944                    && Self::is_explicitly_numeric_expression(&substring.start) =>
6945            {
6946                substring.length = Some(Expression::number(i32::MAX as i64));
6947                Ok(Expression::Substring(substring))
6948            }
6949            Expression::Trim(mut trim) => {
6950                trim.characters = trim.characters.map(Self::strip_postgres_text_literal_cast);
6951                Ok(Expression::Trim(trim))
6952            }
6953            Expression::Function(mut function)
6954                if !function.quoted
6955                    && matches!(
6956                        function.name.to_ascii_uppercase().as_str(),
6957                        "BTRIM" | "LTRIM" | "RTRIM"
6958                    )
6959                    && function.args.len() == 2 =>
6960            {
6961                function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
6962                Ok(Expression::Function(function))
6963            }
6964            Expression::Translate(translate) => {
6965                Ok(Self::normalize_postgres_translate_for_tsql(*translate))
6966            }
6967            Expression::Function(function)
6968                if !function.quoted
6969                    && function.name.eq_ignore_ascii_case("TRANSLATE")
6970                    && function.args.len() == 3 =>
6971            {
6972                Ok(Self::normalize_postgres_translate_function_for_tsql(
6973                    *function,
6974                ))
6975            }
6976            other => Ok(other),
6977        })
6978    }
6979
6980    fn normalize_postgres_bytea_literals_for_tsql(expr: Expression) -> Result<Expression> {
6981        transform_recursive(expr, &|e| match e {
6982            Expression::Cast(cast) if Self::is_postgres_bytea_data_type(&cast.to) => {
6983                let Some(value) = Self::postgres_plain_string_literal_value(&cast.this) else {
6984                    return Ok(Expression::Cast(cast));
6985                };
6986                let Some(hex) = Self::postgres_bytea_hex_payload(value) else {
6987                    return Ok(Expression::Cast(cast));
6988                };
6989
6990                // Replace the complete BYTEA cast. Keeping a bare T-SQL
6991                // CAST(... AS VARBINARY) would apply SQL Server's default length
6992                // and could truncate payloads longer than 30 bytes.
6993                Ok(Expression::Literal(Box::new(Literal::HexString(hex))))
6994            }
6995            other => Ok(other),
6996        })
6997    }
6998
6999    fn postgres_plain_string_literal_value(expr: &Expression) -> Option<&str> {
7000        match expr {
7001            Expression::Literal(literal) => match literal.as_ref() {
7002                Literal::String(value) => Some(value),
7003                _ => None,
7004            },
7005            Expression::Paren(paren) => Self::postgres_plain_string_literal_value(&paren.this),
7006            _ => None,
7007        }
7008    }
7009
7010    fn postgres_bytea_hex_payload(value: &str) -> Option<String> {
7011        let payload = value.strip_prefix("\\x")?;
7012        if payload.is_empty() {
7013            return Some(String::new());
7014        }
7015
7016        let mut chars = payload.chars().peekable();
7017        let mut hex = String::with_capacity(payload.len());
7018        loop {
7019            let high = chars.next()?;
7020            let low = chars.next()?;
7021            if !high.is_ascii_hexdigit() || !low.is_ascii_hexdigit() {
7022                return None;
7023            }
7024            hex.push(high);
7025            hex.push(low);
7026
7027            let Some(next) = chars.peek().copied() else {
7028                return Some(hex);
7029            };
7030            if next.is_ascii_whitespace() {
7031                while chars
7032                    .peek()
7033                    .is_some_and(|character| character.is_ascii_whitespace())
7034                {
7035                    chars.next();
7036                }
7037                // PostgreSQL permits whitespace between byte pairs, not after
7038                // the prefix or after the final pair.
7039                chars.peek()?;
7040            }
7041        }
7042    }
7043
7044    fn is_postgres_bytea_data_type(data_type: &DataType) -> bool {
7045        match data_type {
7046            DataType::VarBinary { length: None } => true,
7047            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("BYTEA"),
7048            _ => false,
7049        }
7050    }
7051
7052    fn postgres_tsql_unsupported_binary_semantics(expr: &Expression) -> Option<&'static str> {
7053        let cast = match expr {
7054            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7055                if Self::is_postgres_bytea_data_type(&cast.to) =>
7056            {
7057                cast
7058            }
7059            _ => return None,
7060        };
7061
7062        let literal = match &cast.this {
7063            Expression::Literal(literal) => literal.as_ref(),
7064            Expression::Paren(paren) => match &paren.this {
7065                Expression::Literal(literal) => literal.as_ref(),
7066                _ => return None,
7067            },
7068            _ => return None,
7069        };
7070        let value = match literal {
7071            Literal::String(value) | Literal::EscapeString(value) => value,
7072            _ => return None,
7073        };
7074
7075        if value.starts_with("\\x") {
7076            Some("bytea hex literals with invalid or unsupported formatting")
7077        } else if value.contains('\\') {
7078            Some("bytea escape-format literals")
7079        } else {
7080            None
7081        }
7082    }
7083
7084    fn recover_postgres_like_escape(op: &mut crate::expressions::LikeOp) {
7085        if op.escape.is_some() {
7086            return;
7087        }
7088
7089        let Expression::Function(function) = &op.right else {
7090            return;
7091        };
7092        if function.quoted
7093            || function.distinct
7094            || !function.name.eq_ignore_ascii_case("LIKE_ESCAPE")
7095            || function.args.len() != 2
7096        {
7097            return;
7098        }
7099
7100        let pattern = function.args[0].clone();
7101        let escape = function.args[1].clone();
7102        op.right = Self::strip_postgres_text_literal_cast(pattern);
7103        op.escape = Some(Self::strip_postgres_text_literal_cast(escape));
7104    }
7105
7106    fn normalize_postgres_translate_for_tsql(
7107        mut translate: crate::expressions::Translate,
7108    ) -> Expression {
7109        let (Some(from), Some(to)) = (&translate.from_, &translate.to) else {
7110            return Expression::Translate(Box::new(translate));
7111        };
7112
7113        let (Some(from_value), Some(to_value)) = (
7114            Self::postgres_text_literal_value(from),
7115            Self::postgres_text_literal_value(to),
7116        ) else {
7117            return Expression::Translate(Box::new(translate));
7118        };
7119        let from_value = from_value.to_string();
7120        let to_value = to_value.to_string();
7121
7122        if from_value.chars().count() > to_value.chars().count() {
7123            if let Some(input) = Self::postgres_text_literal_value(&translate.this) {
7124                return Expression::string(Self::translate_postgres_literal(
7125                    input,
7126                    &from_value,
7127                    &to_value,
7128                ));
7129            }
7130            return Expression::Translate(Box::new(translate));
7131        }
7132
7133        translate.from_ = Some(Box::new(Self::strip_postgres_text_literal_cast(
7134            *translate.from_.expect("checked above"),
7135        )));
7136        let normalized_to = if from_value.chars().count() < to_value.chars().count() {
7137            Expression::string(
7138                to_value
7139                    .chars()
7140                    .take(from_value.chars().count())
7141                    .collect::<String>(),
7142            )
7143        } else {
7144            Self::strip_postgres_text_literal_cast(*translate.to.expect("checked above"))
7145        };
7146        translate.to = Some(Box::new(normalized_to));
7147        Expression::Translate(Box::new(translate))
7148    }
7149
7150    fn normalize_postgres_translate_function_for_tsql(mut function: Function) -> Expression {
7151        let from = Self::postgres_text_literal_value(&function.args[1]);
7152        let to = Self::postgres_text_literal_value(&function.args[2]);
7153        let (Some(from), Some(to)) = (from, to) else {
7154            return Expression::Function(Box::new(function));
7155        };
7156        let from = from.to_string();
7157        let to = to.to_string();
7158
7159        if from.chars().count() > to.chars().count() {
7160            if let Some(input) = Self::postgres_text_literal_value(&function.args[0]) {
7161                return Expression::string(Self::translate_postgres_literal(input, &from, &to));
7162            }
7163            return Expression::Function(Box::new(function));
7164        }
7165
7166        function.args[1] = Self::strip_postgres_text_literal_cast(function.args[1].clone());
7167        function.args[2] = if from.chars().count() < to.chars().count() {
7168            Expression::string(to.chars().take(from.chars().count()).collect::<String>())
7169        } else {
7170            Self::strip_postgres_text_literal_cast(function.args[2].clone())
7171        };
7172        Expression::Function(Box::new(function))
7173    }
7174
7175    fn translate_postgres_literal(input: &str, from: &str, to: &str) -> String {
7176        let from = from.chars().collect::<Vec<_>>();
7177        let to = to.chars().collect::<Vec<_>>();
7178        let mut output = String::with_capacity(input.len());
7179
7180        for ch in input.chars() {
7181            match from.iter().position(|candidate| *candidate == ch) {
7182                Some(index) if index < to.len() => output.push(to[index]),
7183                Some(_) => {}
7184                None => output.push(ch),
7185            }
7186        }
7187
7188        output
7189    }
7190
7191    fn postgres_tsql_unsupported_string_semantics(expr: &Expression) -> Option<&'static str> {
7192        match expr {
7193            Expression::Substring(substring) if substring.length.is_none() => {
7194                if Self::postgres_text_literal_value(&substring.start).is_some() {
7195                    Some("regular-expression SUBSTRING")
7196                } else {
7197                    Some("SUBSTRING without a statically numeric start position")
7198                }
7199            }
7200            Expression::Translate(translate) => {
7201                let from = translate
7202                    .from_
7203                    .as_deref()
7204                    .and_then(Self::postgres_text_literal_value);
7205                let to = translate
7206                    .to
7207                    .as_deref()
7208                    .and_then(Self::postgres_text_literal_value);
7209                match (from, to) {
7210                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7211                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7212                }
7213            }
7214            Expression::Function(function)
7215                if !function.quoted && function.name.eq_ignore_ascii_case("LIKE_ESCAPE") =>
7216            {
7217                Some("LIKE_ESCAPE helper outside a LIKE predicate")
7218            }
7219            Expression::Function(function)
7220                if !function.quoted
7221                    && function.name.eq_ignore_ascii_case("TRANSLATE")
7222                    && function.args.len() == 3 =>
7223            {
7224                let from = Self::postgres_text_literal_value(&function.args[1]);
7225                let to = Self::postgres_text_literal_value(&function.args[2]);
7226                match (from, to) {
7227                    (Some(from), Some(to)) if from.chars().count() == to.chars().count() => None,
7228                    _ => Some("TRANSLATE with source and replacement lengths that differ or cannot be proven equal"),
7229                }
7230            }
7231            Expression::Trim(trim)
7232                if trim
7233                    .characters
7234                    .as_ref()
7235                    .is_some_and(Self::is_unbounded_text_cast) =>
7236            {
7237                Some("TRIM character set cast to an unbounded text type")
7238            }
7239            Expression::Function(function)
7240                if !function.quoted
7241                    && matches!(
7242                        function.name.to_ascii_uppercase().as_str(),
7243                        "LTRIM" | "RTRIM"
7244                    )
7245                    && function.args.len() == 2
7246                    && Self::is_unbounded_text_cast(&function.args[1]) =>
7247            {
7248                Some("TRIM character set cast to an unbounded text type")
7249            }
7250            _ => None,
7251        }
7252    }
7253
7254    fn strip_postgres_text_literal_cast(expr: Expression) -> Expression {
7255        match expr {
7256            Expression::Cast(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::TryCast(cast)
7263                if Self::is_text_data_type(&cast.to)
7264                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7265            {
7266                Self::strip_postgres_text_literal_cast(cast.this)
7267            }
7268            Expression::SafeCast(cast)
7269                if Self::is_text_data_type(&cast.to)
7270                    && Self::postgres_text_literal_value(&cast.this).is_some() =>
7271            {
7272                Self::strip_postgres_text_literal_cast(cast.this)
7273            }
7274            Expression::Paren(mut paren)
7275                if Self::postgres_text_literal_value(&paren.this).is_some() =>
7276            {
7277                paren.this = Self::strip_postgres_text_literal_cast(paren.this);
7278                Expression::Paren(paren)
7279            }
7280            other => other,
7281        }
7282    }
7283
7284    fn postgres_text_literal_value(expr: &Expression) -> Option<&str> {
7285        match expr {
7286            Expression::Literal(literal) if literal.is_string() => Some(literal.value_str()),
7287            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast)
7288                if Self::is_text_data_type(&cast.to) =>
7289            {
7290                Self::postgres_text_literal_value(&cast.this)
7291            }
7292            Expression::Alias(alias) => Self::postgres_text_literal_value(&alias.this),
7293            Expression::Paren(paren) => Self::postgres_text_literal_value(&paren.this),
7294            _ => None,
7295        }
7296    }
7297
7298    fn is_text_data_type(data_type: &DataType) -> bool {
7299        match data_type {
7300            DataType::Char { .. }
7301            | DataType::VarChar { .. }
7302            | DataType::String { .. }
7303            | DataType::Text
7304            | DataType::TextWithLength { .. } => true,
7305            DataType::Custom { name } => {
7306                let base = name
7307                    .split_once('(')
7308                    .map_or(name.as_str(), |(base, _)| base)
7309                    .trim();
7310                matches!(
7311                    base.to_ascii_uppercase().as_str(),
7312                    "CHAR"
7313                        | "NCHAR"
7314                        | "VARCHAR"
7315                        | "NVARCHAR"
7316                        | "TEXT"
7317                        | "NTEXT"
7318                        | "STRING"
7319                        | "CHARACTER VARYING"
7320                )
7321            }
7322            _ => false,
7323        }
7324    }
7325
7326    fn is_unbounded_text_cast(expr: &Expression) -> bool {
7327        let data_type = match expr {
7328            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7329                &cast.to
7330            }
7331            Expression::Paren(paren) => return Self::is_unbounded_text_cast(&paren.this),
7332            _ => return false,
7333        };
7334
7335        match data_type {
7336            DataType::Text => true,
7337            DataType::VarChar { length: None, .. } | DataType::String { length: None } => true,
7338            DataType::Custom { name } => name.to_ascii_uppercase().contains("(MAX)"),
7339            _ => false,
7340        }
7341    }
7342
7343    fn is_explicitly_numeric_expression(expr: &Expression) -> bool {
7344        if expr.inferred_type().is_some_and(Self::is_numeric_data_type) {
7345            return true;
7346        }
7347
7348        match expr {
7349            Expression::Literal(literal) => literal.is_number(),
7350            Expression::Cast(cast) | Expression::TryCast(cast) | Expression::SafeCast(cast) => {
7351                Self::is_numeric_data_type(&cast.to)
7352            }
7353            Expression::Alias(alias) => Self::is_explicitly_numeric_expression(&alias.this),
7354            Expression::Paren(paren) => Self::is_explicitly_numeric_expression(&paren.this),
7355            Expression::Neg(unary) => Self::is_explicitly_numeric_expression(&unary.this),
7356            _ => false,
7357        }
7358    }
7359
7360    fn is_numeric_data_type(data_type: &DataType) -> bool {
7361        match data_type {
7362            DataType::TinyInt { .. }
7363            | DataType::SmallInt { .. }
7364            | DataType::Int { .. }
7365            | DataType::BigInt { .. }
7366            | DataType::Float { .. }
7367            | DataType::Double { .. }
7368            | DataType::Decimal { .. } => true,
7369            DataType::Custom { name } => {
7370                let base = name
7371                    .split_once('(')
7372                    .map_or(name.as_str(), |(base, _)| base)
7373                    .trim();
7374                matches!(
7375                    base.to_ascii_uppercase().as_str(),
7376                    "TINYINT"
7377                        | "SMALLINT"
7378                        | "INT"
7379                        | "INTEGER"
7380                        | "BIGINT"
7381                        | "DECIMAL"
7382                        | "NUMERIC"
7383                        | "REAL"
7384                        | "FLOAT"
7385                        | "MONEY"
7386                        | "SMALLMONEY"
7387                )
7388            }
7389            _ => false,
7390        }
7391    }
7392
7393    fn normalize_postgres_only_for_tsql(expr: Expression) -> Result<Expression> {
7394        transform_recursive(expr, &|e| match e {
7395            Expression::Table(mut table) if table.only => {
7396                table.only = false;
7397                Ok(Expression::Table(table))
7398            }
7399            other => Ok(other),
7400        })
7401    }
7402
7403    fn rewrite_postgres_json_array_elements_select_for_tsql(
7404        expr: Expression,
7405    ) -> Result<Expression> {
7406        let Expression::Select(select) = expr else {
7407            return Ok(expr);
7408        };
7409        let mut select = *select;
7410        if !Self::is_plain_single_projection_select(&select) {
7411            return Ok(Expression::Select(Box::new(select)));
7412        }
7413
7414        let Some(json_arg) =
7415            Self::postgres_json_array_elements_projection_arg(&select.expressions[0])
7416        else {
7417            return Ok(Expression::Select(Box::new(select)));
7418        };
7419
7420        select.expressions = vec![Expression::column("value")];
7421        select.from = Some(From {
7422            expressions: vec![Expression::OpenJSON(Box::new(
7423                crate::expressions::OpenJSON {
7424                    this: Box::new(json_arg),
7425                    path: None,
7426                    expressions: Vec::new(),
7427                },
7428            ))],
7429        });
7430
7431        Ok(Expression::Select(Box::new(select)))
7432    }
7433
7434    fn is_plain_single_projection_select(select: &crate::expressions::Select) -> bool {
7435        select.expressions.len() == 1
7436            && select.from.is_none()
7437            && select.joins.is_empty()
7438            && select.lateral_views.is_empty()
7439            && select.prewhere.is_none()
7440            && select.where_clause.is_none()
7441            && select.group_by.is_none()
7442            && select.having.is_none()
7443            && select.qualify.is_none()
7444            && select.order_by.is_none()
7445            && select.distribute_by.is_none()
7446            && select.cluster_by.is_none()
7447            && select.sort_by.is_none()
7448            && select.limit.is_none()
7449            && select.offset.is_none()
7450            && select.limit_by.is_none()
7451            && select.fetch.is_none()
7452            && !select.distinct
7453            && select.distinct_on.is_none()
7454            && select.top.is_none()
7455            && select.with.is_none()
7456            && select.sample.is_none()
7457            && select.into.is_none()
7458            && select.locks.is_empty()
7459            && select.for_xml.is_empty()
7460            && select.for_json.is_empty()
7461            && select.exclude.is_none()
7462    }
7463
7464    fn postgres_json_array_elements_projection_arg(expr: &Expression) -> Option<Expression> {
7465        match expr {
7466            Expression::Function(function)
7467                if Self::node_is_postgres_json_array_elements(expr) && function.args.len() == 1 =>
7468            {
7469                Some(function.args[0].clone())
7470            }
7471            Expression::Alias(alias) => {
7472                Self::postgres_json_array_elements_projection_arg(&alias.this)
7473            }
7474            _ => None,
7475        }
7476    }
7477
7478    fn normalize_postgres_type_function_casts(
7479        expr: Expression,
7480        target: DialectType,
7481    ) -> Result<Expression> {
7482        transform_recursive(expr, &|e| match e {
7483            Expression::Function(function) => {
7484                let mut function = *function;
7485                if function.args.len() == 1
7486                    && !function.distinct
7487                    && !function.quoted
7488                    && !function.use_bracket_syntax
7489                    && !function.name.contains('.')
7490                {
7491                    if let Some(to) = Self::postgres_type_function_data_type(&function.name) {
7492                        let this = function.args.remove(0);
7493                        let cast = Cast {
7494                            this,
7495                            to,
7496                            trailing_comments: function.trailing_comments,
7497                            double_colon_syntax: false,
7498                            format: None,
7499                            default: None,
7500                            inferred_type: function.inferred_type,
7501                        };
7502                        return Ok(
7503                            if matches!(target, DialectType::TSQL | DialectType::Fabric) {
7504                                normalization::rewrite_postgres_float_to_integer_cast(cast)
7505                            } else {
7506                                Expression::Cast(Box::new(cast))
7507                            },
7508                        );
7509                    }
7510                }
7511                Ok(Expression::Function(Box::new(function)))
7512            }
7513            _ => Ok(e),
7514        })
7515    }
7516
7517    fn node_is_postgres_type_function_cast(expr: &Expression) -> bool {
7518        matches!(
7519            expr,
7520            Expression::Function(function)
7521                if !function.quoted
7522                    && !function.use_bracket_syntax
7523                    && !function.name.contains('.')
7524                    && Self::postgres_type_function_data_type(&function.name).is_some()
7525        )
7526    }
7527
7528    fn postgres_type_function_data_type(name: &str) -> Option<DataType> {
7529        match name.to_ascii_uppercase().as_str() {
7530            "NUMERIC" | "DECIMAL" | "DEC" => Some(DataType::Decimal {
7531                precision: None,
7532                scale: None,
7533            }),
7534            "INT2" | "SMALLINT" => Some(DataType::SmallInt { length: None }),
7535            "INT4" | "INT" => Some(DataType::Int {
7536                length: None,
7537                integer_spelling: false,
7538            }),
7539            "INTEGER" => Some(DataType::Int {
7540                length: None,
7541                integer_spelling: true,
7542            }),
7543            "INT8" | "BIGINT" => Some(DataType::BigInt { length: None }),
7544            "FLOAT4" | "REAL" => Some(DataType::Float {
7545                precision: None,
7546                scale: None,
7547                real_spelling: true,
7548            }),
7549            "FLOAT8" => Some(DataType::Double {
7550                precision: None,
7551                scale: None,
7552            }),
7553            "BOOL" | "BOOLEAN" => Some(DataType::Boolean),
7554            "TEXT" => Some(DataType::Text),
7555            "VARCHAR" => Some(DataType::VarChar {
7556                length: None,
7557                parenthesized_length: false,
7558            }),
7559            "UUID" => Some(DataType::Uuid),
7560            _ => None,
7561        }
7562    }
7563
7564    fn rewrite_boolean_values_for_tsql(expr: Expression) -> Result<Expression> {
7565        match expr {
7566            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
7567            Expression::Subquery(mut subquery) => {
7568                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
7569                Ok(Expression::Subquery(subquery))
7570            }
7571            Expression::Union(mut union) => {
7572                let left = std::mem::replace(&mut union.left, Expression::null());
7573                let right = std::mem::replace(&mut union.right, Expression::null());
7574                union.left = Self::rewrite_boolean_values_for_tsql(left)?;
7575                union.right = Self::rewrite_boolean_values_for_tsql(right)?;
7576                if let Some(mut with) = union.with.take() {
7577                    with.ctes = with
7578                        .ctes
7579                        .into_iter()
7580                        .map(|mut cte| {
7581                            cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7582                            Ok(cte)
7583                        })
7584                        .collect::<Result<Vec<_>>>()?;
7585                    union.with = Some(with);
7586                }
7587                Ok(Expression::Union(union))
7588            }
7589            Expression::Intersect(mut intersect) => {
7590                let left = std::mem::replace(&mut intersect.left, Expression::null());
7591                let right = std::mem::replace(&mut intersect.right, Expression::null());
7592                intersect.left = Self::rewrite_boolean_values_for_tsql(left)?;
7593                intersect.right = Self::rewrite_boolean_values_for_tsql(right)?;
7594                Ok(Expression::Intersect(intersect))
7595            }
7596            Expression::Except(mut except) => {
7597                let left = std::mem::replace(&mut except.left, Expression::null());
7598                let right = std::mem::replace(&mut except.right, Expression::null());
7599                except.left = Self::rewrite_boolean_values_for_tsql(left)?;
7600                except.right = Self::rewrite_boolean_values_for_tsql(right)?;
7601                Ok(Expression::Except(except))
7602            }
7603            other => Self::rewrite_tsql_boolean_nested_contexts(other),
7604        }
7605    }
7606
7607    fn rewrite_postgres_row_value_equality_for_tsql(expr: Expression) -> Result<Expression> {
7608        transform_recursive(expr, &|e| match e {
7609            Expression::Eq(op) => {
7610                let op = *op;
7611                Ok(Self::postgres_row_value_equality_to_tsql_scalar(&op)
7612                    .unwrap_or_else(|| Expression::Eq(Box::new(op))))
7613            }
7614            other => Ok(other),
7615        })
7616    }
7617
7618    fn postgres_row_value_equality_to_tsql_scalar(op: &BinaryOp) -> Option<Expression> {
7619        let (row, query) =
7620            if Self::expr_is_row_value(&op.left) && Self::expr_is_subquery_like(&op.right) {
7621                (&op.left, &op.right)
7622            } else if Self::expr_is_row_value(&op.right) && Self::expr_is_subquery_like(&op.left) {
7623                (&op.right, &op.left)
7624            } else {
7625                return None;
7626            };
7627
7628        let row_values = Self::row_value_expressions(row)?;
7629        let projection_count = Self::subquery_projection_count(query)?;
7630        if row_values.is_empty() || row_values.len() != projection_count {
7631            return None;
7632        }
7633
7634        // Keep the complete original query behind a derived table. The outer scalar
7635        // SELECT therefore returns the same number of rows as the PostgreSQL
7636        // single-row subquery: zero rows stay NULL and multiple rows still raise a
7637        // scalar-subquery cardinality error in T-SQL/Fabric.
7638        let mut taken_names = HashSet::new();
7639        Self::collect_generated_alias_conflicts(row, &mut taken_names);
7640        Self::collect_generated_alias_conflicts(query, &mut taken_names);
7641
7642        let source_alias = find_new_name(&taken_names, "_polyglot_row");
7643        taken_names.insert(source_alias.to_ascii_lowercase());
7644        let column_aliases = (1..=row_values.len())
7645            .map(|index| {
7646                let name = find_new_name(&taken_names, &format!("_polyglot_row_value_{index}"));
7647                taken_names.insert(name.to_ascii_lowercase());
7648                Identifier::new(name)
7649            })
7650            .collect::<Vec<_>>();
7651        let source = Self::subquery_as_derived_table(
7652            query,
7653            Identifier::new(&source_alias),
7654            column_aliases.clone(),
7655        )?;
7656
7657        let mut equal_components = Vec::with_capacity(row_values.len());
7658        let mut unequal_components = Vec::with_capacity(row_values.len());
7659        for (column, row_value) in column_aliases.into_iter().zip(row_values) {
7660            let projected = Expression::qualified_column(source_alias.clone(), column.name);
7661            equal_components.push(Expression::Eq(Box::new(BinaryOp::new(
7662                projected.clone(),
7663                row_value.clone(),
7664            ))));
7665            unequal_components.push(Expression::Neq(Box::new(BinaryOp::new(
7666                projected, row_value,
7667            ))));
7668        }
7669
7670        let all_equal = equal_components
7671            .into_iter()
7672            .reduce(|left, right| Expression::And(Box::new(BinaryOp::new(left, right))))?;
7673        let any_unequal = unequal_components
7674            .into_iter()
7675            .reduce(|left, right| Expression::Or(Box::new(BinaryOp::new(left, right))))?;
7676        let comparison = Expression::Case(Box::new(Case {
7677            operand: None,
7678            whens: vec![
7679                (all_equal, Expression::number(1)),
7680                (any_unequal, Expression::number(0)),
7681            ],
7682            else_: Some(Expression::null()),
7683            comments: Vec::new(),
7684            inferred_type: None,
7685        }));
7686
7687        let scalar_select = Select::new().column(comparison).from(source);
7688        let scalar_subquery = Expression::Subquery(Box::new(Subquery {
7689            this: Expression::Select(Box::new(scalar_select)),
7690            alias: None,
7691            column_aliases: Vec::new(),
7692            alias_explicit_as: false,
7693            alias_keyword: None,
7694            order_by: None,
7695            limit: None,
7696            offset: None,
7697            distribute_by: None,
7698            sort_by: None,
7699            cluster_by: None,
7700            lateral: false,
7701            modifiers_inside: false,
7702            trailing_comments: Vec::new(),
7703            inferred_type: Some(DataType::Boolean),
7704        }));
7705
7706        Some(Expression::Cast(Box::new(Cast {
7707            this: scalar_subquery,
7708            to: DataType::Boolean,
7709            trailing_comments: Vec::new(),
7710            double_colon_syntax: false,
7711            format: None,
7712            default: None,
7713            inferred_type: Some(DataType::Boolean),
7714        })))
7715    }
7716
7717    fn row_value_expressions(expr: &Expression) -> Option<Vec<Expression>> {
7718        match expr {
7719            Expression::Tuple(tuple) => Some(tuple.expressions.clone()),
7720            Expression::Function(function) if function.name.eq_ignore_ascii_case("ROW") => {
7721                Some(function.args.clone())
7722            }
7723            Expression::Paren(paren) => Self::row_value_expressions(&paren.this),
7724            _ => None,
7725        }
7726    }
7727
7728    fn subquery_projection_count(expr: &Expression) -> Option<usize> {
7729        match expr {
7730            Expression::Select(select) => Some(select.expressions.len()),
7731            Expression::Subquery(subquery) => Self::subquery_projection_count(&subquery.this),
7732            Expression::Paren(paren) => Self::subquery_projection_count(&paren.this),
7733            _ => None,
7734        }
7735    }
7736
7737    fn subquery_as_derived_table(
7738        expr: &Expression,
7739        alias: Identifier,
7740        column_aliases: Vec<Identifier>,
7741    ) -> Option<Expression> {
7742        match expr.clone() {
7743            Expression::Subquery(mut subquery) => {
7744                subquery.alias = Some(alias);
7745                subquery.column_aliases = column_aliases;
7746                subquery.alias_explicit_as = true;
7747                subquery.alias_keyword = None;
7748                Some(Expression::Subquery(subquery))
7749            }
7750            Expression::Select(_) | Expression::Paren(_) => {
7751                Some(Expression::Subquery(Box::new(Subquery {
7752                    this: expr.clone(),
7753                    alias: Some(alias),
7754                    column_aliases,
7755                    alias_explicit_as: true,
7756                    alias_keyword: None,
7757                    order_by: None,
7758                    limit: None,
7759                    offset: None,
7760                    distribute_by: None,
7761                    sort_by: None,
7762                    cluster_by: None,
7763                    lateral: false,
7764                    modifiers_inside: false,
7765                    trailing_comments: Vec::new(),
7766                    inferred_type: None,
7767                })))
7768            }
7769            _ => None,
7770        }
7771    }
7772
7773    fn collect_generated_alias_conflicts(expr: &Expression, names: &mut HashSet<String>) {
7774        fn insert(names: &mut HashSet<String>, identifier: &Identifier) {
7775            if !identifier.name.is_empty() {
7776                names.insert(identifier.name.to_ascii_lowercase());
7777            }
7778        }
7779
7780        for node in expr.dfs() {
7781            match node {
7782                Expression::Identifier(identifier) => insert(names, identifier),
7783                Expression::Column(column) => {
7784                    insert(names, &column.name);
7785                    if let Some(table) = &column.table {
7786                        insert(names, table);
7787                    }
7788                }
7789                Expression::Table(table) => {
7790                    insert(names, &table.name);
7791                    if let Some(schema) = &table.schema {
7792                        insert(names, schema);
7793                    }
7794                    if let Some(catalog) = &table.catalog {
7795                        insert(names, catalog);
7796                    }
7797                    if let Some(alias) = &table.alias {
7798                        insert(names, alias);
7799                    }
7800                    for alias in &table.column_aliases {
7801                        insert(names, alias);
7802                    }
7803                }
7804                Expression::Alias(alias) => {
7805                    insert(names, &alias.alias);
7806                    for column_alias in &alias.column_aliases {
7807                        insert(names, column_alias);
7808                    }
7809                }
7810                Expression::Subquery(subquery) => {
7811                    if let Some(alias) = &subquery.alias {
7812                        insert(names, alias);
7813                    }
7814                    for column_alias in &subquery.column_aliases {
7815                        insert(names, column_alias);
7816                    }
7817                }
7818                Expression::Cte(cte) => {
7819                    insert(names, &cte.alias);
7820                    for column in &cte.columns {
7821                        insert(names, column);
7822                    }
7823                    for key in &cte.key_expressions {
7824                        insert(names, key);
7825                    }
7826                }
7827                Expression::Values(values) => {
7828                    if let Some(alias) = &values.alias {
7829                        insert(names, alias);
7830                    }
7831                    for column_alias in &values.column_aliases {
7832                        insert(names, column_alias);
7833                    }
7834                }
7835                Expression::Unnest(unnest) => {
7836                    if let Some(alias) = &unnest.alias {
7837                        insert(names, alias);
7838                    }
7839                    if let Some(offset_alias) = &unnest.offset_alias {
7840                        insert(names, offset_alias);
7841                    }
7842                }
7843                _ => {}
7844            }
7845        }
7846    }
7847
7848    fn rewrite_postgres_format_for_tsql(
7849        expr: Expression,
7850        target: DialectType,
7851    ) -> Result<Expression> {
7852        transform_recursive(expr, &|e| match e {
7853            Expression::Function(f) if f.name.eq_ignore_ascii_case("FORMAT") => {
7854                Self::postgres_format_function_to_tsql(*f, target)
7855            }
7856            other => Ok(other),
7857        })
7858    }
7859
7860    fn postgres_format_function_to_tsql(f: Function, target: DialectType) -> Result<Expression> {
7861        let Some(format_expr) = f.args.first() else {
7862            return Err(Self::unsupported_postgres_format_for_tsql(
7863                target,
7864                "missing format string",
7865            ));
7866        };
7867
7868        let format = match format_expr {
7869            Expression::Literal(lit) if lit.is_string() => lit.value_str(),
7870            _ => {
7871                return Err(Self::unsupported_postgres_format_for_tsql(
7872                    target,
7873                    "dynamic format strings",
7874                ))
7875            }
7876        };
7877
7878        let value_args = &f.args[1..];
7879        let mut arg_index = 0usize;
7880        let mut literal = String::new();
7881        let mut segments = Vec::new();
7882        let mut chars = format.chars();
7883
7884        while let Some(ch) = chars.next() {
7885            if ch != '%' {
7886                literal.push(ch);
7887                continue;
7888            }
7889
7890            let Some(specifier) = chars.next() else {
7891                return Err(Self::unsupported_postgres_format_for_tsql(
7892                    target,
7893                    "unterminated format specifier",
7894                ));
7895            };
7896
7897            match specifier {
7898                '%' => literal.push('%'),
7899                's' => {
7900                    if !literal.is_empty() {
7901                        segments.push(Expression::string(std::mem::take(&mut literal)));
7902                    }
7903                    let Some(arg) = value_args.get(arg_index) else {
7904                        return Err(Self::unsupported_postgres_format_for_tsql(
7905                            target,
7906                            "not enough arguments",
7907                        ));
7908                    };
7909                    segments.push(arg.clone());
7910                    arg_index += 1;
7911                }
7912                other => {
7913                    return Err(Self::unsupported_postgres_format_for_tsql(
7914                        target,
7915                        format!("unsupported format specifier %{other}"),
7916                    ))
7917                }
7918            }
7919        }
7920
7921        if !literal.is_empty() {
7922            segments.push(Expression::string(literal));
7923        }
7924
7925        if arg_index != value_args.len() {
7926            return Err(Self::unsupported_postgres_format_for_tsql(
7927                target,
7928                "unused format arguments",
7929            ));
7930        }
7931
7932        Ok(Self::postgres_format_segments_to_tsql_concat(segments))
7933    }
7934
7935    fn postgres_format_segments_to_tsql_concat(mut segments: Vec<Expression>) -> Expression {
7936        if segments.is_empty() {
7937            return Expression::string("");
7938        }
7939
7940        if segments.len() == 1 {
7941            let only = segments.pop().expect("one segment");
7942            if matches!(&only, Expression::Literal(lit) if lit.is_string()) {
7943                return only;
7944            }
7945
7946            return Expression::Function(Box::new(Function::new(
7947                "CONCAT".to_string(),
7948                vec![only, Expression::string("")],
7949            )));
7950        }
7951
7952        Expression::Function(Box::new(Function::new("CONCAT".to_string(), segments)))
7953    }
7954
7955    fn unsupported_postgres_format_for_tsql(
7956        target: DialectType,
7957        reason: impl Into<String>,
7958    ) -> crate::error::Error {
7959        crate::error::Error::unsupported(
7960            format!("PostgreSQL format() ({})", reason.into()),
7961            target.to_string(),
7962        )
7963    }
7964
7965    fn rewrite_boolean_values_in_tsql_select(
7966        mut select: Box<crate::expressions::Select>,
7967    ) -> Result<Expression> {
7968        if let Some(mut with) = select.with.take() {
7969            with.ctes = with
7970                .ctes
7971                .into_iter()
7972                .map(|mut cte| {
7973                    cte.this = Self::rewrite_boolean_values_for_tsql(cte.this)?;
7974                    Ok(cte)
7975                })
7976                .collect::<Result<Vec<_>>>()?;
7977            select.with = Some(with);
7978        }
7979
7980        select.expressions = select
7981            .expressions
7982            .into_iter()
7983            .map(Self::rewrite_tsql_boolean_scalar_value)
7984            .collect::<Result<Vec<_>>>()?;
7985
7986        if let Some(mut from) = select.from.take() {
7987            from.expressions = from
7988                .expressions
7989                .into_iter()
7990                .map(Self::rewrite_tsql_boolean_nested_contexts)
7991                .collect::<Result<Vec<_>>>()?;
7992            select.from = Some(from);
7993        }
7994
7995        select.joins = select
7996            .joins
7997            .into_iter()
7998            .map(|mut join| {
7999                join.this = Self::rewrite_tsql_boolean_nested_contexts(join.this)?;
8000                if let Some(on) = join.on.take() {
8001                    join.on = Some(Self::rewrite_tsql_boolean_predicate_context(on)?);
8002                }
8003                if let Some(match_condition) = join.match_condition.take() {
8004                    join.match_condition = Some(Self::rewrite_tsql_boolean_predicate_context(
8005                        match_condition,
8006                    )?);
8007                }
8008                join.pivots = join
8009                    .pivots
8010                    .into_iter()
8011                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8012                    .collect::<Result<Vec<_>>>()?;
8013                Ok(join)
8014            })
8015            .collect::<Result<Vec<_>>>()?;
8016
8017        select.lateral_views = select
8018            .lateral_views
8019            .into_iter()
8020            .map(|mut lateral_view| {
8021                lateral_view.this = Self::rewrite_tsql_boolean_nested_contexts(lateral_view.this)?;
8022                Ok(lateral_view)
8023            })
8024            .collect::<Result<Vec<_>>>()?;
8025
8026        if let Some(prewhere) = select.prewhere.take() {
8027            select.prewhere = Some(Self::rewrite_tsql_boolean_predicate_context(prewhere)?);
8028        }
8029
8030        if let Some(mut where_clause) = select.where_clause.take() {
8031            where_clause.this = Self::rewrite_tsql_boolean_predicate_context(where_clause.this)?;
8032            select.where_clause = Some(where_clause);
8033        }
8034
8035        if let Some(mut group_by) = select.group_by.take() {
8036            group_by.expressions = group_by
8037                .expressions
8038                .into_iter()
8039                .map(Self::rewrite_tsql_boolean_scalar_value)
8040                .collect::<Result<Vec<_>>>()?;
8041            select.group_by = Some(group_by);
8042        }
8043
8044        if let Some(mut having) = select.having.take() {
8045            having.this = Self::rewrite_tsql_boolean_predicate_context(having.this)?;
8046            select.having = Some(having);
8047        }
8048
8049        if let Some(mut qualify) = select.qualify.take() {
8050            qualify.this = Self::rewrite_tsql_boolean_predicate_context(qualify.this)?;
8051            select.qualify = Some(qualify);
8052        }
8053
8054        if let Some(mut order_by) = select.order_by.take() {
8055            order_by.expressions = Self::rewrite_tsql_boolean_ordered_values(order_by.expressions)?;
8056            select.order_by = Some(order_by);
8057        }
8058
8059        if let Some(mut distribute_by) = select.distribute_by.take() {
8060            distribute_by.expressions = distribute_by
8061                .expressions
8062                .into_iter()
8063                .map(Self::rewrite_tsql_boolean_scalar_value)
8064                .collect::<Result<Vec<_>>>()?;
8065            select.distribute_by = Some(distribute_by);
8066        }
8067
8068        if let Some(mut cluster_by) = select.cluster_by.take() {
8069            cluster_by.expressions =
8070                Self::rewrite_tsql_boolean_ordered_values(cluster_by.expressions)?;
8071            select.cluster_by = Some(cluster_by);
8072        }
8073
8074        if let Some(mut sort_by) = select.sort_by.take() {
8075            sort_by.expressions = Self::rewrite_tsql_boolean_ordered_values(sort_by.expressions)?;
8076            select.sort_by = Some(sort_by);
8077        }
8078
8079        if let Some(limit_by) = select.limit_by.take() {
8080            select.limit_by = Some(
8081                limit_by
8082                    .into_iter()
8083                    .map(Self::rewrite_tsql_boolean_scalar_value)
8084                    .collect::<Result<Vec<_>>>()?,
8085            );
8086        }
8087
8088        if let Some(distinct_on) = select.distinct_on.take() {
8089            select.distinct_on = Some(
8090                distinct_on
8091                    .into_iter()
8092                    .map(Self::rewrite_tsql_boolean_scalar_value)
8093                    .collect::<Result<Vec<_>>>()?,
8094            );
8095        }
8096
8097        if let Some(mut sample) = select.sample.take() {
8098            sample.size = Self::rewrite_tsql_boolean_nested_contexts(sample.size)?;
8099            if let Some(offset) = sample.offset.take() {
8100                sample.offset = Some(Self::rewrite_tsql_boolean_nested_contexts(offset)?);
8101            }
8102            if let Some(bucket_numerator) = sample.bucket_numerator.take() {
8103                sample.bucket_numerator = Some(Box::new(
8104                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_numerator)?,
8105                ));
8106            }
8107            if let Some(bucket_denominator) = sample.bucket_denominator.take() {
8108                sample.bucket_denominator = Some(Box::new(
8109                    Self::rewrite_tsql_boolean_nested_contexts(*bucket_denominator)?,
8110                ));
8111            }
8112            if let Some(bucket_field) = sample.bucket_field.take() {
8113                sample.bucket_field = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8114                    *bucket_field,
8115                )?));
8116            }
8117            select.sample = Some(sample);
8118        }
8119
8120        if let Some(settings) = select.settings.take() {
8121            select.settings = Some(
8122                settings
8123                    .into_iter()
8124                    .map(Self::rewrite_tsql_boolean_nested_contexts)
8125                    .collect::<Result<Vec<_>>>()?,
8126            );
8127        }
8128
8129        if let Some(format) = select.format.take() {
8130            select.format = Some(Self::rewrite_tsql_boolean_nested_contexts(format)?);
8131        }
8132
8133        if let Some(mut windows) = select.windows.take() {
8134            for window in windows.iter_mut() {
8135                Self::rewrite_tsql_boolean_over_values(&mut window.spec)?;
8136            }
8137            select.windows = Some(windows);
8138        }
8139
8140        Ok(Expression::Select(select))
8141    }
8142
8143    fn normalize_postgres_boolean_semantics_for_tsql(expr: Expression) -> Result<Expression> {
8144        transform_recursive(expr, &|e| match e {
8145            Expression::Function(function)
8146                if function.args.len() == 2
8147                    && (function.name.eq_ignore_ascii_case("BOOLEQ")
8148                        || function.name.eq_ignore_ascii_case("BOOLNE")) =>
8149            {
8150                let is_equal = function.name.eq_ignore_ascii_case("BOOLEQ");
8151                let mut args = function.args.into_iter();
8152                let op = BinaryOp {
8153                    left: args.next().expect("checked boolean operator arity"),
8154                    right: args.next().expect("checked boolean operator arity"),
8155                    left_comments: Vec::new(),
8156                    operator_comments: Vec::new(),
8157                    trailing_comments: function.trailing_comments,
8158                    inferred_type: None,
8159                };
8160                if is_equal {
8161                    Ok(Expression::Eq(Box::new(op)))
8162                } else {
8163                    Ok(Expression::Neq(Box::new(op)))
8164                }
8165            }
8166            Expression::Cast(cast)
8167                if matches!(cast.to, DataType::Text)
8168                    && Self::is_known_postgres_boolean_expression(&cast.this) =>
8169            {
8170                Ok(Self::postgres_boolean_text_value(cast.this))
8171            }
8172            other => Ok(other),
8173        })
8174    }
8175
8176    fn is_known_postgres_boolean_expression(expr: &Expression) -> bool {
8177        match expr {
8178            Expression::Boolean(_) => true,
8179            Expression::Cast(cast) => matches!(cast.to, DataType::Boolean),
8180            Expression::Paren(paren) => Self::is_known_postgres_boolean_expression(&paren.this),
8181            other => Self::is_tsql_boolean_value_expression(other),
8182        }
8183    }
8184
8185    fn postgres_boolean_text_value(predicate: Expression) -> Expression {
8186        if let Expression::Boolean(boolean) = predicate {
8187            return Expression::string(if boolean.value { "true" } else { "false" });
8188        }
8189
8190        Self::three_valued_boolean_case(
8191            predicate,
8192            Expression::string("true"),
8193            Expression::string("false"),
8194        )
8195    }
8196
8197    fn rewrite_tsql_boolean_scalar_value(expr: Expression) -> Result<Expression> {
8198        if let Expression::Boolean(boolean) = expr {
8199            return Ok(Expression::Cast(Box::new(Cast {
8200                this: Expression::Boolean(boolean),
8201                to: DataType::Boolean,
8202                trailing_comments: Vec::new(),
8203                double_colon_syntax: false,
8204                format: None,
8205                default: None,
8206                inferred_type: None,
8207            })));
8208        }
8209
8210        if Self::is_tsql_boolean_value_expression(&expr) {
8211            // Tuple/subquery equality currently lowers only its positive branch to EXISTS.
8212            // Keep its established two-way scalar fallback until that rewrite models UNKNOWN.
8213            let can_be_unknown = Self::tsql_boolean_expression_can_be_unknown(&expr)
8214                && !Self::node_is_row_value_subquery_comparison(&expr);
8215            let predicate = Self::rewrite_tsql_boolean_predicate_context(expr)?;
8216            return Ok(Self::tsql_boolean_value_case(predicate, can_be_unknown));
8217        }
8218
8219        match expr {
8220            Expression::Alias(mut alias) => {
8221                alias.this = Self::rewrite_tsql_boolean_scalar_value(alias.this)?;
8222                Ok(Expression::Alias(alias))
8223            }
8224            Expression::Paren(mut paren) => {
8225                paren.this = Self::rewrite_tsql_boolean_scalar_value(paren.this)?;
8226                Ok(Expression::Paren(paren))
8227            }
8228            Expression::Cast(mut cast) => {
8229                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8230                if let Some(format) = cast.format.take() {
8231                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8232                        *format,
8233                    )?));
8234                }
8235                if let Some(default) = cast.default.take() {
8236                    cast.default =
8237                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8238                }
8239                Ok(Expression::Cast(cast))
8240            }
8241            Expression::TryCast(mut cast) => {
8242                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8243                if let Some(format) = cast.format.take() {
8244                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8245                        *format,
8246                    )?));
8247                }
8248                if let Some(default) = cast.default.take() {
8249                    cast.default =
8250                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8251                }
8252                Ok(Expression::TryCast(cast))
8253            }
8254            Expression::SafeCast(mut cast) => {
8255                cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8256                if let Some(format) = cast.format.take() {
8257                    cast.format = Some(Box::new(Self::rewrite_tsql_boolean_nested_contexts(
8258                        *format,
8259                    )?));
8260                }
8261                if let Some(default) = cast.default.take() {
8262                    cast.default =
8263                        Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*default)?));
8264                }
8265                Ok(Expression::SafeCast(cast))
8266            }
8267            Expression::Case(mut case) => {
8268                let is_simple_case = case.operand.is_some();
8269                if let Some(operand) = case.operand.take() {
8270                    case.operand = Some(Self::rewrite_tsql_boolean_scalar_value(operand)?);
8271                }
8272                case.whens = case
8273                    .whens
8274                    .into_iter()
8275                    .map(|(condition, result)| {
8276                        let condition = if is_simple_case {
8277                            Self::rewrite_tsql_boolean_scalar_value(condition)?
8278                        } else {
8279                            Self::rewrite_tsql_boolean_predicate_context(condition)?
8280                        };
8281                        Ok((condition, Self::rewrite_tsql_boolean_scalar_value(result)?))
8282                    })
8283                    .collect::<Result<Vec<_>>>()?;
8284                if let Some(else_) = case.else_.take() {
8285                    case.else_ = Some(Self::rewrite_tsql_boolean_scalar_value(else_)?);
8286                }
8287                Ok(Expression::Case(case))
8288            }
8289            Expression::IfFunc(mut if_func) => {
8290                if_func.condition =
8291                    Self::rewrite_tsql_boolean_predicate_context(if_func.condition)?;
8292                if_func.true_value = Self::rewrite_tsql_boolean_scalar_value(if_func.true_value)?;
8293                if let Some(false_value) = if_func.false_value.take() {
8294                    if_func.false_value =
8295                        Some(Self::rewrite_tsql_boolean_scalar_value(false_value)?);
8296                }
8297                Ok(Expression::IfFunc(if_func))
8298            }
8299            Expression::WindowFunction(mut window_function) => {
8300                window_function.this =
8301                    Self::rewrite_tsql_boolean_nested_contexts(window_function.this)?;
8302                Self::rewrite_tsql_boolean_over_values(&mut window_function.over)?;
8303                if let Some(mut keep) = window_function.keep.take() {
8304                    keep.order_by = Self::rewrite_tsql_boolean_ordered_values(keep.order_by)?;
8305                    window_function.keep = Some(keep);
8306                }
8307                Ok(Expression::WindowFunction(window_function))
8308            }
8309            Expression::WithinGroup(mut within_group) => {
8310                within_group.this = Self::rewrite_tsql_boolean_nested_contexts(within_group.this)?;
8311                within_group.order_by =
8312                    Self::rewrite_tsql_boolean_ordered_values(within_group.order_by)?;
8313                Ok(Expression::WithinGroup(within_group))
8314            }
8315            Expression::Subquery(mut subquery) => {
8316                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8317                Ok(Expression::Subquery(subquery))
8318            }
8319            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8320            other => Self::rewrite_tsql_boolean_nested_contexts(other),
8321        }
8322    }
8323
8324    fn rewrite_tsql_boolean_predicate_context(expr: Expression) -> Result<Expression> {
8325        let expr = Self::rewrite_tsql_boolean_nested_contexts(expr)?;
8326        Ok(crate::transforms::ensure_bool_condition(expr))
8327    }
8328
8329    fn rewrite_tsql_boolean_nested_contexts(expr: Expression) -> Result<Expression> {
8330        transform_recursive(expr, &|e| match e {
8331            Expression::Select(select) => Self::rewrite_boolean_values_in_tsql_select(select),
8332            Expression::Subquery(mut subquery) => {
8333                subquery.this = Self::rewrite_boolean_values_for_tsql(subquery.this)?;
8334                Ok(Expression::Subquery(subquery))
8335            }
8336            Expression::Union(_) | Expression::Intersect(_) | Expression::Except(_) => {
8337                Self::rewrite_boolean_values_for_tsql(e)
8338            }
8339            other => Self::rewrite_tsql_boolean_cast_operand(other),
8340        })
8341    }
8342
8343    fn rewrite_tsql_boolean_cast_operand(expr: Expression) -> Result<Expression> {
8344        macro_rules! rewrite_cast_operand {
8345            ($variant:ident, $cast:expr) => {{
8346                let mut cast = $cast;
8347                if Self::is_tsql_boolean_value_expression(&cast.this) {
8348                    cast.this = Self::rewrite_tsql_boolean_scalar_value(cast.this)?;
8349                }
8350                Ok(Expression::$variant(cast))
8351            }};
8352        }
8353
8354        match expr {
8355            Expression::Cast(cast) => rewrite_cast_operand!(Cast, cast),
8356            Expression::TryCast(cast) => rewrite_cast_operand!(TryCast, cast),
8357            Expression::SafeCast(cast) => rewrite_cast_operand!(SafeCast, cast),
8358            other => Ok(other),
8359        }
8360    }
8361
8362    fn rewrite_tsql_boolean_ordered_values(
8363        ordered: Vec<crate::expressions::Ordered>,
8364    ) -> Result<Vec<crate::expressions::Ordered>> {
8365        ordered
8366            .into_iter()
8367            .map(|mut ordered| {
8368                ordered.this = Self::rewrite_tsql_boolean_scalar_value(ordered.this)?;
8369                if let Some(with_fill) = ordered.with_fill.take() {
8370                    ordered.with_fill = Some(Box::new(
8371                        Self::rewrite_tsql_boolean_with_fill_values(*with_fill)?,
8372                    ));
8373                }
8374                Ok(ordered)
8375            })
8376            .collect()
8377    }
8378
8379    fn rewrite_tsql_boolean_with_fill_values(
8380        mut with_fill: crate::expressions::WithFill,
8381    ) -> Result<crate::expressions::WithFill> {
8382        if let Some(from) = with_fill.from_.take() {
8383            with_fill.from_ = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*from)?));
8384        }
8385        if let Some(to) = with_fill.to.take() {
8386            with_fill.to = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*to)?));
8387        }
8388        if let Some(step) = with_fill.step.take() {
8389            with_fill.step = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(*step)?));
8390        }
8391        if let Some(staleness) = with_fill.staleness.take() {
8392            with_fill.staleness = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8393                *staleness,
8394            )?));
8395        }
8396        if let Some(interpolate) = with_fill.interpolate.take() {
8397            with_fill.interpolate = Some(Box::new(Self::rewrite_tsql_boolean_scalar_value(
8398                *interpolate,
8399            )?));
8400        }
8401        Ok(with_fill)
8402    }
8403
8404    fn rewrite_tsql_boolean_over_values(over: &mut crate::expressions::Over) -> Result<()> {
8405        over.partition_by = std::mem::take(&mut over.partition_by)
8406            .into_iter()
8407            .map(Self::rewrite_tsql_boolean_scalar_value)
8408            .collect::<Result<Vec<_>>>()?;
8409        over.order_by =
8410            Self::rewrite_tsql_boolean_ordered_values(std::mem::take(&mut over.order_by))?;
8411        Ok(())
8412    }
8413
8414    fn is_tsql_boolean_value_expression(expr: &Expression) -> bool {
8415        match expr {
8416            Expression::Paren(paren) => Self::is_tsql_boolean_value_expression(&paren.this),
8417            Expression::Eq(_)
8418            | Expression::Neq(_)
8419            | Expression::Lt(_)
8420            | Expression::Lte(_)
8421            | Expression::Gt(_)
8422            | Expression::Gte(_)
8423            | Expression::Is(_)
8424            | Expression::IsNull(_)
8425            | Expression::IsTrue(_)
8426            | Expression::IsFalse(_)
8427            | Expression::Like(_)
8428            | Expression::ILike(_)
8429            | Expression::StartsWith(_)
8430            | Expression::SimilarTo(_)
8431            | Expression::Glob(_)
8432            | Expression::RegexpLike(_)
8433            | Expression::In(_)
8434            | Expression::Between(_)
8435            | Expression::Exists(_)
8436            | Expression::And(_)
8437            | Expression::Or(_)
8438            | Expression::Not(_)
8439            | Expression::Any(_)
8440            | Expression::All(_)
8441            | Expression::NullSafeEq(_)
8442            | Expression::NullSafeNeq(_)
8443            | Expression::EqualNull(_) => true,
8444            _ => false,
8445        }
8446    }
8447
8448    fn tsql_boolean_expression_can_be_unknown(expr: &Expression) -> bool {
8449        match expr {
8450            Expression::Boolean(_)
8451            | Expression::IsNull(_)
8452            | Expression::IsTrue(_)
8453            | Expression::IsFalse(_)
8454            | Expression::Exists(_)
8455            | Expression::NullSafeEq(_)
8456            | Expression::NullSafeNeq(_)
8457            | Expression::EqualNull(_) => false,
8458            Expression::Paren(paren) => Self::tsql_boolean_expression_can_be_unknown(&paren.this),
8459            Expression::Not(op) => Self::tsql_boolean_expression_can_be_unknown(&op.this),
8460            Expression::And(op) | Expression::Or(op) => {
8461                Self::tsql_boolean_expression_can_be_unknown(&op.left)
8462                    || Self::tsql_boolean_expression_can_be_unknown(&op.right)
8463            }
8464            _ => true,
8465        }
8466    }
8467
8468    fn tsql_boolean_value_case(predicate: Expression, can_be_unknown: bool) -> Expression {
8469        let case = if can_be_unknown {
8470            Self::three_valued_boolean_case(predicate, Expression::number(1), Expression::number(0))
8471        } else {
8472            Expression::Case(Box::new(crate::expressions::Case {
8473                operand: None,
8474                whens: vec![(predicate, Expression::number(1))],
8475                else_: Some(Expression::number(0)),
8476                comments: Vec::new(),
8477                inferred_type: None,
8478            }))
8479        };
8480
8481        Expression::Cast(Box::new(Cast {
8482            this: case,
8483            to: DataType::Boolean,
8484            trailing_comments: Vec::new(),
8485            double_colon_syntax: false,
8486            format: None,
8487            default: None,
8488            inferred_type: None,
8489        }))
8490    }
8491
8492    fn three_valued_boolean_case(
8493        predicate: Expression,
8494        true_value: Expression,
8495        false_value: Expression,
8496    ) -> Expression {
8497        let false_operand = if matches!(predicate, Expression::And(_) | Expression::Or(_)) {
8498            Expression::Paren(Box::new(crate::expressions::Paren {
8499                this: predicate.clone(),
8500                trailing_comments: Vec::new(),
8501            }))
8502        } else {
8503            predicate.clone()
8504        };
8505        let false_predicate = Expression::Not(Box::new(crate::expressions::UnaryOp {
8506            this: false_operand,
8507            inferred_type: None,
8508        }));
8509
8510        Expression::Case(Box::new(crate::expressions::Case {
8511            operand: None,
8512            whens: vec![(predicate, true_value), (false_predicate, false_value)],
8513            else_: Some(Expression::null()),
8514            comments: Vec::new(),
8515            inferred_type: None,
8516        }))
8517    }
8518
8519    fn rewrite_aggregate_filters_for_tsql(expr: Expression) -> Result<Expression> {
8520        transform_recursive(expr, &|e| Self::rewrite_aggregate_filter_for_tsql(e))
8521    }
8522
8523    fn rewrite_aggregate_filter_for_tsql(expr: Expression) -> Result<Expression> {
8524        macro_rules! rewrite_agg_filter {
8525            ($variant:ident, $agg:expr) => {{
8526                let mut agg = $agg;
8527                if let Some(filter) = agg.filter.take() {
8528                    let this = std::mem::replace(&mut agg.this, Expression::null());
8529                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8530                }
8531                Ok(Expression::$variant(agg))
8532            }};
8533        }
8534
8535        match expr {
8536            Expression::Filter(filter) => {
8537                let condition = match *filter.expression {
8538                    Expression::Where(where_) => where_.this,
8539                    other => other,
8540                };
8541                Ok(Self::push_filter_into_tsql_aggregate(
8542                    *filter.this,
8543                    condition,
8544                ))
8545            }
8546            Expression::AggregateFunction(mut agg) => {
8547                if let Some(filter) = agg.filter.take() {
8548                    Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8549                }
8550                Ok(Expression::AggregateFunction(agg))
8551            }
8552            Expression::Count(mut count) => {
8553                if let Some(filter) = count.filter.take() {
8554                    let value = if count.star {
8555                        Expression::number(1)
8556                    } else {
8557                        count.this.take().unwrap_or_else(|| Expression::number(1))
8558                    };
8559                    count.star = false;
8560                    count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8561                }
8562                Ok(Expression::Count(count))
8563            }
8564            Expression::Sum(agg) => rewrite_agg_filter!(Sum, agg),
8565            Expression::Avg(agg) => rewrite_agg_filter!(Avg, agg),
8566            Expression::Min(agg) => rewrite_agg_filter!(Min, agg),
8567            Expression::Max(agg) => rewrite_agg_filter!(Max, agg),
8568            Expression::ArrayAgg(agg) => rewrite_agg_filter!(ArrayAgg, agg),
8569            Expression::CountIf(agg) => Ok(Expression::CountIf(agg)),
8570            Expression::Stddev(agg) => rewrite_agg_filter!(Stddev, agg),
8571            Expression::StddevPop(agg) => rewrite_agg_filter!(StddevPop, agg),
8572            Expression::StddevSamp(agg) => rewrite_agg_filter!(StddevSamp, agg),
8573            Expression::Variance(agg) => rewrite_agg_filter!(Variance, agg),
8574            Expression::VarPop(agg) => rewrite_agg_filter!(VarPop, agg),
8575            Expression::VarSamp(agg) => rewrite_agg_filter!(VarSamp, agg),
8576            Expression::Median(agg) => rewrite_agg_filter!(Median, agg),
8577            Expression::Mode(agg) => rewrite_agg_filter!(Mode, agg),
8578            Expression::First(agg) => rewrite_agg_filter!(First, agg),
8579            Expression::Last(agg) => rewrite_agg_filter!(Last, agg),
8580            Expression::AnyValue(agg) => rewrite_agg_filter!(AnyValue, agg),
8581            Expression::ApproxDistinct(agg) => rewrite_agg_filter!(ApproxDistinct, agg),
8582            Expression::ApproxCountDistinct(agg) => {
8583                rewrite_agg_filter!(ApproxCountDistinct, agg)
8584            }
8585            Expression::LogicalAnd(agg) => rewrite_agg_filter!(LogicalAnd, agg),
8586            Expression::LogicalOr(agg) => rewrite_agg_filter!(LogicalOr, agg),
8587            Expression::Skewness(agg) => rewrite_agg_filter!(Skewness, agg),
8588            Expression::ArrayConcatAgg(agg) => rewrite_agg_filter!(ArrayConcatAgg, agg),
8589            Expression::ArrayUniqueAgg(agg) => rewrite_agg_filter!(ArrayUniqueAgg, agg),
8590            Expression::BoolXorAgg(agg) => rewrite_agg_filter!(BoolXorAgg, agg),
8591            Expression::BitwiseAndAgg(agg) => rewrite_agg_filter!(BitwiseAndAgg, agg),
8592            Expression::BitwiseOrAgg(agg) => rewrite_agg_filter!(BitwiseOrAgg, agg),
8593            Expression::BitwiseXorAgg(agg) => rewrite_agg_filter!(BitwiseXorAgg, agg),
8594            Expression::StringAgg(mut agg) => {
8595                if let Some(filter) = agg.filter.take() {
8596                    let this = std::mem::replace(&mut agg.this, Expression::null());
8597                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8598                }
8599                Ok(Expression::StringAgg(agg))
8600            }
8601            Expression::GroupConcat(mut agg) => {
8602                if let Some(filter) = agg.filter.take() {
8603                    let this = std::mem::replace(&mut agg.this, Expression::null());
8604                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8605                }
8606                Ok(Expression::GroupConcat(agg))
8607            }
8608            Expression::ListAgg(mut agg) => {
8609                if let Some(filter) = agg.filter.take() {
8610                    let this = std::mem::replace(&mut agg.this, Expression::null());
8611                    agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8612                }
8613                Ok(Expression::ListAgg(agg))
8614            }
8615            Expression::WithinGroup(mut within_group) => {
8616                within_group.this = Self::rewrite_aggregate_filters_for_tsql(within_group.this)?;
8617                Ok(Expression::WithinGroup(within_group))
8618            }
8619            other => Ok(other),
8620        }
8621    }
8622
8623    fn push_filter_into_tsql_aggregate(expr: Expression, filter: Expression) -> Expression {
8624        macro_rules! push_agg_filter {
8625            ($variant:ident, $agg:expr) => {{
8626                let mut agg = $agg;
8627                let this = std::mem::replace(&mut agg.this, Expression::null());
8628                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8629                agg.filter = None;
8630                Expression::$variant(agg)
8631            }};
8632        }
8633
8634        match expr {
8635            Expression::AggregateFunction(mut agg) => {
8636                Self::rewrite_generic_aggregate_filter_for_tsql(&mut agg, filter);
8637                Expression::AggregateFunction(agg)
8638            }
8639            Expression::Count(mut count) => {
8640                let value = if count.star {
8641                    Expression::number(1)
8642                } else {
8643                    count.this.take().unwrap_or_else(|| Expression::number(1))
8644                };
8645                count.star = false;
8646                count.filter = None;
8647                count.this = Some(Self::conditional_aggregate_value_for_tsql(filter, value));
8648                Expression::Count(count)
8649            }
8650            Expression::Sum(agg) => push_agg_filter!(Sum, agg),
8651            Expression::Avg(agg) => push_agg_filter!(Avg, agg),
8652            Expression::Min(agg) => push_agg_filter!(Min, agg),
8653            Expression::Max(agg) => push_agg_filter!(Max, agg),
8654            Expression::ArrayAgg(agg) => push_agg_filter!(ArrayAgg, agg),
8655            Expression::CountIf(mut agg) => {
8656                agg.filter = Some(filter);
8657                Expression::CountIf(agg)
8658            }
8659            Expression::Stddev(agg) => push_agg_filter!(Stddev, agg),
8660            Expression::StddevPop(agg) => push_agg_filter!(StddevPop, agg),
8661            Expression::StddevSamp(agg) => push_agg_filter!(StddevSamp, agg),
8662            Expression::Variance(agg) => push_agg_filter!(Variance, agg),
8663            Expression::VarPop(agg) => push_agg_filter!(VarPop, agg),
8664            Expression::VarSamp(agg) => push_agg_filter!(VarSamp, agg),
8665            Expression::Median(agg) => push_agg_filter!(Median, agg),
8666            Expression::Mode(agg) => push_agg_filter!(Mode, agg),
8667            Expression::First(agg) => push_agg_filter!(First, agg),
8668            Expression::Last(agg) => push_agg_filter!(Last, agg),
8669            Expression::AnyValue(agg) => push_agg_filter!(AnyValue, agg),
8670            Expression::ApproxDistinct(agg) => push_agg_filter!(ApproxDistinct, agg),
8671            Expression::ApproxCountDistinct(agg) => {
8672                push_agg_filter!(ApproxCountDistinct, agg)
8673            }
8674            Expression::LogicalAnd(agg) => push_agg_filter!(LogicalAnd, agg),
8675            Expression::LogicalOr(agg) => push_agg_filter!(LogicalOr, agg),
8676            Expression::Skewness(agg) => push_agg_filter!(Skewness, agg),
8677            Expression::ArrayConcatAgg(agg) => push_agg_filter!(ArrayConcatAgg, agg),
8678            Expression::ArrayUniqueAgg(agg) => push_agg_filter!(ArrayUniqueAgg, agg),
8679            Expression::BoolXorAgg(agg) => push_agg_filter!(BoolXorAgg, agg),
8680            Expression::BitwiseAndAgg(agg) => push_agg_filter!(BitwiseAndAgg, agg),
8681            Expression::BitwiseOrAgg(agg) => push_agg_filter!(BitwiseOrAgg, agg),
8682            Expression::BitwiseXorAgg(agg) => push_agg_filter!(BitwiseXorAgg, agg),
8683            Expression::StringAgg(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::StringAgg(agg)
8688            }
8689            Expression::GroupConcat(mut agg) => {
8690                let this = std::mem::replace(&mut agg.this, Expression::null());
8691                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8692                agg.filter = None;
8693                Expression::GroupConcat(agg)
8694            }
8695            Expression::ListAgg(mut agg) => {
8696                let this = std::mem::replace(&mut agg.this, Expression::null());
8697                agg.this = Self::conditional_aggregate_value_for_tsql(filter, this);
8698                agg.filter = None;
8699                Expression::ListAgg(agg)
8700            }
8701            Expression::WithinGroup(mut within_group) => {
8702                within_group.this =
8703                    Self::push_filter_into_tsql_aggregate(within_group.this, filter);
8704                Expression::WithinGroup(within_group)
8705            }
8706            other => Expression::Filter(Box::new(crate::expressions::Filter {
8707                this: Box::new(other),
8708                expression: Box::new(filter),
8709            })),
8710        }
8711    }
8712
8713    fn rewrite_generic_aggregate_filter_for_tsql(
8714        agg: &mut crate::expressions::AggregateFunction,
8715        filter: Expression,
8716    ) {
8717        let is_count =
8718            agg.name.eq_ignore_ascii_case("COUNT") || agg.name.eq_ignore_ascii_case("COUNT_BIG");
8719        let is_count_star = is_count
8720            && (agg.args.is_empty()
8721                || (agg.args.len() == 1 && matches!(agg.args[0], Expression::Star(_))));
8722
8723        if is_count_star {
8724            agg.args = vec![Self::conditional_aggregate_value_for_tsql(
8725                filter,
8726                Expression::number(1),
8727            )];
8728        } else if !agg.args.is_empty() {
8729            agg.args = agg
8730                .args
8731                .drain(..)
8732                .map(|arg| Self::conditional_aggregate_value_for_tsql(filter.clone(), arg))
8733                .collect();
8734        } else {
8735            agg.filter = Some(filter);
8736        }
8737    }
8738
8739    fn conditional_aggregate_value_for_tsql(filter: Expression, value: Expression) -> Expression {
8740        let filter = crate::transforms::ensure_bool_condition(filter);
8741        Expression::Case(Box::new(crate::expressions::Case {
8742            operand: None,
8743            whens: vec![(filter, value)],
8744            else_: None,
8745            comments: Vec::new(),
8746            inferred_type: None,
8747        }))
8748    }
8749
8750    fn reject_pgvector_distance_operators_for_sqlite(&self, sql: &str) -> Result<()> {
8751        let tokens = self.tokenize(sql)?;
8752        for (i, token) in tokens.iter().enumerate() {
8753            if token.token_type == TokenType::NullsafeEq {
8754                return Err(crate::error::Error::unsupported(
8755                    "PostgreSQL pgvector cosine distance operator <=>",
8756                    "SQLite",
8757                ));
8758            }
8759            if token.token_type == TokenType::Lt
8760                && tokens
8761                    .get(i + 1)
8762                    .is_some_and(|token| token.token_type == TokenType::Tilde)
8763                && tokens
8764                    .get(i + 2)
8765                    .is_some_and(|token| token.token_type == TokenType::Gt)
8766            {
8767                return Err(crate::error::Error::unsupported(
8768                    "PostgreSQL pgvector Hamming distance operator <~>",
8769                    "SQLite",
8770                ));
8771            }
8772        }
8773        Ok(())
8774    }
8775
8776    fn normalize_sqlite_double_quoted_defaults(expr: Expression) -> Result<Expression> {
8777        fn normalize_default_expr(expr: Expression) -> Result<Expression> {
8778            transform_recursive(expr, &|e| match e {
8779                Expression::Column(col)
8780                    if col.table.is_none() && col.name.quoted && !col.join_mark =>
8781                {
8782                    Ok(Expression::Literal(Box::new(Literal::String(
8783                        col.name.name,
8784                    ))))
8785                }
8786                Expression::Identifier(id) if id.quoted => {
8787                    Ok(Expression::Literal(Box::new(Literal::String(id.name))))
8788                }
8789                _ => Ok(e),
8790            })
8791        }
8792
8793        fn normalize_column_default(col: &mut crate::expressions::ColumnDef) -> Result<()> {
8794            if let Some(default) = col.default.take() {
8795                col.default = Some(normalize_default_expr(default)?);
8796            }
8797
8798            for constraint in &mut col.constraints {
8799                if let ColumnConstraint::Default(default) = constraint {
8800                    *default = normalize_default_expr(default.clone())?;
8801                }
8802            }
8803
8804            Ok(())
8805        }
8806
8807        transform_recursive(expr, &|e| match e {
8808            Expression::CreateTable(mut ct) => {
8809                for column in &mut ct.columns {
8810                    normalize_column_default(column)?;
8811                }
8812                Ok(Expression::CreateTable(ct))
8813            }
8814            Expression::ColumnDef(mut col) => {
8815                normalize_column_default(&mut col)?;
8816                Ok(Expression::ColumnDef(col))
8817            }
8818            _ => Ok(e),
8819        })
8820    }
8821
8822    fn normalize_postgres_to_sqlite_types(expr: Expression) -> Result<Expression> {
8823        fn sqlite_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8824            use crate::expressions::DataType;
8825
8826            match dt {
8827                DataType::Bit { .. } => DataType::Int {
8828                    length: None,
8829                    integer_spelling: true,
8830                },
8831                DataType::TextWithLength { .. } => DataType::Text,
8832                DataType::VarChar { .. } => DataType::Text,
8833                DataType::Char { .. } => DataType::Text,
8834                DataType::Timestamp { timezone: true, .. } => DataType::Text,
8835                DataType::Custom { name } => {
8836                    let base = name
8837                        .split_once('(')
8838                        .map_or(name.as_str(), |(base, _)| base)
8839                        .trim();
8840                    if base.eq_ignore_ascii_case("TSVECTOR")
8841                        || base.eq_ignore_ascii_case("TIMESTAMPTZ")
8842                        || base.eq_ignore_ascii_case("TIMESTAMP WITH TIME ZONE")
8843                        || base.eq_ignore_ascii_case("NVARCHAR")
8844                        || base.eq_ignore_ascii_case("NCHAR")
8845                    {
8846                        DataType::Text
8847                    } else {
8848                        DataType::Custom { name }
8849                    }
8850                }
8851                _ => dt,
8852            }
8853        }
8854
8855        transform_recursive(expr, &|e| match e {
8856            Expression::DataType(dt) => Ok(Expression::DataType(sqlite_type(dt))),
8857            Expression::CreateTable(mut ct) => {
8858                for column in &mut ct.columns {
8859                    column.data_type = sqlite_type(column.data_type.clone());
8860                }
8861                Ok(Expression::CreateTable(ct))
8862            }
8863            _ => Ok(e),
8864        })
8865    }
8866
8867    fn normalize_postgres_to_fabric_types(expr: Expression) -> Result<Expression> {
8868        fn fabric_type(dt: crate::expressions::DataType) -> crate::expressions::DataType {
8869            use crate::expressions::DataType;
8870
8871            match dt {
8872                DataType::Decimal {
8873                    precision: None,
8874                    scale: None,
8875                } => DataType::Decimal {
8876                    precision: Some(38),
8877                    scale: Some(10),
8878                },
8879                DataType::Json | DataType::JsonB => DataType::Custom {
8880                    name: "VARCHAR(MAX)".to_string(),
8881                },
8882                _ => dt,
8883            }
8884        }
8885
8886        transform_recursive(expr, &|e| match e {
8887            Expression::DataType(dt) => Ok(Expression::DataType(fabric_type(dt))),
8888            Expression::CreateTable(mut ct) => {
8889                for column in &mut ct.columns {
8890                    column.data_type = fabric_type(column.data_type.clone());
8891                }
8892                Ok(Expression::CreateTable(ct))
8893            }
8894            Expression::ColumnDef(mut col) => {
8895                col.data_type = fabric_type(col.data_type);
8896                Ok(Expression::ColumnDef(col))
8897            }
8898            _ => Ok(e),
8899        })
8900    }
8901
8902    /// For DuckDB target: when FROM clause contains RANGE(n), replace
8903    /// `(ROW_NUMBER() OVER (ORDER BY 1 NULLS FIRST) - 1)` with `range` in select expressions.
8904    /// This handles SEQ1/2/4/8 → RANGE transpilation from Snowflake.
8905    fn seq_rownum_to_range(expr: Expression) -> Result<Expression> {
8906        if let Expression::Select(mut select) = expr {
8907            // Check if FROM contains a RANGE function
8908            let has_range_from = if let Some(ref from) = select.from {
8909                from.expressions.iter().any(|e| {
8910                    // Check for direct RANGE(...) or aliased RANGE(...)
8911                    match e {
8912                        Expression::Function(f) => f.name.eq_ignore_ascii_case("RANGE"),
8913                        Expression::Alias(a) => {
8914                            matches!(&a.this, Expression::Function(f) if f.name.eq_ignore_ascii_case("RANGE"))
8915                        }
8916                        _ => false,
8917                    }
8918                })
8919            } else {
8920                false
8921            };
8922
8923            if has_range_from {
8924                // Replace the ROW_NUMBER pattern in select expressions
8925                select.expressions = select
8926                    .expressions
8927                    .into_iter()
8928                    .map(|e| Self::replace_rownum_with_range(e))
8929                    .collect();
8930            }
8931
8932            Ok(Expression::Select(select))
8933        } else {
8934            Ok(expr)
8935        }
8936    }
8937
8938    /// Replace `(ROW_NUMBER() OVER (...) - 1)` with `range` column reference
8939    fn replace_rownum_with_range(expr: Expression) -> Expression {
8940        match expr {
8941            // Match: (ROW_NUMBER() OVER (...) - 1) % N → range % N
8942            Expression::Mod(op) => {
8943                let new_left = Self::try_replace_rownum_paren(&op.left);
8944                Expression::Mod(Box::new(crate::expressions::BinaryOp {
8945                    left: new_left,
8946                    right: op.right,
8947                    left_comments: op.left_comments,
8948                    operator_comments: op.operator_comments,
8949                    trailing_comments: op.trailing_comments,
8950                    inferred_type: op.inferred_type,
8951                }))
8952            }
8953            // Match: (CASE WHEN (ROW...) % N >= ... THEN ... ELSE ... END)
8954            Expression::Paren(p) => {
8955                let inner = Self::replace_rownum_with_range(p.this);
8956                Expression::Paren(Box::new(crate::expressions::Paren {
8957                    this: inner,
8958                    trailing_comments: p.trailing_comments,
8959                }))
8960            }
8961            Expression::Case(mut c) => {
8962                // Replace ROW_NUMBER in WHEN conditions and THEN expressions
8963                c.whens = c
8964                    .whens
8965                    .into_iter()
8966                    .map(|(cond, then)| {
8967                        (
8968                            Self::replace_rownum_with_range(cond),
8969                            Self::replace_rownum_with_range(then),
8970                        )
8971                    })
8972                    .collect();
8973                if let Some(else_) = c.else_ {
8974                    c.else_ = Some(Self::replace_rownum_with_range(else_));
8975                }
8976                Expression::Case(c)
8977            }
8978            Expression::Gte(op) => Expression::Gte(Box::new(crate::expressions::BinaryOp {
8979                left: Self::replace_rownum_with_range(op.left),
8980                right: op.right,
8981                left_comments: op.left_comments,
8982                operator_comments: op.operator_comments,
8983                trailing_comments: op.trailing_comments,
8984                inferred_type: op.inferred_type,
8985            })),
8986            Expression::Sub(op) => Expression::Sub(Box::new(crate::expressions::BinaryOp {
8987                left: Self::replace_rownum_with_range(op.left),
8988                right: op.right,
8989                left_comments: op.left_comments,
8990                operator_comments: op.operator_comments,
8991                trailing_comments: op.trailing_comments,
8992                inferred_type: op.inferred_type,
8993            })),
8994            Expression::Alias(mut a) => {
8995                a.this = Self::replace_rownum_with_range(a.this);
8996                Expression::Alias(a)
8997            }
8998            other => other,
8999        }
9000    }
9001
9002    /// Check if an expression is `(ROW_NUMBER() OVER (...) - 1)` and replace with `range`
9003    fn try_replace_rownum_paren(expr: &Expression) -> Expression {
9004        if let Expression::Paren(ref p) = expr {
9005            if let Expression::Sub(ref sub) = p.this {
9006                if let Expression::WindowFunction(ref wf) = sub.left {
9007                    if let Expression::Function(ref f) = wf.this {
9008                        if f.name.eq_ignore_ascii_case("ROW_NUMBER") {
9009                            if let Expression::Literal(ref lit) = sub.right {
9010                                if let crate::expressions::Literal::Number(ref n) = lit.as_ref() {
9011                                    if n == "1" {
9012                                        return Expression::column("range");
9013                                    }
9014                                }
9015                            }
9016                        }
9017                    }
9018                }
9019            }
9020        }
9021        expr.clone()
9022    }
9023
9024    /// Transform BigQuery GENERATE_DATE_ARRAY in UNNEST for Snowflake target.
9025    /// Converts:
9026    ///   SELECT ..., alias, ... FROM t CROSS JOIN UNNEST(GENERATE_DATE_ARRAY(start, end, INTERVAL '1' unit)) AS alias
9027    /// To:
9028    ///   SELECT ..., DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE)) AS alias, ...
9029    ///   FROM t, LATERAL FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)) AS _t0(seq, key, path, index, alias, this)
9030    fn transform_generate_date_array_snowflake(expr: Expression) -> Result<Expression> {
9031        use crate::expressions::*;
9032        transform_recursive(expr, &|e| {
9033            // Handle ARRAY_SIZE(GENERATE_DATE_ARRAY(...)) -> ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM subquery))
9034            if let Expression::ArraySize(ref af) = e {
9035                if let Expression::Function(ref f) = af.this {
9036                    if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9037                        let result = Self::convert_array_size_gda_snowflake(f)?;
9038                        return Ok(result);
9039                    }
9040                }
9041            }
9042
9043            let Expression::Select(mut sel) = e else {
9044                return Ok(e);
9045            };
9046
9047            // Find joins with UNNEST containing GenerateSeries (from GENERATE_DATE_ARRAY conversion)
9048            let mut gda_info: Option<(String, Expression, Expression, String)> = None; // (alias_name, start_expr, end_expr, unit)
9049            let mut gda_join_idx: Option<usize> = None;
9050
9051            for (idx, join) in sel.joins.iter().enumerate() {
9052                // The join.this may be:
9053                // 1. Unnest(UnnestFunc { alias: Some("mnth"), ... })
9054                // 2. Alias(Alias { this: Unnest(UnnestFunc { alias: None, ... }), alias: "mnth", ... })
9055                let (unnest_ref, alias_name) = match &join.this {
9056                    Expression::Unnest(ref unnest) => {
9057                        let alias = unnest.alias.as_ref().map(|id| id.name.clone());
9058                        (Some(unnest.as_ref()), alias)
9059                    }
9060                    Expression::Alias(ref a) => {
9061                        if let Expression::Unnest(ref unnest) = a.this {
9062                            (Some(unnest.as_ref()), Some(a.alias.name.clone()))
9063                        } else {
9064                            (None, None)
9065                        }
9066                    }
9067                    _ => (None, None),
9068                };
9069
9070                if let (Some(unnest), Some(alias)) = (unnest_ref, alias_name) {
9071                    // Check the main expression (this) of the UNNEST for GENERATE_DATE_ARRAY function
9072                    if let Expression::Function(ref f) = unnest.this {
9073                        if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY") && f.args.len() >= 2 {
9074                            let start_expr = f.args[0].clone();
9075                            let end_expr = f.args[1].clone();
9076                            let step = f.args.get(2).cloned();
9077
9078                            // Extract unit from step interval
9079                            let unit = if let Some(Expression::Interval(ref iv)) = step {
9080                                if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9081                                    Some(format!("{:?}", unit).to_ascii_uppercase())
9082                                } else if let Some(ref this) = iv.this {
9083                                    // The interval may be stored as a string like "1 MONTH"
9084                                    if let Expression::Literal(lit) = this {
9085                                        if let Literal::String(ref s) = lit.as_ref() {
9086                                            let parts: Vec<&str> = s.split_whitespace().collect();
9087                                            if parts.len() == 2 {
9088                                                Some(parts[1].to_ascii_uppercase())
9089                                            } else if parts.len() == 1 {
9090                                                // Single word like "MONTH" or just "1"
9091                                                let upper = parts[0].to_ascii_uppercase();
9092                                                if matches!(
9093                                                    upper.as_str(),
9094                                                    "YEAR"
9095                                                        | "QUARTER"
9096                                                        | "MONTH"
9097                                                        | "WEEK"
9098                                                        | "DAY"
9099                                                        | "HOUR"
9100                                                        | "MINUTE"
9101                                                        | "SECOND"
9102                                                ) {
9103                                                    Some(upper)
9104                                                } else {
9105                                                    None
9106                                                }
9107                                            } else {
9108                                                None
9109                                            }
9110                                        } else {
9111                                            None
9112                                        }
9113                                    } else {
9114                                        None
9115                                    }
9116                                } else {
9117                                    None
9118                                }
9119                            } else {
9120                                None
9121                            };
9122
9123                            if let Some(unit_str) = unit {
9124                                gda_info = Some((alias, start_expr, end_expr, unit_str));
9125                                gda_join_idx = Some(idx);
9126                            }
9127                        }
9128                    }
9129                }
9130                if gda_info.is_some() {
9131                    break;
9132                }
9133            }
9134
9135            let Some((alias_name, start_expr, end_expr, unit_str)) = gda_info else {
9136                // Also check FROM clause for UNNEST(GENERATE_DATE_ARRAY(...)) patterns
9137                // This handles Generic->Snowflake where GENERATE_DATE_ARRAY is in FROM, not in JOIN
9138                let result = Self::try_transform_from_gda_snowflake(sel);
9139                return result;
9140            };
9141            let join_idx = gda_join_idx.unwrap();
9142
9143            // Build ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1)
9144            // ARRAY_GENERATE_RANGE uses exclusive end, and we need DATEDIFF + 1 values
9145            // (inclusive date range), so the exclusive end is DATEDIFF + 1.
9146            let datediff = Expression::Function(Box::new(Function::new(
9147                "DATEDIFF".to_string(),
9148                vec![
9149                    Expression::boxed_column(Column {
9150                        name: Identifier::new(&unit_str),
9151                        table: None,
9152                        join_mark: false,
9153                        trailing_comments: vec![],
9154                        span: None,
9155                        inferred_type: None,
9156                    }),
9157                    start_expr.clone(),
9158                    end_expr.clone(),
9159                ],
9160            )));
9161            let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9162                left: datediff,
9163                right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9164                left_comments: vec![],
9165                operator_comments: vec![],
9166                trailing_comments: vec![],
9167                inferred_type: None,
9168            }));
9169
9170            let array_gen_range = Expression::Function(Box::new(Function::new(
9171                "ARRAY_GENERATE_RANGE".to_string(),
9172                vec![
9173                    Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9174                    datediff_plus_one,
9175                ],
9176            )));
9177
9178            // Build FLATTEN(INPUT => ARRAY_GENERATE_RANGE(...))
9179            let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9180                name: Identifier::new("INPUT"),
9181                value: array_gen_range,
9182                separator: crate::expressions::NamedArgSeparator::DArrow,
9183            }));
9184            let flatten = Expression::Function(Box::new(Function::new(
9185                "FLATTEN".to_string(),
9186                vec![flatten_input],
9187            )));
9188
9189            // Build LATERAL FLATTEN(...) AS _t0(seq, key, path, index, alias, this)
9190            let alias_table = Alias {
9191                this: flatten,
9192                alias: Identifier::new("_t0"),
9193                column_aliases: vec![
9194                    Identifier::new("seq"),
9195                    Identifier::new("key"),
9196                    Identifier::new("path"),
9197                    Identifier::new("index"),
9198                    Identifier::new(&alias_name),
9199                    Identifier::new("this"),
9200                ],
9201                alias_explicit_as: false,
9202                alias_keyword: None,
9203                pre_alias_comments: vec![],
9204                trailing_comments: vec![],
9205                inferred_type: None,
9206            };
9207            let lateral_expr = Expression::Lateral(Box::new(Lateral {
9208                this: Box::new(Expression::Alias(Box::new(alias_table))),
9209                view: None,
9210                outer: None,
9211                alias: None,
9212                alias_quoted: false,
9213                cross_apply: None,
9214                ordinality: None,
9215                column_aliases: vec![],
9216                column_aliases_quoted: vec![],
9217            }));
9218
9219            // Remove the original join and add to FROM expressions
9220            sel.joins.remove(join_idx);
9221            if let Some(ref mut from) = sel.from {
9222                from.expressions.push(lateral_expr);
9223            }
9224
9225            // Build DATEADD(unit, CAST(alias AS INT), CAST(start AS DATE))
9226            let dateadd_expr = Expression::Function(Box::new(Function::new(
9227                "DATEADD".to_string(),
9228                vec![
9229                    Expression::boxed_column(Column {
9230                        name: Identifier::new(&unit_str),
9231                        table: None,
9232                        join_mark: false,
9233                        trailing_comments: vec![],
9234                        span: None,
9235                        inferred_type: None,
9236                    }),
9237                    Expression::Cast(Box::new(Cast {
9238                        this: Expression::boxed_column(Column {
9239                            name: Identifier::new(&alias_name),
9240                            table: None,
9241                            join_mark: false,
9242                            trailing_comments: vec![],
9243                            span: None,
9244                            inferred_type: None,
9245                        }),
9246                        to: DataType::Int {
9247                            length: None,
9248                            integer_spelling: false,
9249                        },
9250                        trailing_comments: vec![],
9251                        double_colon_syntax: false,
9252                        format: None,
9253                        default: None,
9254                        inferred_type: None,
9255                    })),
9256                    Expression::Cast(Box::new(Cast {
9257                        this: start_expr.clone(),
9258                        to: DataType::Date,
9259                        trailing_comments: vec![],
9260                        double_colon_syntax: false,
9261                        format: None,
9262                        default: None,
9263                        inferred_type: None,
9264                    })),
9265                ],
9266            )));
9267
9268            // Replace references to the alias in the SELECT list
9269            let new_exprs: Vec<Expression> = sel
9270                .expressions
9271                .iter()
9272                .map(|expr| Self::replace_column_ref_with_dateadd(expr, &alias_name, &dateadd_expr))
9273                .collect();
9274            sel.expressions = new_exprs;
9275
9276            Ok(Expression::Select(sel))
9277        })
9278    }
9279
9280    /// Helper: replace column references to `alias_name` with dateadd expression
9281    fn replace_column_ref_with_dateadd(
9282        expr: &Expression,
9283        alias_name: &str,
9284        dateadd: &Expression,
9285    ) -> Expression {
9286        use crate::expressions::*;
9287        match expr {
9288            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9289                // Plain column reference -> DATEADD(...) AS alias_name
9290                Expression::Alias(Box::new(Alias {
9291                    this: dateadd.clone(),
9292                    alias: Identifier::new(alias_name),
9293                    column_aliases: vec![],
9294                    alias_explicit_as: false,
9295                    alias_keyword: None,
9296                    pre_alias_comments: vec![],
9297                    trailing_comments: vec![],
9298                    inferred_type: None,
9299                }))
9300            }
9301            Expression::Alias(a) => {
9302                // Check if the inner expression references the alias
9303                let new_this = Self::replace_column_ref_inner(&a.this, alias_name, dateadd);
9304                Expression::Alias(Box::new(Alias {
9305                    this: new_this,
9306                    alias: a.alias.clone(),
9307                    column_aliases: a.column_aliases.clone(),
9308                    alias_explicit_as: false,
9309                    alias_keyword: None,
9310                    pre_alias_comments: a.pre_alias_comments.clone(),
9311                    trailing_comments: a.trailing_comments.clone(),
9312                    inferred_type: None,
9313                }))
9314            }
9315            _ => expr.clone(),
9316        }
9317    }
9318
9319    /// Helper: replace column references in inner expression (not top-level)
9320    fn replace_column_ref_inner(
9321        expr: &Expression,
9322        alias_name: &str,
9323        dateadd: &Expression,
9324    ) -> Expression {
9325        use crate::expressions::*;
9326        match expr {
9327            Expression::Column(c) if c.name.name == alias_name && c.table.is_none() => {
9328                dateadd.clone()
9329            }
9330            Expression::Add(op) => {
9331                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9332                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9333                Expression::Add(Box::new(BinaryOp {
9334                    left,
9335                    right,
9336                    left_comments: op.left_comments.clone(),
9337                    operator_comments: op.operator_comments.clone(),
9338                    trailing_comments: op.trailing_comments.clone(),
9339                    inferred_type: None,
9340                }))
9341            }
9342            Expression::Sub(op) => {
9343                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9344                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9345                Expression::Sub(Box::new(BinaryOp {
9346                    left,
9347                    right,
9348                    left_comments: op.left_comments.clone(),
9349                    operator_comments: op.operator_comments.clone(),
9350                    trailing_comments: op.trailing_comments.clone(),
9351                    inferred_type: None,
9352                }))
9353            }
9354            Expression::Mul(op) => {
9355                let left = Self::replace_column_ref_inner(&op.left, alias_name, dateadd);
9356                let right = Self::replace_column_ref_inner(&op.right, alias_name, dateadd);
9357                Expression::Mul(Box::new(BinaryOp {
9358                    left,
9359                    right,
9360                    left_comments: op.left_comments.clone(),
9361                    operator_comments: op.operator_comments.clone(),
9362                    trailing_comments: op.trailing_comments.clone(),
9363                    inferred_type: None,
9364                }))
9365            }
9366            _ => expr.clone(),
9367        }
9368    }
9369
9370    /// Handle UNNEST(GENERATE_DATE_ARRAY(...)) in FROM clause for Snowflake target.
9371    /// Converts to a subquery with DATEADD + TABLE(FLATTEN(ARRAY_GENERATE_RANGE(...))).
9372    fn try_transform_from_gda_snowflake(
9373        mut sel: Box<crate::expressions::Select>,
9374    ) -> Result<Expression> {
9375        use crate::expressions::*;
9376
9377        // Extract GDA info from FROM clause
9378        let mut gda_info: Option<(
9379            usize,
9380            String,
9381            Expression,
9382            Expression,
9383            String,
9384            Option<(String, Vec<Identifier>)>,
9385        )> = None; // (from_idx, col_name, start, end, unit, outer_alias)
9386
9387        if let Some(ref from) = sel.from {
9388            for (idx, table_expr) in from.expressions.iter().enumerate() {
9389                // Pattern 1: UNNEST(GENERATE_DATE_ARRAY(...))
9390                // Pattern 2: Alias(UNNEST(GENERATE_DATE_ARRAY(...))) AS _q(date_week)
9391                let (unnest_opt, outer_alias_info) = match table_expr {
9392                    Expression::Unnest(ref unnest) => (Some(unnest.as_ref()), None),
9393                    Expression::Alias(ref a) => {
9394                        if let Expression::Unnest(ref unnest) = a.this {
9395                            let alias_info = (a.alias.name.clone(), a.column_aliases.clone());
9396                            (Some(unnest.as_ref()), Some(alias_info))
9397                        } else {
9398                            (None, None)
9399                        }
9400                    }
9401                    _ => (None, None),
9402                };
9403
9404                if let Some(unnest) = unnest_opt {
9405                    // Check for GENERATE_DATE_ARRAY function
9406                    let func_opt = match &unnest.this {
9407                        Expression::Function(ref f)
9408                            if f.name.eq_ignore_ascii_case("GENERATE_DATE_ARRAY")
9409                                && f.args.len() >= 2 =>
9410                        {
9411                            Some(f)
9412                        }
9413                        // Also check for GenerateSeries (from earlier normalization)
9414                        _ => None,
9415                    };
9416
9417                    if let Some(f) = func_opt {
9418                        let start_expr = f.args[0].clone();
9419                        let end_expr = f.args[1].clone();
9420                        let step = f.args.get(2).cloned();
9421
9422                        // Extract unit and column name
9423                        let unit = Self::extract_interval_unit_str(&step);
9424                        let col_name = outer_alias_info
9425                            .as_ref()
9426                            .and_then(|(_, cols)| cols.first().map(|id| id.name.clone()))
9427                            .unwrap_or_else(|| "value".to_string());
9428
9429                        if let Some(unit_str) = unit {
9430                            gda_info = Some((
9431                                idx,
9432                                col_name,
9433                                start_expr,
9434                                end_expr,
9435                                unit_str,
9436                                outer_alias_info,
9437                            ));
9438                            break;
9439                        }
9440                    }
9441                }
9442            }
9443        }
9444
9445        let Some((from_idx, col_name, start_expr, end_expr, unit_str, outer_alias_info)) = gda_info
9446        else {
9447            return Ok(Expression::Select(sel));
9448        };
9449
9450        // Build the Snowflake subquery:
9451        // (SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9452        //  FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(seq, key, path, index, col_name, this))
9453
9454        // DATEDIFF(unit, start, end)
9455        let datediff = Expression::Function(Box::new(Function::new(
9456            "DATEDIFF".to_string(),
9457            vec![
9458                Expression::boxed_column(Column {
9459                    name: Identifier::new(&unit_str),
9460                    table: None,
9461                    join_mark: false,
9462                    trailing_comments: vec![],
9463                    span: None,
9464                    inferred_type: None,
9465                }),
9466                start_expr.clone(),
9467                end_expr.clone(),
9468            ],
9469        )));
9470        // DATEDIFF(...) + 1
9471        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9472            left: datediff,
9473            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9474            left_comments: vec![],
9475            operator_comments: vec![],
9476            trailing_comments: vec![],
9477            inferred_type: None,
9478        }));
9479
9480        let array_gen_range = Expression::Function(Box::new(Function::new(
9481            "ARRAY_GENERATE_RANGE".to_string(),
9482            vec![
9483                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9484                datediff_plus_one,
9485            ],
9486        )));
9487
9488        // TABLE(FLATTEN(INPUT => ...))
9489        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9490            name: Identifier::new("INPUT"),
9491            value: array_gen_range,
9492            separator: crate::expressions::NamedArgSeparator::DArrow,
9493        }));
9494        let flatten = Expression::Function(Box::new(Function::new(
9495            "FLATTEN".to_string(),
9496            vec![flatten_input],
9497        )));
9498
9499        // Determine alias name for the table: use outer alias or _t0
9500        let table_alias_name = outer_alias_info
9501            .as_ref()
9502            .map(|(name, _)| name.clone())
9503            .unwrap_or_else(|| "_t0".to_string());
9504
9505        // TABLE(FLATTEN(...)) AS _t0(seq, key, path, index, col_name, this)
9506        let table_func =
9507            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9508        let flatten_aliased = Expression::Alias(Box::new(Alias {
9509            this: table_func,
9510            alias: Identifier::new(&table_alias_name),
9511            column_aliases: vec![
9512                Identifier::new("seq"),
9513                Identifier::new("key"),
9514                Identifier::new("path"),
9515                Identifier::new("index"),
9516                Identifier::new(&col_name),
9517                Identifier::new("this"),
9518            ],
9519            alias_explicit_as: false,
9520            alias_keyword: None,
9521            pre_alias_comments: vec![],
9522            trailing_comments: vec![],
9523            inferred_type: None,
9524        }));
9525
9526        // SELECT DATEADD(unit, CAST(col_name AS INT), CAST(start AS DATE)) AS col_name
9527        let dateadd_expr = Expression::Function(Box::new(Function::new(
9528            "DATEADD".to_string(),
9529            vec![
9530                Expression::boxed_column(Column {
9531                    name: Identifier::new(&unit_str),
9532                    table: None,
9533                    join_mark: false,
9534                    trailing_comments: vec![],
9535                    span: None,
9536                    inferred_type: None,
9537                }),
9538                Expression::Cast(Box::new(Cast {
9539                    this: Expression::boxed_column(Column {
9540                        name: Identifier::new(&col_name),
9541                        table: None,
9542                        join_mark: false,
9543                        trailing_comments: vec![],
9544                        span: None,
9545                        inferred_type: None,
9546                    }),
9547                    to: DataType::Int {
9548                        length: None,
9549                        integer_spelling: false,
9550                    },
9551                    trailing_comments: vec![],
9552                    double_colon_syntax: false,
9553                    format: None,
9554                    default: None,
9555                    inferred_type: None,
9556                })),
9557                // Use start_expr directly - it's already been normalized (DATE literal -> CAST)
9558                start_expr.clone(),
9559            ],
9560        )));
9561        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9562            this: dateadd_expr,
9563            alias: Identifier::new(&col_name),
9564            column_aliases: vec![],
9565            alias_explicit_as: false,
9566            alias_keyword: None,
9567            pre_alias_comments: vec![],
9568            trailing_comments: vec![],
9569            inferred_type: None,
9570        }));
9571
9572        // Build inner SELECT
9573        let mut inner_select = Select::new();
9574        inner_select.expressions = vec![dateadd_aliased];
9575        inner_select.from = Some(From {
9576            expressions: vec![flatten_aliased],
9577        });
9578
9579        let inner_select_expr = Expression::Select(Box::new(inner_select));
9580        let subquery = Expression::Subquery(Box::new(Subquery {
9581            this: inner_select_expr,
9582            alias: None,
9583            column_aliases: vec![],
9584            alias_explicit_as: false,
9585            alias_keyword: None,
9586            order_by: None,
9587            limit: None,
9588            offset: None,
9589            distribute_by: None,
9590            sort_by: None,
9591            cluster_by: None,
9592            lateral: false,
9593            modifiers_inside: false,
9594            trailing_comments: vec![],
9595            inferred_type: None,
9596        }));
9597
9598        // If there was an outer alias (e.g., AS _q(date_week)), wrap with alias
9599        let replacement = if let Some((alias_name, col_aliases)) = outer_alias_info {
9600            Expression::Alias(Box::new(Alias {
9601                this: subquery,
9602                alias: Identifier::new(&alias_name),
9603                column_aliases: col_aliases,
9604                alias_explicit_as: false,
9605                alias_keyword: None,
9606                pre_alias_comments: vec![],
9607                trailing_comments: vec![],
9608                inferred_type: None,
9609            }))
9610        } else {
9611            subquery
9612        };
9613
9614        // Replace the FROM expression
9615        if let Some(ref mut from) = sel.from {
9616            from.expressions[from_idx] = replacement;
9617        }
9618
9619        Ok(Expression::Select(sel))
9620    }
9621
9622    /// Convert ARRAY_SIZE(GENERATE_DATE_ARRAY(start, end, step)) for Snowflake.
9623    /// Produces: ARRAY_SIZE((SELECT ARRAY_AGG(*) FROM (SELECT DATEADD(unit, CAST(value AS INT), start) AS value
9624    ///   FROM TABLE(FLATTEN(INPUT => ARRAY_GENERATE_RANGE(0, DATEDIFF(unit, start, end) + 1))) AS _t0(...))))
9625    fn convert_array_size_gda_snowflake(f: &crate::expressions::Function) -> Result<Expression> {
9626        use crate::expressions::*;
9627
9628        let start_expr = f.args[0].clone();
9629        let end_expr = f.args[1].clone();
9630        let step = f.args.get(2).cloned();
9631        let unit_str = Self::extract_interval_unit_str(&step).unwrap_or_else(|| "DAY".to_string());
9632        let col_name = "value";
9633
9634        // Build the inner subquery: same as try_transform_from_gda_snowflake
9635        let datediff = Expression::Function(Box::new(Function::new(
9636            "DATEDIFF".to_string(),
9637            vec![
9638                Expression::boxed_column(Column {
9639                    name: Identifier::new(&unit_str),
9640                    table: None,
9641                    join_mark: false,
9642                    trailing_comments: vec![],
9643                    span: None,
9644                    inferred_type: None,
9645                }),
9646                start_expr.clone(),
9647                end_expr.clone(),
9648            ],
9649        )));
9650        // DATEDIFF(...) + 1
9651        let datediff_plus_one = Expression::Add(Box::new(BinaryOp {
9652            left: datediff,
9653            right: Expression::Literal(Box::new(Literal::Number("1".to_string()))),
9654            left_comments: vec![],
9655            operator_comments: vec![],
9656            trailing_comments: vec![],
9657            inferred_type: None,
9658        }));
9659
9660        let array_gen_range = Expression::Function(Box::new(Function::new(
9661            "ARRAY_GENERATE_RANGE".to_string(),
9662            vec![
9663                Expression::Literal(Box::new(Literal::Number("0".to_string()))),
9664                datediff_plus_one,
9665            ],
9666        )));
9667
9668        let flatten_input = Expression::NamedArgument(Box::new(NamedArgument {
9669            name: Identifier::new("INPUT"),
9670            value: array_gen_range,
9671            separator: crate::expressions::NamedArgSeparator::DArrow,
9672        }));
9673        let flatten = Expression::Function(Box::new(Function::new(
9674            "FLATTEN".to_string(),
9675            vec![flatten_input],
9676        )));
9677
9678        let table_func =
9679            Expression::Function(Box::new(Function::new("TABLE".to_string(), vec![flatten])));
9680        let flatten_aliased = Expression::Alias(Box::new(Alias {
9681            this: table_func,
9682            alias: Identifier::new("_t0"),
9683            column_aliases: vec![
9684                Identifier::new("seq"),
9685                Identifier::new("key"),
9686                Identifier::new("path"),
9687                Identifier::new("index"),
9688                Identifier::new(col_name),
9689                Identifier::new("this"),
9690            ],
9691            alias_explicit_as: false,
9692            alias_keyword: None,
9693            pre_alias_comments: vec![],
9694            trailing_comments: vec![],
9695            inferred_type: None,
9696        }));
9697
9698        let dateadd_expr = Expression::Function(Box::new(Function::new(
9699            "DATEADD".to_string(),
9700            vec![
9701                Expression::boxed_column(Column {
9702                    name: Identifier::new(&unit_str),
9703                    table: None,
9704                    join_mark: false,
9705                    trailing_comments: vec![],
9706                    span: None,
9707                    inferred_type: None,
9708                }),
9709                Expression::Cast(Box::new(Cast {
9710                    this: Expression::boxed_column(Column {
9711                        name: Identifier::new(col_name),
9712                        table: None,
9713                        join_mark: false,
9714                        trailing_comments: vec![],
9715                        span: None,
9716                        inferred_type: None,
9717                    }),
9718                    to: DataType::Int {
9719                        length: None,
9720                        integer_spelling: false,
9721                    },
9722                    trailing_comments: vec![],
9723                    double_colon_syntax: false,
9724                    format: None,
9725                    default: None,
9726                    inferred_type: None,
9727                })),
9728                start_expr.clone(),
9729            ],
9730        )));
9731        let dateadd_aliased = Expression::Alias(Box::new(Alias {
9732            this: dateadd_expr,
9733            alias: Identifier::new(col_name),
9734            column_aliases: vec![],
9735            alias_explicit_as: false,
9736            alias_keyword: None,
9737            pre_alias_comments: vec![],
9738            trailing_comments: vec![],
9739            inferred_type: None,
9740        }));
9741
9742        // Inner SELECT: SELECT DATEADD(...) AS value FROM TABLE(FLATTEN(...)) AS _t0(...)
9743        let mut inner_select = Select::new();
9744        inner_select.expressions = vec![dateadd_aliased];
9745        inner_select.from = Some(From {
9746            expressions: vec![flatten_aliased],
9747        });
9748
9749        // Wrap in subquery for the inner part
9750        let inner_subquery = Expression::Subquery(Box::new(Subquery {
9751            this: Expression::Select(Box::new(inner_select)),
9752            alias: None,
9753            column_aliases: vec![],
9754            alias_explicit_as: false,
9755            alias_keyword: None,
9756            order_by: None,
9757            limit: None,
9758            offset: None,
9759            distribute_by: None,
9760            sort_by: None,
9761            cluster_by: None,
9762            lateral: false,
9763            modifiers_inside: false,
9764            trailing_comments: vec![],
9765            inferred_type: None,
9766        }));
9767
9768        // Outer: SELECT ARRAY_AGG(*) FROM (inner_subquery)
9769        let star = Expression::Star(Star {
9770            table: None,
9771            except: None,
9772            replace: None,
9773            rename: None,
9774            trailing_comments: vec![],
9775            span: None,
9776        });
9777        let array_agg = Expression::ArrayAgg(Box::new(AggFunc {
9778            this: star,
9779            distinct: false,
9780            filter: None,
9781            order_by: vec![],
9782            name: Some("ARRAY_AGG".to_string()),
9783            ignore_nulls: None,
9784            having_max: None,
9785            limit: None,
9786            inferred_type: None,
9787        }));
9788
9789        let mut outer_select = Select::new();
9790        outer_select.expressions = vec![array_agg];
9791        outer_select.from = Some(From {
9792            expressions: vec![inner_subquery],
9793        });
9794
9795        // Wrap in a subquery
9796        let outer_subquery = Expression::Subquery(Box::new(Subquery {
9797            this: Expression::Select(Box::new(outer_select)),
9798            alias: None,
9799            column_aliases: vec![],
9800            alias_explicit_as: false,
9801            alias_keyword: None,
9802            order_by: None,
9803            limit: None,
9804            offset: None,
9805            distribute_by: None,
9806            sort_by: None,
9807            cluster_by: None,
9808            lateral: false,
9809            modifiers_inside: false,
9810            trailing_comments: vec![],
9811            inferred_type: None,
9812        }));
9813
9814        // ARRAY_SIZE(subquery)
9815        Ok(Expression::ArraySize(Box::new(UnaryFunc::new(
9816            outer_subquery,
9817        ))))
9818    }
9819
9820    /// Extract interval unit string from an optional step expression.
9821    fn extract_interval_unit_str(step: &Option<Expression>) -> Option<String> {
9822        use crate::expressions::*;
9823        if let Some(Expression::Interval(ref iv)) = step {
9824            if let Some(IntervalUnitSpec::Simple { ref unit, .. }) = iv.unit {
9825                return Some(format!("{:?}", unit).to_ascii_uppercase());
9826            }
9827            if let Some(ref this) = iv.this {
9828                if let Expression::Literal(lit) = this {
9829                    if let Literal::String(ref s) = lit.as_ref() {
9830                        let parts: Vec<&str> = s.split_whitespace().collect();
9831                        if parts.len() == 2 {
9832                            return Some(parts[1].to_ascii_uppercase());
9833                        } else if parts.len() == 1 {
9834                            let upper = parts[0].to_ascii_uppercase();
9835                            if matches!(
9836                                upper.as_str(),
9837                                "YEAR"
9838                                    | "QUARTER"
9839                                    | "MONTH"
9840                                    | "WEEK"
9841                                    | "DAY"
9842                                    | "HOUR"
9843                                    | "MINUTE"
9844                                    | "SECOND"
9845                            ) {
9846                                return Some(upper);
9847                            }
9848                        }
9849                    }
9850                }
9851            }
9852        }
9853        // Default to DAY if no step or no interval
9854        if step.is_none() {
9855            return Some("DAY".to_string());
9856        }
9857        None
9858    }
9859
9860    fn normalize_snowflake_pretty(mut sql: String) -> String {
9861        if sql.contains("LATERAL IFF(_u.pos = _u_2.pos_2, _u_2.entity, NULL) AS datasource(SEQ, KEY, PATH, INDEX, VALUE, THIS)")
9862            && sql.contains("ARRAY_GENERATE_RANGE(0, (GREATEST(ARRAY_SIZE(INPUT => PARSE_JSON(flags))) - 1) + 1)")
9863        {
9864            sql = sql.replace(
9865                "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')",
9866                "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    )",
9867            );
9868
9869            sql = sql.replace(
9870                "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)",
9871                "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)",
9872            );
9873
9874            sql = sql.replace(
9875                "OR (_u.pos > (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1)\n  AND _u_2.pos_2 = (ARRAY_SIZE(INPUT => PARSE_JSON(flags)) - 1))",
9876                "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  )",
9877            );
9878        }
9879
9880        sql
9881    }
9882
9883    #[cfg(feature = "transpile")]
9884    fn wrap_tsql_top_level_values(expr: Expression) -> Expression {
9885        match expr {
9886            Expression::Values(values) => Self::tsql_values_as_select(*values),
9887            Expression::Union(mut union) => {
9888                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9889                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9890                union.left = Self::wrap_tsql_values_set_operand(left);
9891                union.right = Self::wrap_tsql_values_set_operand(right);
9892                Expression::Union(union)
9893            }
9894            Expression::Intersect(mut intersect) => {
9895                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9896                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9897                intersect.left = Self::wrap_tsql_values_set_operand(left);
9898                intersect.right = Self::wrap_tsql_values_set_operand(right);
9899                Expression::Intersect(intersect)
9900            }
9901            Expression::Except(mut except) => {
9902                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9903                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9904                except.left = Self::wrap_tsql_values_set_operand(left);
9905                except.right = Self::wrap_tsql_values_set_operand(right);
9906                Expression::Except(except)
9907            }
9908            other => other,
9909        }
9910    }
9911
9912    #[cfg(feature = "transpile")]
9913    fn wrap_tsql_values_set_operand(expr: Expression) -> Expression {
9914        match expr {
9915            Expression::Values(values) => Self::tsql_values_as_select(*values),
9916            Expression::Select(mut select)
9917                if Self::is_parser_wrapped_values_set_operand(&select) =>
9918            {
9919                let mut from = select.from.take().expect("checked as present");
9920                let Expression::Values(values) = from
9921                    .expressions
9922                    .pop()
9923                    .expect("checked as a single VALUES source")
9924                else {
9925                    unreachable!("checked as a VALUES source");
9926                };
9927                Self::tsql_values_as_select(*values)
9928            }
9929            Expression::Annotated(mut annotated) => {
9930                annotated.this = Self::wrap_tsql_values_set_operand(annotated.this);
9931                Expression::Annotated(annotated)
9932            }
9933            Expression::Union(mut union) => {
9934                let left = std::mem::replace(&mut union.left, Expression::Null(Null));
9935                let right = std::mem::replace(&mut union.right, Expression::Null(Null));
9936                union.left = Self::wrap_tsql_values_set_operand(left);
9937                union.right = Self::wrap_tsql_values_set_operand(right);
9938                Expression::Union(union)
9939            }
9940            Expression::Intersect(mut intersect) => {
9941                let left = std::mem::replace(&mut intersect.left, Expression::Null(Null));
9942                let right = std::mem::replace(&mut intersect.right, Expression::Null(Null));
9943                intersect.left = Self::wrap_tsql_values_set_operand(left);
9944                intersect.right = Self::wrap_tsql_values_set_operand(right);
9945                Expression::Intersect(intersect)
9946            }
9947            Expression::Except(mut except) => {
9948                let left = std::mem::replace(&mut except.left, Expression::Null(Null));
9949                let right = std::mem::replace(&mut except.right, Expression::Null(Null));
9950                except.left = Self::wrap_tsql_values_set_operand(left);
9951                except.right = Self::wrap_tsql_values_set_operand(right);
9952                Expression::Except(except)
9953            }
9954            other => other,
9955        }
9956    }
9957
9958    #[cfg(feature = "transpile")]
9959    fn is_parser_wrapped_values_set_operand(select: &Select) -> bool {
9960        let Some(from) = &select.from else {
9961            return false;
9962        };
9963        let [Expression::Values(values)] = from.expressions.as_slice() else {
9964            return false;
9965        };
9966        if !values
9967            .alias
9968            .as_ref()
9969            .is_some_and(|alias| alias.name == "_values")
9970        {
9971            return false;
9972        }
9973
9974        let mut parser_wrapper = Select::new();
9975        parser_wrapper.expressions = vec![Expression::star()];
9976        parser_wrapper.from = Some(from.clone());
9977        select == &parser_wrapper
9978    }
9979
9980    #[cfg(feature = "transpile")]
9981    fn tsql_values_as_select(mut values: crate::expressions::Values) -> Expression {
9982        let column_aliases = if values.column_aliases.is_empty() {
9983            let column_count = values
9984                .expressions
9985                .first()
9986                .map(|row| row.expressions.len())
9987                .unwrap_or(0);
9988            (1..=column_count)
9989                .map(|index| Identifier::new(format!("column{index}")))
9990                .collect()
9991        } else {
9992            std::mem::take(&mut values.column_aliases)
9993        };
9994
9995        values.alias = None;
9996
9997        let values_subquery = Expression::Subquery(Box::new(crate::expressions::Subquery {
9998            this: Expression::Values(Box::new(values)),
9999            alias: Some(Identifier::new("_v")),
10000            column_aliases,
10001            alias_explicit_as: false,
10002            alias_keyword: None,
10003            order_by: None,
10004            limit: None,
10005            offset: None,
10006            distribute_by: None,
10007            sort_by: None,
10008            cluster_by: None,
10009            lateral: false,
10010            modifiers_inside: false,
10011            trailing_comments: Vec::new(),
10012            inferred_type: None,
10013        }));
10014
10015        let mut select = crate::expressions::Select::new();
10016        select.expressions = vec![Expression::star()];
10017        select.from = Some(From {
10018            expressions: vec![values_subquery],
10019        });
10020
10021        Expression::Select(Box::new(select))
10022    }
10023
10024    fn extract_interval_parts(
10025        interval_expr: &Expression,
10026    ) -> Option<(Expression, crate::expressions::IntervalUnit)> {
10027        use crate::expressions::{DataType, IntervalUnit, IntervalUnitSpec, Literal};
10028
10029        fn unit_from_str(unit: &str) -> Option<IntervalUnit> {
10030            match unit.trim().to_ascii_uppercase().as_str() {
10031                "YEAR" | "YEARS" | "Y" | "YR" | "YRS" | "YY" | "YYYY" => Some(IntervalUnit::Year),
10032                "QUARTER" | "QUARTERS" | "Q" | "QTR" | "QTRS" | "QQ" => Some(IntervalUnit::Quarter),
10033                "MONTH" | "MONTHS" | "MON" | "MONS" | "MM" => Some(IntervalUnit::Month),
10034                "WEEK" | "WEEKS" | "W" | "WK" | "WKS" | "WW" | "ISOWEEK" => {
10035                    Some(IntervalUnit::Week)
10036                }
10037                "DAY" | "DAYS" | "D" | "DD" => Some(IntervalUnit::Day),
10038                "HOUR" | "HOURS" | "H" | "HH" | "HR" | "HRS" => Some(IntervalUnit::Hour),
10039                "MINUTE" | "MINUTES" | "MI" | "MIN" | "MINS" | "N" => Some(IntervalUnit::Minute),
10040                "SECOND" | "SECONDS" | "S" | "SEC" | "SECS" | "SS" => Some(IntervalUnit::Second),
10041                "MILLISECOND" | "MILLISECONDS" | "MS" | "MSEC" | "MSECS" | "MSECOND"
10042                | "MSECONDS" | "MILLISEC" | "MILLISECS" | "MILLISECON" => {
10043                    Some(IntervalUnit::Millisecond)
10044                }
10045                "MICROSECOND" | "MICROSECONDS" | "US" | "USEC" | "USECS" | "USECOND"
10046                | "USECONDS" | "MICROSEC" | "MICROSECS" | "MCS" => Some(IntervalUnit::Microsecond),
10047                "NANOSECOND" | "NANOSECONDS" | "NS" | "NSEC" | "NSECS" | "NSECOND" | "NSECONDS"
10048                | "NANOSEC" | "NANOSECS" => Some(IntervalUnit::Nanosecond),
10049                _ => None,
10050            }
10051        }
10052
10053        fn parts_from_literal_string(s: &str) -> Option<(Expression, IntervalUnit)> {
10054            let mut parts = s.split_whitespace();
10055            let value = parts.next()?;
10056            let unit = unit_from_str(parts.next()?)?;
10057            Some((
10058                Expression::Literal(Box::new(Literal::String(value.to_string()))),
10059                unit,
10060            ))
10061        }
10062
10063        fn unit_from_spec(unit: &IntervalUnitSpec) -> Option<IntervalUnit> {
10064            match unit {
10065                IntervalUnitSpec::Simple { unit, .. } => Some(*unit),
10066                IntervalUnitSpec::Expr(expr) => match expr.as_ref() {
10067                    Expression::Day(_) => Some(IntervalUnit::Day),
10068                    Expression::Month(_) => Some(IntervalUnit::Month),
10069                    Expression::Year(_) => Some(IntervalUnit::Year),
10070                    Expression::Identifier(id) => unit_from_str(&id.name),
10071                    Expression::Var(v) => unit_from_str(&v.this),
10072                    Expression::Column(col) => unit_from_str(&col.name.name),
10073                    _ => None,
10074                },
10075                _ => None,
10076            }
10077        }
10078
10079        match interval_expr {
10080            Expression::Interval(iv) => {
10081                let val = iv.this.clone().unwrap_or(Expression::number(0));
10082                if let Expression::Literal(lit) = &val {
10083                    if let Literal::String(s) = lit.as_ref() {
10084                        if let Some(parts) = parts_from_literal_string(s) {
10085                            return Some(parts);
10086                        }
10087                    }
10088                }
10089                let unit = iv
10090                    .unit
10091                    .as_ref()
10092                    .and_then(unit_from_spec)
10093                    .unwrap_or(IntervalUnit::Day);
10094                Some((val, unit))
10095            }
10096            Expression::Cast(cast) if matches!(cast.to, DataType::Interval { .. }) => {
10097                if let Expression::Literal(lit) = &cast.this {
10098                    if let Literal::String(s) = lit.as_ref() {
10099                        if let Some(parts) = parts_from_literal_string(s) {
10100                            return Some(parts);
10101                        }
10102                    }
10103                }
10104                let unit = match &cast.to {
10105                    DataType::Interval {
10106                        unit: Some(unit), ..
10107                    } => unit_from_str(unit).unwrap_or(IntervalUnit::Day),
10108                    _ => IntervalUnit::Day,
10109                };
10110                Some((cast.this.clone(), unit))
10111            }
10112            _ => None,
10113        }
10114    }
10115
10116    fn data_type_is_interval(dt: &DataType) -> bool {
10117        match dt {
10118            DataType::Interval { .. } => true,
10119            DataType::Custom { name } => name.trim().eq_ignore_ascii_case("INTERVAL"),
10120            _ => false,
10121        }
10122    }
10123
10124    fn node_is_interval_cast(node: &Expression) -> bool {
10125        match node {
10126            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10127                Self::data_type_is_interval(&c.to)
10128            }
10129            _ => false,
10130        }
10131    }
10132
10133    fn reject_tsql_interval_casts(
10134        expr: &Expression,
10135        target: DialectType,
10136        opts: &TranspileOptions,
10137    ) -> Result<()> {
10138        if !matches!(
10139            opts.unsupported_level,
10140            UnsupportedLevel::Raise | UnsupportedLevel::Immediate
10141        ) {
10142            return Ok(());
10143        }
10144
10145        if expr.dfs().any(Self::node_is_interval_cast) {
10146            return Err(crate::error::Error::unsupported(
10147                "INTERVAL casts",
10148                target.to_string(),
10149            ));
10150        }
10151
10152        Ok(())
10153    }
10154
10155    fn tsql_varchar_max_type() -> DataType {
10156        DataType::Custom {
10157            name: "VARCHAR(MAX)".to_string(),
10158        }
10159    }
10160
10161    fn rewrite_tsql_interval_casts_to_varchar(expr: Expression) -> Result<Expression> {
10162        transform_recursive(expr, &|e| match e {
10163            Expression::Cast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10164                cast.to = Self::tsql_varchar_max_type();
10165                cast.double_colon_syntax = false;
10166                Ok(Expression::Cast(cast))
10167            }
10168            Expression::TryCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10169                cast.to = Self::tsql_varchar_max_type();
10170                cast.double_colon_syntax = false;
10171                Ok(Expression::TryCast(cast))
10172            }
10173            Expression::SafeCast(mut cast) if Self::data_type_is_interval(&cast.to) => {
10174                cast.to = Self::tsql_varchar_max_type();
10175                cast.double_colon_syntax = false;
10176                Ok(Expression::SafeCast(cast))
10177            }
10178            _ => Ok(e),
10179        })
10180    }
10181
10182    fn rewrite_tsql_interval_arithmetic_legacy(
10183        expr: &Expression,
10184        source: DialectType,
10185    ) -> Option<Expression> {
10186        match expr {
10187            Expression::Add(op) => {
10188                if Self::extract_interval_parts(&op.right).is_some() {
10189                    return Some(Self::build_tsql_dateadd_from_interval(
10190                        op.left.clone(),
10191                        &op.right,
10192                        false,
10193                    ));
10194                }
10195
10196                if Self::is_postgres_family_source(source) {
10197                    if Self::is_explicit_date_expr(&op.left)
10198                        && Self::is_integer_day_offset_expr(&op.right)
10199                    {
10200                        return Some(Self::build_tsql_dateadd_days(
10201                            op.left.clone(),
10202                            op.right.clone(),
10203                            false,
10204                        ));
10205                    }
10206
10207                    if Self::is_integer_day_offset_expr(&op.left)
10208                        && Self::is_explicit_date_expr(&op.right)
10209                    {
10210                        return Some(Self::build_tsql_dateadd_days(
10211                            op.right.clone(),
10212                            op.left.clone(),
10213                            false,
10214                        ));
10215                    }
10216                }
10217
10218                None
10219            }
10220            Expression::Sub(op) => {
10221                if Self::extract_interval_parts(&op.right).is_some() {
10222                    return Some(Self::build_tsql_dateadd_from_interval(
10223                        op.left.clone(),
10224                        &op.right,
10225                        true,
10226                    ));
10227                }
10228
10229                if Self::is_postgres_family_source(source) {
10230                    if Self::is_explicit_date_expr(&op.left)
10231                        && Self::is_explicit_date_expr(&op.right)
10232                    {
10233                        return Some(Self::build_tsql_datediff_days(
10234                            op.right.clone(),
10235                            op.left.clone(),
10236                        ));
10237                    }
10238
10239                    if Self::is_explicit_date_expr(&op.left)
10240                        && Self::is_integer_day_offset_expr(&op.right)
10241                    {
10242                        return Some(Self::build_tsql_dateadd_days(
10243                            op.left.clone(),
10244                            op.right.clone(),
10245                            true,
10246                        ));
10247                    }
10248                }
10249
10250                None
10251            }
10252            _ => None,
10253        }
10254    }
10255
10256    fn is_postgres_family_source(source: DialectType) -> bool {
10257        matches!(
10258            source,
10259            DialectType::PostgreSQL
10260                | DialectType::Redshift
10261                | DialectType::Materialize
10262                | DialectType::RisingWave
10263                | DialectType::CockroachDB
10264        )
10265    }
10266
10267    fn is_explicit_date_expr(expr: &Expression) -> bool {
10268        use crate::expressions::Literal;
10269
10270        match expr {
10271            Expression::Literal(lit) => matches!(lit.as_ref(), Literal::Date(_)),
10272            Expression::Cast(c) | Expression::TryCast(c) | Expression::SafeCast(c) => {
10273                matches!(c.to, crate::expressions::DataType::Date)
10274            }
10275            Expression::Paren(p) => Self::is_explicit_date_expr(&p.this),
10276            Expression::CurrentDate(_)
10277            | Expression::Date(_)
10278            | Expression::MakeDate(_)
10279            | Expression::ToDate(_)
10280            | Expression::DateStrToDate(_) => true,
10281            _ => false,
10282        }
10283    }
10284
10285    fn is_integer_day_offset_expr(expr: &Expression) -> bool {
10286        use crate::expressions::Literal;
10287
10288        match expr {
10289            Expression::Literal(lit) => match lit.as_ref() {
10290                Literal::Number(n) => n.parse::<i64>().is_ok(),
10291                _ => false,
10292            },
10293            Expression::Parameter(_) | Expression::Placeholder(_) => true,
10294            Expression::Neg(op) => Self::is_integer_day_offset_expr(&op.this),
10295            Expression::Paren(p) => Self::is_integer_day_offset_expr(&p.this),
10296            _ => false,
10297        }
10298    }
10299
10300    fn build_tsql_datediff_days(start: Expression, end: Expression) -> Expression {
10301        Expression::Function(Box::new(Function::new(
10302            "DATEDIFF".to_string(),
10303            vec![Expression::Identifier(Identifier::new("DAY")), start, end],
10304        )))
10305    }
10306
10307    fn build_tsql_dateadd_days(date: Expression, amount: Expression, subtract: bool) -> Expression {
10308        Expression::Function(Box::new(Function::new(
10309            "DATEADD".to_string(),
10310            vec![
10311                Expression::Identifier(Identifier::new("DAY")),
10312                Self::tsql_dateadd_amount(amount, subtract),
10313                date,
10314            ],
10315        )))
10316    }
10317
10318    fn build_tsql_dateadd_from_interval(
10319        date: Expression,
10320        interval: &Expression,
10321        subtract: bool,
10322    ) -> Expression {
10323        let (value, unit) = Self::extract_interval_parts(interval)
10324            .unwrap_or_else(|| (interval.clone(), crate::expressions::IntervalUnit::Day));
10325        let unit = normalization::temporal::interval_unit_to_string(&unit);
10326        let amount = Self::tsql_dateadd_amount(value, subtract);
10327
10328        Expression::Function(Box::new(Function::new(
10329            "DATEADD".to_string(),
10330            vec![Expression::Identifier(Identifier::new(unit)), amount, date],
10331        )))
10332    }
10333
10334    fn tsql_dateadd_amount(value: Expression, negate: bool) -> Expression {
10335        use crate::expressions::{Parameter, ParameterStyle, UnaryOp};
10336
10337        fn numeric_literal_value(value: &Expression) -> Option<&str> {
10338            match value {
10339                Expression::Literal(lit) => match lit.as_ref() {
10340                    crate::expressions::Literal::Number(n)
10341                    | crate::expressions::Literal::String(n) => Some(n.as_str()),
10342                    _ => None,
10343                },
10344                _ => None,
10345            }
10346        }
10347
10348        fn colon_parameter(value: &Expression) -> Option<Expression> {
10349            let Expression::Literal(lit) = value else {
10350                return None;
10351            };
10352            let crate::expressions::Literal::String(s) = lit.as_ref() else {
10353                return None;
10354            };
10355            let name = s.strip_prefix(':')?;
10356            if name.is_empty()
10357                || !name
10358                    .chars()
10359                    .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
10360            {
10361                return None;
10362            }
10363
10364            Some(Expression::Parameter(Box::new(Parameter {
10365                name: if name.chars().all(|ch| ch.is_ascii_digit()) {
10366                    None
10367                } else {
10368                    Some(name.to_string())
10369                },
10370                index: name.parse::<u32>().ok(),
10371                style: ParameterStyle::Colon,
10372                quoted: false,
10373                string_quoted: false,
10374                expression: None,
10375            })))
10376        }
10377
10378        let value = colon_parameter(&value).unwrap_or(value);
10379
10380        if let Some(n) = numeric_literal_value(&value) {
10381            if let Ok(parsed) = n.parse::<f64>() {
10382                let normalized = if negate { -parsed } else { parsed };
10383                let rendered = if normalized.fract() == 0.0 {
10384                    format!("{}", normalized as i64)
10385                } else {
10386                    normalized.to_string()
10387                };
10388                return Expression::Literal(Box::new(crate::expressions::Literal::Number(
10389                    rendered,
10390                )));
10391            }
10392        }
10393
10394        if !negate {
10395            return value;
10396        }
10397
10398        match value {
10399            Expression::Neg(op) => op.this,
10400            other => Expression::Neg(Box::new(UnaryOp {
10401                this: other,
10402                inferred_type: None,
10403            })),
10404        }
10405    }
10406
10407    /// Internal TO_DATE function that won't be converted to CAST by the Snowflake handler.
10408    /// Uses the name `_POLYGLOT_TO_DATE` which is not recognized by the TO_DATE -> CAST logic.
10409    /// The Snowflake DATEDIFF handler converts these back to TO_DATE.
10410    const PRESERVED_TO_DATE: &'static str = "_POLYGLOT_TO_DATE";
10411}
10412
10413#[cfg(test)]
10414mod tests {
10415    use super::*;
10416
10417    #[cfg(all(
10418        feature = "transpile",
10419        feature = "dialect-hana",
10420        feature = "dialect-duckdb",
10421        feature = "dialect-trino"
10422    ))]
10423    #[test]
10424    fn hana_validation_and_generation_agree_after_ast_serialization() {
10425        let hana = Dialect::get(DialectType::HANA);
10426        for (sql, duckdb, trino) in [
10427            ("SELECT COALESCE(IFNULL(x, 0), 1) FROM t", true, true),
10428            ("SELECT LOCATE('abcabc', 'bc', 2)", true, true),
10429            ("SELECT LOCATE('abcabc', 'bc', 1, 2)", false, true),
10430            ("SELECT SUBSTRING('abc', -1, -2)", true, true),
10431            ("SELECT TO_DECIMAL(12.345, 5, 2)", true, true),
10432            (
10433                "SELECT CAST(12.345 AS DECIMAL(5)), CAST(-12.9 AS INT)",
10434                true,
10435                true,
10436            ),
10437            ("SELECT TO_DATE('2024-01-15', 'YYYY-MM-DD')", true, true),
10438            (
10439                "SELECT TO_TIMESTAMP('2024-01-15', 'YYYY-MM-DD')",
10440                false,
10441                true,
10442            ),
10443            (
10444                "SELECT TO_VARCHAR(DATE '2024-01-15', 'YYYY-MM-DD')",
10445                true,
10446                true,
10447            ),
10448            ("SELECT CURRENT_UTCDATE", true, true),
10449            (
10450                "SELECT CURRENT_UTCTIMESTAMP(7), CURRENT_UTCTIME",
10451                false,
10452                true,
10453            ),
10454            ("CREATE TABLE t (n INT, xs INT ARRAY)", true, true),
10455            ("CREATE TABLE t (n SMALLDECIMAL ARRAY)", false, false),
10456            ("ALTER TABLE t ADD (n SMALLDECIMAL ARRAY)", false, false),
10457            ("ALTER TABLE t ADD (n INT ARRAY)", true, true),
10458            ("SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t", false, false),
10459            ("SELECT COALESCE(CAST(x AS INT), 0) FROM t", false, false),
10460            (
10461                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10462                false,
10463                false,
10464            ),
10465            (
10466                "SELECT COALESCE(SUBSTR_REGEXPR('a' IN s), 'x') FROM t",
10467                false,
10468                false,
10469            ),
10470            (
10471                "SELECT COALESCE(STRING_AGG(x, ','), '') FROM t",
10472                false,
10473                false,
10474            ),
10475            (
10476                "CREATE TABLE t (n INT DEFAULT ADD_DAYS(d, 1))",
10477                false,
10478                false,
10479            ),
10480            ("SELECT * FROM t FOR JSON", false, false),
10481            ("CREATE COLUMN TABLE t (n INT)", false, false),
10482            ("SELECT * FROM t WITH HINT (NO_INLINE)", false, false),
10483        ] {
10484            let ast = hana.parse(sql).unwrap().remove(0);
10485            let json = serde_json::to_string(&ast).unwrap();
10486            let ast: Expression = serde_json::from_str(&json).unwrap();
10487            for (target, supported) in [(DialectType::DuckDB, duckdb), (DialectType::Trino, trino)]
10488            {
10489                assert_eq!(
10490                    Dialect::reject_hana_source_semantics(&ast, DialectType::HANA, target).is_ok(),
10491                    supported,
10492                    "validation: {sql} -> {target}"
10493                );
10494                assert_eq!(
10495                    Dialect::get(target).generate(&ast).is_ok(),
10496                    supported,
10497                    "generation: {sql} -> {target}"
10498                );
10499            }
10500            assert!(Dialect::reject_hana_source_semantics(
10501                &ast,
10502                DialectType::HANA,
10503                DialectType::HANA
10504            )
10505            .is_ok());
10506        }
10507    }
10508
10509    #[cfg(all(
10510        feature = "transpile",
10511        feature = "dialect-hana",
10512        feature = "dialect-duckdb"
10513    ))]
10514    #[test]
10515    fn hana_rejects_source_semantics_before_preprocessing_can_erase_them() {
10516        use std::sync::atomic::{AtomicBool, Ordering};
10517        let called = Arc::new(AtomicBool::new(false));
10518        let seen = Arc::clone(&called);
10519        let mut hana = Dialect::get(DialectType::HANA);
10520        hana.custom_preprocess = Some(Box::new(move |_| {
10521            seen.store(true, Ordering::Relaxed);
10522            Ok(Expression::number(1))
10523        }));
10524        for level in [
10525            UnsupportedLevel::Ignore,
10526            UnsupportedLevel::Warn,
10527            UnsupportedLevel::Raise,
10528            UnsupportedLevel::Immediate,
10529        ] {
10530            for sql in [
10531                "SELECT COALESCE(ADD_DAYS(d, 1), d) FROM t",
10532                "SELECT COALESCE(CAST(x AS INT), 0) FROM t",
10533                "SELECT COALESCE(JSON_VALUE(j, '$.a'), 'x') FROM t",
10534                "CREATE TABLE t (x SMALLDECIMAL ARRAY)",
10535                "ALTER TABLE t ADD (x SMALLDECIMAL ARRAY)",
10536                "SELECT CAST(xs AS SMALLDECIMAL ARRAY) FROM t",
10537                "CREATE FUNCTION f(x SMALLDECIMAL ARRAY) RETURNS INT AS 1",
10538                "SELECT * FROM t FOR JSON",
10539                "SELECT * FROM t WITH HINT (NO_INLINE)",
10540            ] {
10541                assert!(
10542                    hana.transpile_with(
10543                        sql,
10544                        DialectType::DuckDB,
10545                        TranspileOptions::default().with_unsupported_level(level)
10546                    )
10547                    .is_err(),
10548                    "{sql}"
10549                );
10550                assert!(
10551                    !called.load(Ordering::Relaxed),
10552                    "preprocessing must not erase unsupported input: {sql}"
10553                );
10554            }
10555        }
10556        let mut target = Dialect::get(DialectType::DuckDB);
10557        Arc::make_mut(&mut target.generator_config).dialect = Some(DialectType::Generic);
10558        assert!(hana
10559            .transpile("SELECT LOCATE('abc', 'b')", &target)
10560            .is_err());
10561        assert!(!called.load(Ordering::Relaxed));
10562        assert!(hana
10563            .transpile("SELECT COALESCE(x, 0) FROM t", DialectType::DuckDB)
10564            .is_ok());
10565        assert!(called.load(Ordering::Relaxed));
10566    }
10567
10568    #[test]
10569    fn built_in_dialect_instances_share_tokenizer_config() {
10570        let first = Dialect::get(DialectType::PostgreSQL);
10571        let second = Dialect::get(DialectType::PostgreSQL);
10572
10573        assert!(first.tokenizer.shares_config_with(&second.tokenizer));
10574    }
10575
10576    #[test]
10577    fn test_dialect_type_from_str() {
10578        assert_eq!(
10579            "postgres".parse::<DialectType>().unwrap(),
10580            DialectType::PostgreSQL
10581        );
10582        assert_eq!(
10583            "postgresql".parse::<DialectType>().unwrap(),
10584            DialectType::PostgreSQL
10585        );
10586        assert_eq!("mysql".parse::<DialectType>().unwrap(), DialectType::MySQL);
10587        assert_eq!(
10588            "bigquery".parse::<DialectType>().unwrap(),
10589            DialectType::BigQuery
10590        );
10591    }
10592
10593    #[test]
10594    fn test_basic_transpile() {
10595        let dialect = Dialect::get(DialectType::Generic);
10596        let result = dialect
10597            .transpile("SELECT 1", DialectType::PostgreSQL)
10598            .unwrap();
10599        assert_eq!(result.len(), 1);
10600        assert_eq!(result[0], "SELECT 1");
10601    }
10602
10603    #[test]
10604    fn test_sqlite_double_quoted_column_defaults_to_postgres_strings() {
10605        let sqlite = Dialect::get(DialectType::SQLite);
10606        let result = sqlite
10607            .transpile(
10608                r#"CREATE TABLE "_collections" (
10609                    "type" TEXT DEFAULT "base" NOT NULL,
10610                    "fields" JSON DEFAULT "[]" NOT NULL,
10611                    "options" JSON DEFAULT "{}" NOT NULL
10612                )"#,
10613                DialectType::PostgreSQL,
10614            )
10615            .unwrap();
10616
10617        assert!(result[0].contains(r#""type" TEXT DEFAULT 'base' NOT NULL"#));
10618        assert!(result[0].contains(r#""fields" JSON DEFAULT '[]' NOT NULL"#));
10619        assert!(result[0].contains(r#""options" JSON DEFAULT '{}' NOT NULL"#));
10620    }
10621
10622    #[test]
10623    fn test_sqlite_identity_preserves_double_quoted_column_defaults() {
10624        let sqlite = Dialect::get(DialectType::SQLite);
10625        let result = sqlite
10626            .transpile(
10627                r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#,
10628                DialectType::SQLite,
10629            )
10630            .unwrap();
10631
10632        assert_eq!(
10633            result[0],
10634            r#"CREATE TABLE "_collections" ("type" TEXT DEFAULT "base" NOT NULL)"#
10635        );
10636    }
10637
10638    #[test]
10639    fn test_function_transformation_mysql() {
10640        // NVL should be transformed to IFNULL in MySQL
10641        let dialect = Dialect::get(DialectType::Generic);
10642        let result = dialect
10643            .transpile("SELECT NVL(a, b)", DialectType::MySQL)
10644            .unwrap();
10645        assert_eq!(result[0], "SELECT IFNULL(a, b)");
10646    }
10647
10648    #[test]
10649    fn test_get_path_duckdb() {
10650        // Test: step by step
10651        let snowflake = Dialect::get(DialectType::Snowflake);
10652
10653        // Step 1: Parse and check what Snowflake produces as intermediate
10654        let result_sf_sf = snowflake
10655            .transpile(
10656                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10657                DialectType::Snowflake,
10658            )
10659            .unwrap();
10660        eprintln!("Snowflake->Snowflake colon: {}", result_sf_sf[0]);
10661
10662        // Step 2: DuckDB target
10663        let result_sf_dk = snowflake
10664            .transpile(
10665                "SELECT PARSE_JSON('{\"fruit\":\"banana\"}'):fruit",
10666                DialectType::DuckDB,
10667            )
10668            .unwrap();
10669        eprintln!("Snowflake->DuckDB colon: {}", result_sf_dk[0]);
10670
10671        // Step 3: GET_PATH directly
10672        let result_gp = snowflake
10673            .transpile(
10674                "SELECT GET_PATH(PARSE_JSON('{\"fruit\":\"banana\"}'), 'fruit')",
10675                DialectType::DuckDB,
10676            )
10677            .unwrap();
10678        eprintln!("Snowflake->DuckDB explicit GET_PATH: {}", result_gp[0]);
10679    }
10680
10681    #[test]
10682    fn test_function_transformation_postgres() {
10683        // IFNULL should be transformed to COALESCE in PostgreSQL
10684        let dialect = Dialect::get(DialectType::Generic);
10685        let result = dialect
10686            .transpile("SELECT IFNULL(a, b)", DialectType::PostgreSQL)
10687            .unwrap();
10688        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10689
10690        // NVL should also be transformed to COALESCE
10691        let result = dialect
10692            .transpile("SELECT NVL(a, b)", DialectType::PostgreSQL)
10693            .unwrap();
10694        assert_eq!(result[0], "SELECT COALESCE(a, b)");
10695    }
10696
10697    #[test]
10698    fn test_hive_cast_to_trycast() {
10699        // Hive CAST should become TRY_CAST for targets that support it
10700        let hive = Dialect::get(DialectType::Hive);
10701        let result = hive
10702            .transpile("CAST(1 AS INT)", DialectType::DuckDB)
10703            .unwrap();
10704        assert_eq!(result[0], "TRY_CAST(1 AS INT)");
10705
10706        let result = hive
10707            .transpile("CAST(1 AS INT)", DialectType::Presto)
10708            .unwrap();
10709        assert_eq!(result[0], "TRY_CAST(1 AS INTEGER)");
10710    }
10711
10712    #[test]
10713    fn test_hive_array_identity() {
10714        // Hive ARRAY<DATE> should preserve angle bracket syntax
10715        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')";
10716        let hive = Dialect::get(DialectType::Hive);
10717
10718        // Test via transpile (this works)
10719        let result = hive.transpile(sql, DialectType::Hive).unwrap();
10720        eprintln!("Hive ARRAY via transpile: {}", result[0]);
10721        assert!(
10722            result[0].contains("ARRAY<DATE>"),
10723            "transpile: Expected ARRAY<DATE>, got: {}",
10724            result[0]
10725        );
10726
10727        // Test via parse -> transform -> generate (identity test path)
10728        let ast = hive.parse(sql).unwrap();
10729        let transformed = hive.transform(ast[0].clone()).unwrap();
10730        let output = hive.generate(&transformed).unwrap();
10731        eprintln!("Hive ARRAY via identity path: {}", output);
10732        assert!(
10733            output.contains("ARRAY<DATE>"),
10734            "identity path: Expected ARRAY<DATE>, got: {}",
10735            output
10736        );
10737    }
10738
10739    #[test]
10740    fn test_starrocks_delete_between_expansion() {
10741        // StarRocks doesn't support BETWEEN in DELETE statements
10742        let dialect = Dialect::get(DialectType::Generic);
10743
10744        // BETWEEN should be expanded to >= AND <= in DELETE
10745        let result = dialect
10746            .transpile(
10747                "DELETE FROM t WHERE a BETWEEN b AND c",
10748                DialectType::StarRocks,
10749            )
10750            .unwrap();
10751        assert_eq!(result[0], "DELETE FROM t WHERE a >= b AND a <= c");
10752
10753        // NOT BETWEEN should be expanded to < OR > in DELETE
10754        let result = dialect
10755            .transpile(
10756                "DELETE FROM t WHERE a NOT BETWEEN b AND c",
10757                DialectType::StarRocks,
10758            )
10759            .unwrap();
10760        assert_eq!(result[0], "DELETE FROM t WHERE a < b OR a > c");
10761
10762        // BETWEEN in SELECT should NOT be expanded (StarRocks supports it there)
10763        let result = dialect
10764            .transpile(
10765                "SELECT * FROM t WHERE a BETWEEN b AND c",
10766                DialectType::StarRocks,
10767            )
10768            .unwrap();
10769        assert!(
10770            result[0].contains("BETWEEN"),
10771            "BETWEEN should be preserved in SELECT"
10772        );
10773    }
10774
10775    #[test]
10776    fn test_snowflake_ltrim_rtrim_parse() {
10777        let sf = Dialect::get(DialectType::Snowflake);
10778        let sql = "SELECT LTRIM(RTRIM(col)) FROM t1";
10779        let result = sf.transpile(sql, DialectType::DuckDB);
10780        match &result {
10781            Ok(r) => eprintln!("LTRIM/RTRIM result: {}", r[0]),
10782            Err(e) => eprintln!("LTRIM/RTRIM error: {}", e),
10783        }
10784        assert!(
10785            result.is_ok(),
10786            "Expected successful parse of LTRIM(RTRIM(col)), got error: {:?}",
10787            result.err()
10788        );
10789    }
10790
10791    #[test]
10792    fn test_duckdb_count_if_parse() {
10793        let duck = Dialect::get(DialectType::DuckDB);
10794        let sql = "COUNT_IF(x)";
10795        let result = duck.transpile(sql, DialectType::DuckDB);
10796        match &result {
10797            Ok(r) => eprintln!("COUNT_IF result: {}", r[0]),
10798            Err(e) => eprintln!("COUNT_IF error: {}", e),
10799        }
10800        assert!(
10801            result.is_ok(),
10802            "Expected successful parse of COUNT_IF(x), got error: {:?}",
10803            result.err()
10804        );
10805    }
10806
10807    #[test]
10808    fn test_tsql_cast_tinyint_parse() {
10809        let tsql = Dialect::get(DialectType::TSQL);
10810        let sql = "CAST(X AS TINYINT)";
10811        let result = tsql.transpile(sql, DialectType::DuckDB);
10812        match &result {
10813            Ok(r) => eprintln!("TSQL CAST TINYINT result: {}", r[0]),
10814            Err(e) => eprintln!("TSQL CAST TINYINT error: {}", e),
10815        }
10816        assert!(
10817            result.is_ok(),
10818            "Expected successful transpile, got error: {:?}",
10819            result.err()
10820        );
10821    }
10822
10823    #[test]
10824    fn test_pg_hash_bitwise_xor() {
10825        let dialect = Dialect::get(DialectType::PostgreSQL);
10826        let result = dialect.transpile("x # y", DialectType::PostgreSQL).unwrap();
10827        assert_eq!(result[0], "x # y");
10828    }
10829
10830    #[test]
10831    fn test_pg_array_to_duckdb() {
10832        let dialect = Dialect::get(DialectType::PostgreSQL);
10833        let result = dialect
10834            .transpile("SELECT ARRAY[1, 2, 3] @> ARRAY[1, 2]", DialectType::DuckDB)
10835            .unwrap();
10836        assert_eq!(result[0], "SELECT [1, 2, 3] @> [1, 2]");
10837    }
10838
10839    #[test]
10840    fn test_array_remove_bigquery() {
10841        let dialect = Dialect::get(DialectType::Generic);
10842        let result = dialect
10843            .transpile("ARRAY_REMOVE(the_array, target)", DialectType::BigQuery)
10844            .unwrap();
10845        assert_eq!(
10846            result[0],
10847            "ARRAY(SELECT _u FROM UNNEST(the_array) AS _u WHERE _u <> target)"
10848        );
10849    }
10850
10851    #[test]
10852    fn test_map_clickhouse_case() {
10853        let dialect = Dialect::get(DialectType::Generic);
10854        let parsed = dialect
10855            .parse("CAST(MAP('a', '1') AS MAP(TEXT, TEXT))")
10856            .unwrap();
10857        eprintln!("MAP parsed: {:?}", parsed);
10858        let result = dialect
10859            .transpile(
10860                "CAST(MAP('a', '1') AS MAP(TEXT, TEXT))",
10861                DialectType::ClickHouse,
10862            )
10863            .unwrap();
10864        eprintln!("MAP result: {}", result[0]);
10865    }
10866
10867    #[test]
10868    fn test_generate_date_array_presto() {
10869        let dialect = Dialect::get(DialectType::Generic);
10870        let result = dialect.transpile(
10871            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10872            DialectType::Presto,
10873        ).unwrap();
10874        eprintln!("GDA -> Presto: {}", result[0]);
10875        assert_eq!(result[0], "SELECT * FROM UNNEST(SEQUENCE(CAST('2020-01-01' AS DATE), CAST('2020-02-01' AS DATE), (1 * INTERVAL '7' DAY)))");
10876    }
10877
10878    #[test]
10879    fn test_generate_date_array_postgres() {
10880        let dialect = Dialect::get(DialectType::Generic);
10881        let result = dialect.transpile(
10882            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10883            DialectType::PostgreSQL,
10884        ).unwrap();
10885        eprintln!("GDA -> PostgreSQL: {}", result[0]);
10886    }
10887
10888    #[test]
10889    fn test_generate_date_array_snowflake() {
10890        let dialect = Dialect::get(DialectType::Generic);
10891        let result = dialect
10892            .transpile(
10893                "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10894                DialectType::Snowflake,
10895            )
10896            .unwrap();
10897        eprintln!("GDA -> Snowflake: {}", result[0]);
10898    }
10899
10900    #[test]
10901    fn test_array_length_generate_date_array_snowflake() {
10902        let dialect = Dialect::get(DialectType::Generic);
10903        let result = dialect.transpile(
10904            "SELECT ARRAY_LENGTH(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10905            DialectType::Snowflake,
10906        ).unwrap();
10907        eprintln!("ARRAY_LENGTH(GDA) -> Snowflake: {}", result[0]);
10908    }
10909
10910    #[test]
10911    fn test_generate_date_array_mysql() {
10912        let dialect = Dialect::get(DialectType::Generic);
10913        let result = dialect.transpile(
10914            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10915            DialectType::MySQL,
10916        ).unwrap();
10917        eprintln!("GDA -> MySQL: {}", result[0]);
10918    }
10919
10920    #[test]
10921    fn test_generate_date_array_redshift() {
10922        let dialect = Dialect::get(DialectType::Generic);
10923        let result = dialect.transpile(
10924            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10925            DialectType::Redshift,
10926        ).unwrap();
10927        eprintln!("GDA -> Redshift: {}", result[0]);
10928    }
10929
10930    #[test]
10931    fn test_generate_date_array_tsql() {
10932        let dialect = Dialect::get(DialectType::Generic);
10933        let result = dialect.transpile(
10934            "SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))",
10935            DialectType::TSQL,
10936        ).unwrap();
10937        eprintln!("GDA -> TSQL: {}", result[0]);
10938    }
10939
10940    #[test]
10941    fn test_struct_colon_syntax() {
10942        let dialect = Dialect::get(DialectType::Generic);
10943        // Test without colon first
10944        let result = dialect.transpile(
10945            "CAST((1, 2, 3, 4) AS STRUCT<a TINYINT, b SMALLINT, c INT, d BIGINT>)",
10946            DialectType::ClickHouse,
10947        );
10948        match result {
10949            Ok(r) => eprintln!("STRUCT no colon -> ClickHouse: {}", r[0]),
10950            Err(e) => eprintln!("STRUCT no colon error: {}", e),
10951        }
10952        // Now test with colon
10953        let result = dialect.transpile(
10954            "CAST((1, 2, 3, 4) AS STRUCT<a: TINYINT, b: SMALLINT, c: INT, d: BIGINT>)",
10955            DialectType::ClickHouse,
10956        );
10957        match result {
10958            Ok(r) => eprintln!("STRUCT colon -> ClickHouse: {}", r[0]),
10959            Err(e) => eprintln!("STRUCT colon error: {}", e),
10960        }
10961    }
10962
10963    #[test]
10964    fn test_generate_date_array_cte_wrapped_mysql() {
10965        let dialect = Dialect::get(DialectType::Generic);
10966        let result = dialect.transpile(
10967            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10968            DialectType::MySQL,
10969        ).unwrap();
10970        eprintln!("GDA CTE -> MySQL: {}", result[0]);
10971    }
10972
10973    #[test]
10974    fn test_generate_date_array_cte_wrapped_tsql() {
10975        let dialect = Dialect::get(DialectType::Generic);
10976        let result = dialect.transpile(
10977            "WITH dates AS (SELECT * FROM UNNEST(GENERATE_DATE_ARRAY(DATE '2020-01-01', DATE '2020-02-01', INTERVAL 1 WEEK))) SELECT * FROM dates",
10978            DialectType::TSQL,
10979        ).unwrap();
10980        eprintln!("GDA CTE -> TSQL: {}", result[0]);
10981    }
10982
10983    #[test]
10984    fn test_decode_literal_no_null_check() {
10985        // Oracle DECODE with all literals should produce simple equality, no IS NULL
10986        let dialect = Dialect::get(DialectType::Oracle);
10987        let result = dialect
10988            .transpile("SELECT decode(1,2,3,4)", DialectType::DuckDB)
10989            .unwrap();
10990        assert_eq!(
10991            result[0], "SELECT CASE WHEN 1 = 2 THEN 3 ELSE 4 END",
10992            "Literal DECODE should not have IS NULL checks"
10993        );
10994    }
10995
10996    #[test]
10997    fn test_decode_column_vs_literal_no_null_check() {
10998        // Oracle DECODE with column vs literal should use simple equality (like sqlglot)
10999        let dialect = Dialect::get(DialectType::Oracle);
11000        let result = dialect
11001            .transpile("SELECT decode(col, 2, 3, 4) FROM t", DialectType::DuckDB)
11002            .unwrap();
11003        assert_eq!(
11004            result[0], "SELECT CASE WHEN col = 2 THEN 3 ELSE 4 END FROM t",
11005            "Column vs literal DECODE should not have IS NULL checks"
11006        );
11007    }
11008
11009    #[test]
11010    fn test_decode_column_vs_column_keeps_null_check() {
11011        // Oracle DECODE with column vs column should keep null-safe comparison
11012        let dialect = Dialect::get(DialectType::Oracle);
11013        let result = dialect
11014            .transpile("SELECT decode(col, col2, 3, 4) FROM t", DialectType::DuckDB)
11015            .unwrap();
11016        assert!(
11017            result[0].contains("IS NULL"),
11018            "Column vs column DECODE should have IS NULL checks, got: {}",
11019            result[0]
11020        );
11021    }
11022
11023    #[test]
11024    fn test_decode_null_search() {
11025        // Oracle DECODE with NULL search should use IS NULL
11026        let dialect = Dialect::get(DialectType::Oracle);
11027        let result = dialect
11028            .transpile("SELECT decode(col, NULL, 3, 4) FROM t", DialectType::DuckDB)
11029            .unwrap();
11030        assert_eq!(
11031            result[0],
11032            "SELECT CASE WHEN col IS NULL THEN 3 ELSE 4 END FROM t",
11033        );
11034    }
11035
11036    // =========================================================================
11037    // REGEXP function transpilation tests
11038    // =========================================================================
11039
11040    #[test]
11041    fn test_regexp_substr_snowflake_to_duckdb_2arg() {
11042        let dialect = Dialect::get(DialectType::Snowflake);
11043        let result = dialect
11044            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern')", DialectType::DuckDB)
11045            .unwrap();
11046        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11047    }
11048
11049    #[test]
11050    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos1() {
11051        let dialect = Dialect::get(DialectType::Snowflake);
11052        let result = dialect
11053            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 1)", DialectType::DuckDB)
11054            .unwrap();
11055        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11056    }
11057
11058    #[test]
11059    fn test_regexp_substr_snowflake_to_duckdb_3arg_pos_gt1() {
11060        let dialect = Dialect::get(DialectType::Snowflake);
11061        let result = dialect
11062            .transpile("SELECT REGEXP_SUBSTR(s, 'pattern', 3)", DialectType::DuckDB)
11063            .unwrap();
11064        assert_eq!(
11065            result[0],
11066            "SELECT REGEXP_EXTRACT(NULLIF(SUBSTRING(s, 3), ''), 'pattern')"
11067        );
11068    }
11069
11070    #[test]
11071    fn test_regexp_substr_snowflake_to_duckdb_4arg_occ_gt1() {
11072        let dialect = Dialect::get(DialectType::Snowflake);
11073        let result = dialect
11074            .transpile(
11075                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 3)",
11076                DialectType::DuckDB,
11077            )
11078            .unwrap();
11079        assert_eq!(
11080            result[0],
11081            "SELECT ARRAY_EXTRACT(REGEXP_EXTRACT_ALL(s, 'pattern'), 3)"
11082        );
11083    }
11084
11085    #[test]
11086    fn test_regexp_substr_snowflake_to_duckdb_5arg_e_flag() {
11087        let dialect = Dialect::get(DialectType::Snowflake);
11088        let result = dialect
11089            .transpile(
11090                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')",
11091                DialectType::DuckDB,
11092            )
11093            .unwrap();
11094        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11095    }
11096
11097    #[test]
11098    fn test_regexp_substr_snowflake_to_duckdb_6arg_group0() {
11099        let dialect = Dialect::get(DialectType::Snowflake);
11100        let result = dialect
11101            .transpile(
11102                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11103                DialectType::DuckDB,
11104            )
11105            .unwrap();
11106        assert_eq!(result[0], "SELECT REGEXP_EXTRACT(s, 'pattern')");
11107    }
11108
11109    #[test]
11110    fn test_regexp_substr_snowflake_identity_strip_group0() {
11111        let dialect = Dialect::get(DialectType::Snowflake);
11112        let result = dialect
11113            .transpile(
11114                "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e', 0)",
11115                DialectType::Snowflake,
11116            )
11117            .unwrap();
11118        assert_eq!(result[0], "SELECT REGEXP_SUBSTR(s, 'pattern', 1, 1, 'e')");
11119    }
11120
11121    #[test]
11122    fn test_regexp_substr_all_snowflake_to_duckdb_2arg() {
11123        let dialect = Dialect::get(DialectType::Snowflake);
11124        let result = dialect
11125            .transpile(
11126                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')",
11127                DialectType::DuckDB,
11128            )
11129            .unwrap();
11130        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11131    }
11132
11133    #[test]
11134    fn test_regexp_substr_all_snowflake_to_duckdb_3arg_pos_gt1() {
11135        let dialect = Dialect::get(DialectType::Snowflake);
11136        let result = dialect
11137            .transpile(
11138                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 3)",
11139                DialectType::DuckDB,
11140            )
11141            .unwrap();
11142        assert_eq!(
11143            result[0],
11144            "SELECT REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')"
11145        );
11146    }
11147
11148    #[test]
11149    fn test_regexp_substr_all_snowflake_to_duckdb_5arg_e_flag() {
11150        let dialect = Dialect::get(DialectType::Snowflake);
11151        let result = dialect
11152            .transpile(
11153                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')",
11154                DialectType::DuckDB,
11155            )
11156            .unwrap();
11157        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11158    }
11159
11160    #[test]
11161    fn test_regexp_substr_all_snowflake_to_duckdb_6arg_group0() {
11162        let dialect = Dialect::get(DialectType::Snowflake);
11163        let result = dialect
11164            .transpile(
11165                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11166                DialectType::DuckDB,
11167            )
11168            .unwrap();
11169        assert_eq!(result[0], "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')");
11170    }
11171
11172    #[test]
11173    fn test_regexp_substr_all_snowflake_identity_strip_group0() {
11174        let dialect = Dialect::get(DialectType::Snowflake);
11175        let result = dialect
11176            .transpile(
11177                "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e', 0)",
11178                DialectType::Snowflake,
11179            )
11180            .unwrap();
11181        assert_eq!(
11182            result[0],
11183            "SELECT REGEXP_SUBSTR_ALL(s, 'pattern', 1, 1, 'e')"
11184        );
11185    }
11186
11187    #[test]
11188    fn test_regexp_count_snowflake_to_duckdb_2arg() {
11189        let dialect = Dialect::get(DialectType::Snowflake);
11190        let result = dialect
11191            .transpile("SELECT REGEXP_COUNT(s, 'pattern')", DialectType::DuckDB)
11192            .unwrap();
11193        assert_eq!(
11194            result[0],
11195            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(s, 'pattern')) END"
11196        );
11197    }
11198
11199    #[test]
11200    fn test_regexp_count_snowflake_to_duckdb_3arg() {
11201        let dialect = Dialect::get(DialectType::Snowflake);
11202        let result = dialect
11203            .transpile("SELECT REGEXP_COUNT(s, 'pattern', 3)", DialectType::DuckDB)
11204            .unwrap();
11205        assert_eq!(
11206            result[0],
11207            "SELECT CASE WHEN 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 3), 'pattern')) END"
11208        );
11209    }
11210
11211    #[test]
11212    fn test_regexp_count_snowflake_to_duckdb_4arg_flags() {
11213        let dialect = Dialect::get(DialectType::Snowflake);
11214        let result = dialect
11215            .transpile(
11216                "SELECT REGEXP_COUNT(s, 'pattern', 1, 'i')",
11217                DialectType::DuckDB,
11218            )
11219            .unwrap();
11220        assert_eq!(
11221            result[0],
11222            "SELECT CASE WHEN '(?i)' || 'pattern' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING(s, 1), '(?i)' || 'pattern')) END"
11223        );
11224    }
11225
11226    #[test]
11227    fn test_regexp_count_snowflake_to_duckdb_4arg_flags_literal_string() {
11228        let dialect = Dialect::get(DialectType::Snowflake);
11229        let result = dialect
11230            .transpile(
11231                "SELECT REGEXP_COUNT('Hello World', 'L', 1, 'im')",
11232                DialectType::DuckDB,
11233            )
11234            .unwrap();
11235        assert_eq!(
11236            result[0],
11237            "SELECT CASE WHEN '(?im)' || 'L' = '' THEN 0 ELSE LENGTH(REGEXP_EXTRACT_ALL(SUBSTRING('Hello World', 1), '(?im)' || 'L')) END"
11238        );
11239    }
11240
11241    #[test]
11242    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos1_occ1() {
11243        let dialect = Dialect::get(DialectType::Snowflake);
11244        let result = dialect
11245            .transpile(
11246                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 1, 1)",
11247                DialectType::DuckDB,
11248            )
11249            .unwrap();
11250        assert_eq!(result[0], "SELECT REGEXP_REPLACE(s, 'pattern', 'repl')");
11251    }
11252
11253    #[test]
11254    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ0() {
11255        let dialect = Dialect::get(DialectType::Snowflake);
11256        let result = dialect
11257            .transpile(
11258                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 0)",
11259                DialectType::DuckDB,
11260            )
11261            .unwrap();
11262        assert_eq!(
11263            result[0],
11264            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl', 'g')"
11265        );
11266    }
11267
11268    #[test]
11269    fn test_regexp_replace_snowflake_to_duckdb_5arg_pos_gt1_occ1() {
11270        let dialect = Dialect::get(DialectType::Snowflake);
11271        let result = dialect
11272            .transpile(
11273                "SELECT REGEXP_REPLACE(s, 'pattern', 'repl', 3, 1)",
11274                DialectType::DuckDB,
11275            )
11276            .unwrap();
11277        assert_eq!(
11278            result[0],
11279            "SELECT SUBSTRING(s, 1, 2) || REGEXP_REPLACE(SUBSTRING(s, 3), 'pattern', 'repl')"
11280        );
11281    }
11282
11283    #[test]
11284    fn test_rlike_snowflake_to_duckdb_2arg() {
11285        let dialect = Dialect::get(DialectType::Snowflake);
11286        let result = dialect
11287            .transpile("SELECT RLIKE(a, b)", DialectType::DuckDB)
11288            .unwrap();
11289        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b)");
11290    }
11291
11292    #[test]
11293    fn test_rlike_snowflake_to_duckdb_3arg_flags() {
11294        let dialect = Dialect::get(DialectType::Snowflake);
11295        let result = dialect
11296            .transpile("SELECT RLIKE(a, b, 'i')", DialectType::DuckDB)
11297            .unwrap();
11298        assert_eq!(result[0], "SELECT REGEXP_FULL_MATCH(a, b, 'i')");
11299    }
11300
11301    #[test]
11302    fn test_regexp_extract_all_bigquery_to_snowflake_no_capture() {
11303        let dialect = Dialect::get(DialectType::BigQuery);
11304        let result = dialect
11305            .transpile(
11306                "SELECT REGEXP_EXTRACT_ALL(s, 'pattern')",
11307                DialectType::Snowflake,
11308            )
11309            .unwrap();
11310        assert_eq!(result[0], "SELECT REGEXP_SUBSTR_ALL(s, 'pattern')");
11311    }
11312
11313    #[test]
11314    fn test_regexp_extract_all_bigquery_to_snowflake_with_capture() {
11315        let dialect = Dialect::get(DialectType::BigQuery);
11316        let result = dialect
11317            .transpile(
11318                "SELECT REGEXP_EXTRACT_ALL(s, '(a)[0-9]')",
11319                DialectType::Snowflake,
11320            )
11321            .unwrap();
11322        assert_eq!(
11323            result[0],
11324            "SELECT REGEXP_SUBSTR_ALL(s, '(a)[0-9]', 1, 1, 'c', 1)"
11325        );
11326    }
11327
11328    #[test]
11329    fn test_regexp_instr_snowflake_to_duckdb_2arg() {
11330        let dialect = Dialect::get(DialectType::Snowflake);
11331        let result = dialect
11332            .transpile("SELECT REGEXP_INSTR(s, 'pattern')", DialectType::DuckDB)
11333            .unwrap();
11334        assert!(
11335            result[0].contains("CASE WHEN"),
11336            "Expected CASE WHEN in result: {}",
11337            result[0]
11338        );
11339        assert!(
11340            result[0].contains("LIST_SUM"),
11341            "Expected LIST_SUM in result: {}",
11342            result[0]
11343        );
11344    }
11345
11346    #[test]
11347    fn test_array_except_generic_to_duckdb() {
11348        let dialect = Dialect::get(DialectType::Generic);
11349        let result = dialect
11350            .transpile(
11351                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11352                DialectType::DuckDB,
11353            )
11354            .unwrap();
11355        eprintln!("ARRAY_EXCEPT Generic->DuckDB: {}", result[0]);
11356        assert!(
11357            result[0].contains("CASE WHEN"),
11358            "Expected CASE WHEN: {}",
11359            result[0]
11360        );
11361        assert!(
11362            result[0].contains("LIST_FILTER"),
11363            "Expected LIST_FILTER: {}",
11364            result[0]
11365        );
11366        assert!(
11367            result[0].contains("LIST_DISTINCT"),
11368            "Expected LIST_DISTINCT: {}",
11369            result[0]
11370        );
11371        assert!(
11372            result[0].contains("IS NOT DISTINCT FROM"),
11373            "Expected IS NOT DISTINCT FROM: {}",
11374            result[0]
11375        );
11376        assert!(
11377            result[0].contains("= 0"),
11378            "Expected = 0 filter: {}",
11379            result[0]
11380        );
11381    }
11382
11383    #[test]
11384    fn test_array_except_generic_to_snowflake() {
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::Snowflake,
11390            )
11391            .unwrap();
11392        eprintln!("ARRAY_EXCEPT Generic->Snowflake: {}", result[0]);
11393        assert_eq!(result[0], "SELECT ARRAY_EXCEPT([1, 2, 3], [2])");
11394    }
11395
11396    #[test]
11397    fn test_array_except_generic_to_presto() {
11398        let dialect = Dialect::get(DialectType::Generic);
11399        let result = dialect
11400            .transpile(
11401                "SELECT ARRAY_EXCEPT(ARRAY(1, 2, 3), ARRAY(2))",
11402                DialectType::Presto,
11403            )
11404            .unwrap();
11405        eprintln!("ARRAY_EXCEPT Generic->Presto: {}", result[0]);
11406        assert_eq!(result[0], "SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3], ARRAY[2])");
11407    }
11408
11409    #[test]
11410    fn test_array_except_snowflake_to_duckdb() {
11411        let dialect = Dialect::get(DialectType::Snowflake);
11412        let result = dialect
11413            .transpile("SELECT ARRAY_EXCEPT([1, 2, 3], [2])", DialectType::DuckDB)
11414            .unwrap();
11415        eprintln!("ARRAY_EXCEPT Snowflake->DuckDB: {}", result[0]);
11416        assert!(
11417            result[0].contains("CASE WHEN"),
11418            "Expected CASE WHEN: {}",
11419            result[0]
11420        );
11421        assert!(
11422            result[0].contains("LIST_TRANSFORM"),
11423            "Expected LIST_TRANSFORM: {}",
11424            result[0]
11425        );
11426    }
11427
11428    #[test]
11429    fn test_array_contains_snowflake_to_snowflake() {
11430        let dialect = Dialect::get(DialectType::Snowflake);
11431        let result = dialect
11432            .transpile(
11433                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11434                DialectType::Snowflake,
11435            )
11436            .unwrap();
11437        eprintln!("ARRAY_CONTAINS Snowflake->Snowflake: {}", result[0]);
11438        assert_eq!(result[0], "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])");
11439    }
11440
11441    #[test]
11442    fn test_array_contains_snowflake_to_duckdb() {
11443        let dialect = Dialect::get(DialectType::Snowflake);
11444        let result = dialect
11445            .transpile(
11446                "SELECT ARRAY_CONTAINS(x, [1, NULL, 3])",
11447                DialectType::DuckDB,
11448            )
11449            .unwrap();
11450        eprintln!("ARRAY_CONTAINS Snowflake->DuckDB: {}", result[0]);
11451        assert!(
11452            result[0].contains("CASE WHEN"),
11453            "Expected CASE WHEN: {}",
11454            result[0]
11455        );
11456        assert!(
11457            result[0].contains("NULLIF"),
11458            "Expected NULLIF: {}",
11459            result[0]
11460        );
11461        assert!(
11462            result[0].contains("ARRAY_CONTAINS"),
11463            "Expected ARRAY_CONTAINS: {}",
11464            result[0]
11465        );
11466    }
11467
11468    #[test]
11469    fn test_array_distinct_snowflake_to_duckdb() {
11470        let dialect = Dialect::get(DialectType::Snowflake);
11471        let result = dialect
11472            .transpile(
11473                "SELECT ARRAY_DISTINCT([1, 2, 2, 3, 1])",
11474                DialectType::DuckDB,
11475            )
11476            .unwrap();
11477        eprintln!("ARRAY_DISTINCT Snowflake->DuckDB: {}", result[0]);
11478        assert!(
11479            result[0].contains("CASE WHEN"),
11480            "Expected CASE WHEN: {}",
11481            result[0]
11482        );
11483        assert!(
11484            result[0].contains("LIST_DISTINCT"),
11485            "Expected LIST_DISTINCT: {}",
11486            result[0]
11487        );
11488        assert!(
11489            result[0].contains("LIST_APPEND"),
11490            "Expected LIST_APPEND: {}",
11491            result[0]
11492        );
11493        assert!(
11494            result[0].contains("LIST_FILTER"),
11495            "Expected LIST_FILTER: {}",
11496            result[0]
11497        );
11498    }
11499}